forked from SteamWar/SteamWar
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8204a181b
|
||
|
|
2208dcc0fb
|
||
|
|
b466216b3a | ||
|
|
5a862b251b | ||
|
|
60a82a685d | ||
|
|
573b0c14ae | ||
|
|
82abe7e20f | ||
|
|
34da59714e | ||
|
|
97071165cd | ||
|
|
634465fbf1 |
@@ -440,6 +440,13 @@ public final class GameModeConfig<M, W> {
|
|||||||
*/
|
*/
|
||||||
public final boolean DisableSnowMelt;
|
public final boolean DisableSnowMelt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable ice forming
|
||||||
|
*
|
||||||
|
* @implSpec {@code false} by default
|
||||||
|
*/
|
||||||
|
public final boolean DisableIceForm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allow leaving the arena area as spectator
|
* Allow leaving the arena area as spectator
|
||||||
*
|
*
|
||||||
@@ -470,6 +477,7 @@ public final class GameModeConfig<M, W> {
|
|||||||
BorderFromSchematic = loader.getInt("BorderFromSchematic", 21);
|
BorderFromSchematic = loader.getInt("BorderFromSchematic", 21);
|
||||||
GroundWalkable = loader.getBoolean("GroundWalkable", true);
|
GroundWalkable = loader.getBoolean("GroundWalkable", true);
|
||||||
DisableSnowMelt = loader.getBoolean("DisableSnowMelt", false);
|
DisableSnowMelt = loader.getBoolean("DisableSnowMelt", false);
|
||||||
|
DisableIceForm = loader.getBoolean("DisableIceForm", false);
|
||||||
Leaveable = loader.getBoolean("Leaveable", false);
|
Leaveable = loader.getBoolean("Leaveable", false);
|
||||||
AllowMissiles = loader.getBoolean("AllowMissiles", !EnterStages.isEmpty());
|
AllowMissiles = loader.getBoolean("AllowMissiles", !EnterStages.isEmpty());
|
||||||
NoFloor = loader.getBoolean("NoFloor", false);
|
NoFloor = loader.getBoolean("NoFloor", false);
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class NodeMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
|
|||||||
{ Optional.ofNullable(it?.value) })
|
{ Optional.ofNullable(it?.value) })
|
||||||
private set
|
private set
|
||||||
|
|
||||||
fun setParentId(id: Int?) {
|
fun setParentId(id: Int?) = useDb {
|
||||||
parent = Optional.ofNullable(id)
|
parent = Optional.ofNullable(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ class SteamwarUser(id: EntityID<Int>): IntEntity(id) {
|
|||||||
|
|
||||||
var discordId by SteamwarUserTable.discordId
|
var discordId by SteamwarUserTable.discordId
|
||||||
|
|
||||||
private val punishments by lazy { Punishment.getPunishmentsOfPlayer(id.value) }
|
val punishments by lazy { Punishment.getPunishmentsOfPlayer(id.value) }
|
||||||
private val perms by lazy { UserPerm.getPerms(id.value) }
|
private val perms by lazy { UserPerm.getPerms(id.value) }
|
||||||
private val prefix by lazy { perms.firstOrNull { UserPerm.prefixes.containsKey(it) }?.let { UserPerm.prefixes[it]} ?: UserPerm.emptyPrefix }
|
private val prefix by lazy { perms.firstOrNull { UserPerm.prefixes.containsKey(it) }?.let { UserPerm.prefixes[it]} ?: UserPerm.emptyPrefix }
|
||||||
|
|
||||||
@@ -254,27 +254,12 @@ class SteamwarUser(id: EntityID<Int>): IntEntity(id) {
|
|||||||
punishments[punishment] = Punishment.createPunishment(this@SteamwarUser.id.value, from, punishment, reason, time, perma)
|
punishments[punishment] = Punishment.createPunishment(this@SteamwarUser.id.value, from, punishment, reason, time, perma)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setJoinLocale(locale: Locale?) {
|
fun setLocale(locale: Locale?, manualeLocale: Boolean) {
|
||||||
if (locale == null || this.manualLocale) return
|
if (locale == null || (this.manualLocale && !manualLocale)) return
|
||||||
setCurrentLocale(locale)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setCurrentLocale(locale: Locale?) {
|
|
||||||
if (locale == null) return
|
|
||||||
useDb {
|
useDb {
|
||||||
this@SteamwarUser.locale = locale
|
this@SteamwarUser.locale = locale
|
||||||
}
|
this@SteamwarUser.manualLocale = manualeLocale
|
||||||
}
|
|
||||||
|
|
||||||
fun lockLocale() {
|
|
||||||
useDb {
|
|
||||||
this@SteamwarUser.manualLocale = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun unlockLocale() {
|
|
||||||
useDb {
|
|
||||||
this@SteamwarUser.manualLocale = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +36,6 @@ object TeamTable : IntIdTable("Team", "TeamID") {
|
|||||||
|
|
||||||
class Team(id: EntityID<Int>) : IntEntity(id) {
|
class Team(id: EntityID<Int>) : IntEntity(id) {
|
||||||
companion object : IntEntityClass<Team>(TeamTable) {
|
companion object : IntEntityClass<Team>(TeamTable) {
|
||||||
const val PUBLIC: Int = 0
|
|
||||||
|
|
||||||
private val teamCache = mutableMapOf<Int, Team>()
|
private val teamCache = mutableMapOf<Int, Team>()
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@@ -68,6 +66,7 @@ class Team(id: EntityID<Int>) : IntEntity(id) {
|
|||||||
var deleted by TeamTable.deleted
|
var deleted by TeamTable.deleted
|
||||||
private set
|
private set
|
||||||
val members by lazy { useDb { SteamwarUserTable.select(SteamwarUserTable.id).where { SteamwarUserTable.team eq teamId }.map { it[SteamwarUserTable.id].value } } }
|
val members by lazy { useDb { SteamwarUserTable.select(SteamwarUserTable.id).where { SteamwarUserTable.team eq teamId }.map { it[SteamwarUserTable.id].value } } }
|
||||||
|
val membersUser by lazy { useDb { SteamwarUser.find { SteamwarUserTable.team eq teamId }.toList() } }
|
||||||
|
|
||||||
fun size() = useDb { SteamwarUser.find { SteamwarUserTable.team eq teamId }.count().toInt() }
|
fun size() = useDb { SteamwarUser.find { SteamwarUserTable.team eq teamId }.count().toInt() }
|
||||||
fun disband(user: SteamwarUser) = useDb {
|
fun disband(user: SteamwarUser) = useDb {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class TeamTeilnahme(id: EntityID<CompositeID>) : CompositeEntity(id) {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getEvents(teamId: Int) = useDb {
|
fun getEvents(teamId: Int) = useDb {
|
||||||
find { TeamTeilnahmeTable.teamId eq teamId }.map { Event.byId(it.eventId.value) }.toSet()
|
find { TeamTeilnahmeTable.teamId eq teamId }.mapNotNull { Event.byId(it.eventId.value) }.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ import de.steamwar.fightsystem.states.FightState;
|
|||||||
import de.steamwar.fightsystem.states.OneShotStateDependent;
|
import de.steamwar.fightsystem.states.OneShotStateDependent;
|
||||||
import de.steamwar.fightsystem.states.StateDependentListener;
|
import de.steamwar.fightsystem.states.StateDependentListener;
|
||||||
import de.steamwar.fightsystem.utils.*;
|
import de.steamwar.fightsystem.utils.*;
|
||||||
import de.steamwar.fightsystem.winconditions.Wincondition;
|
|
||||||
import de.steamwar.fightsystem.winconditions.WinconditionComparisonTimeout;
|
|
||||||
import de.steamwar.fightsystem.winconditions.Winconditions;
|
|
||||||
import de.steamwar.linkage.AbstractLinker;
|
import de.steamwar.linkage.AbstractLinker;
|
||||||
import de.steamwar.linkage.SpigotLinker;
|
import de.steamwar.linkage.SpigotLinker;
|
||||||
import de.steamwar.message.Message;
|
import de.steamwar.message.Message;
|
||||||
@@ -43,6 +40,7 @@ import de.steamwar.sql.NodeData;
|
|||||||
import de.steamwar.sql.SchematicNode;
|
import de.steamwar.sql.SchematicNode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.GameRule;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
public class FightSystem extends JavaPlugin {
|
public class FightSystem extends JavaPlugin {
|
||||||
@@ -100,6 +98,11 @@ public class FightSystem extends JavaPlugin {
|
|||||||
new StateDependentListener(ArenaMode.All, FightState.All, BountifulWrapper.impl.newDenyArrowPickupListener());
|
new StateDependentListener(ArenaMode.All, FightState.All, BountifulWrapper.impl.newDenyArrowPickupListener());
|
||||||
new OneShotStateDependent(ArenaMode.All, FightState.PreSchemSetup, () -> Fight.playSound(SWSound.BLOCK_NOTE_PLING.getSound(), 100.0f, 2.0f));
|
new OneShotStateDependent(ArenaMode.All, FightState.PreSchemSetup, () -> Fight.playSound(SWSound.BLOCK_NOTE_PLING.getSound(), 100.0f, 2.0f));
|
||||||
new OneShotStateDependent(ArenaMode.Test, FightState.All, WorldEditRendererCUIEditor::new);
|
new OneShotStateDependent(ArenaMode.Test, FightState.All, WorldEditRendererCUIEditor::new);
|
||||||
|
try {
|
||||||
|
Bukkit.getWorlds().get(0).setGameRule(GameRule.REDUCED_DEBUG_INFO, ArenaMode.AntiTest.contains(Config.mode));
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ignore if failed!
|
||||||
|
}
|
||||||
|
|
||||||
techHider = new TechHiderWrapper();
|
techHider = new TechHiderWrapper();
|
||||||
hullHider = new HullHider();
|
hullHider = new HullHider();
|
||||||
|
|||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.fightsystem.listener;
|
||||||
|
|
||||||
|
import de.steamwar.fightsystem.Config;
|
||||||
|
import de.steamwar.fightsystem.states.FightState;
|
||||||
|
import de.steamwar.fightsystem.states.StateDependentListener;
|
||||||
|
import de.steamwar.linkage.Linked;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.block.BlockFormEvent;
|
||||||
|
|
||||||
|
@Linked
|
||||||
|
public class BlockFormListener implements Listener {
|
||||||
|
|
||||||
|
public BlockFormListener() {
|
||||||
|
new StateDependentListener(Config.GameModeConfig.Arena.DisableIceForm, FightState.All, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onBlockForm(BlockFormEvent event) {
|
||||||
|
if (Config.ArenaRegion.inRegion(event.getBlock()) && event.getNewState().getType() == Material.ICE) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ package de.steamwar.towerrun.commands;
|
|||||||
import de.steamwar.command.SWCommand;
|
import de.steamwar.command.SWCommand;
|
||||||
import de.steamwar.command.TypeValidator;
|
import de.steamwar.command.TypeValidator;
|
||||||
import de.steamwar.linkage.Linked;
|
import de.steamwar.linkage.Linked;
|
||||||
|
import de.steamwar.linkage.LinkedInstance;
|
||||||
import de.steamwar.sql.SteamwarUser;
|
import de.steamwar.sql.SteamwarUser;
|
||||||
import de.steamwar.sql.UserPerm;
|
import de.steamwar.sql.UserPerm;
|
||||||
import de.steamwar.towerrun.TowerRun;
|
import de.steamwar.towerrun.TowerRun;
|
||||||
@@ -30,6 +31,8 @@ import org.bukkit.entity.Player;
|
|||||||
|
|
||||||
@Linked
|
@Linked
|
||||||
public class StartCommand extends SWCommand {
|
public class StartCommand extends SWCommand {
|
||||||
|
|
||||||
|
@LinkedInstance
|
||||||
private LobbyCountdown countdown;
|
private LobbyCountdown countdown;
|
||||||
|
|
||||||
public StartCommand() {
|
public StartCommand() {
|
||||||
|
|||||||
@@ -159,8 +159,8 @@ public class TowerGenerator {
|
|||||||
noKeyFloors--;
|
noKeyFloors--;
|
||||||
if (!chestBlocks.isEmpty() && noKeyFloors < 0 && random.nextDouble() < config.keyChance) {
|
if (!chestBlocks.isEmpty() && noKeyFloors < 0 && random.nextDouble() < config.keyChance) {
|
||||||
noKeyFloors = random.nextInt(config.maxNoKeyFloors - config.minNoKeyFloors) + config.minNoKeyFloors;
|
noKeyFloors = random.nextInt(config.maxNoKeyFloors - config.minNoKeyFloors) + config.minNoKeyFloors;
|
||||||
for (int i = 0; i < 2; i++) {
|
for (int i = 0; i < 2 && !chestBlocks.isEmpty(); i++) {
|
||||||
Container container = chestBlocks.get(random.nextInt(chestBlocks.size()));
|
Container container = chestBlocks.remove(random.nextInt(chestBlocks.size()));
|
||||||
keys.add(container.getLocation());
|
keys.add(container.getLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import org.bukkit.event.entity.EntityRegainHealthEvent;
|
|||||||
import org.bukkit.event.entity.ItemSpawnEvent;
|
import org.bukkit.event.entity.ItemSpawnEvent;
|
||||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||||
import org.bukkit.event.player.PlayerInteractEvent;
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.scheduler.BukkitRunnable;
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -158,10 +159,16 @@ public class IngameListener extends GameStateBukkitListener {
|
|||||||
public void onKeyUse(PlayerInteractEvent event) {
|
public void onKeyUse(PlayerInteractEvent event) {
|
||||||
if (!event.hasItem()) return;
|
if (!event.hasItem()) return;
|
||||||
if (event.getItem().getType() != Material.LEVER) return;
|
if (event.getItem().getType() != Material.LEVER) return;
|
||||||
event.setCancelled(true);
|
|
||||||
if (!event.hasBlock()) return;
|
if (!event.hasBlock()) return;
|
||||||
if (event.getClickedBlock().getType() != Material.IRON_DOOR) return;
|
if (event.getClickedBlock().getType() != Material.IRON_DOOR) return;
|
||||||
event.getPlayer().getInventory().setItemInMainHand(null);
|
if (event.getHand() == null) return;
|
||||||
|
event.setCancelled(true);
|
||||||
|
ItemStack itemStack = event.getItem();
|
||||||
|
itemStack.setAmount(event.getItem().getAmount() - 1);
|
||||||
|
switch (event.getHand()) {
|
||||||
|
case OFF_HAND -> event.getPlayer().getInventory().setItemInOffHand(itemStack);
|
||||||
|
case HAND -> event.getPlayer().getInventory().setItemInMainHand(itemStack);
|
||||||
|
}
|
||||||
event.getClickedBlock().breakNaturally();
|
event.getClickedBlock().breakNaturally();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,6 +230,8 @@ public class IngameListener extends GameStateBukkitListener {
|
|||||||
shouldMelt(block.getRelative(0, 0, -1));
|
shouldMelt(block.getRelative(0, 0, -1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Random RANDOM = new Random();
|
||||||
|
|
||||||
private void shouldMelt(Block block) {
|
private void shouldMelt(Block block) {
|
||||||
if (block.getType().isBurnable()) return;
|
if (block.getType().isBurnable()) return;
|
||||||
if (block.getType().isAir()) return;
|
if (block.getType().isAir()) return;
|
||||||
@@ -269,7 +278,9 @@ public class IngameListener extends GameStateBukkitListener {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Pos pos = new Pos(block.getLocation().getBlockX(), block.getLocation().getBlockY(), block.getLocation().getBlockZ());
|
Pos pos = new Pos(block.getLocation().getBlockX(), block.getLocation().getBlockY(), block.getLocation().getBlockZ());
|
||||||
blocksToMelt.putIfAbsent(pos, time + meltingTime + 1);
|
int delay = meltingTime + 1 + RANDOM.nextInt(30*20)-30*10;
|
||||||
|
if (delay < 0) delay = meltingTime + 1;
|
||||||
|
blocksToMelt.putIfAbsent(pos, time + delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ public abstract class OutsideWincondition extends WinCondition {
|
|||||||
@EventHandler
|
@EventHandler
|
||||||
public void onPlayerMove(PlayerMoveEvent event) {
|
public void onPlayerMove(PlayerMoveEvent event) {
|
||||||
if (event.getTo().getY() > WorldConfig.ESCAPE_HEIGHT) {
|
if (event.getTo().getY() > WorldConfig.ESCAPE_HEIGHT) {
|
||||||
|
if (event.getTo().getY() > WorldConfig.ESCAPE_HEIGHT + 10 && Arrays.stream(WorldConfig.REGIONS).noneMatch(region -> region.contains(event.getTo().toVector()))) {
|
||||||
|
TowerRunPlayer tPlayer = TowerRunPlayer.get(event.getPlayer());
|
||||||
|
tPlayer.player().damage(Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,19 +17,6 @@
|
|||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
LOCALE = English
|
|
||||||
|
|
||||||
SETTINGS_TITLE = Settings
|
|
||||||
SETTINGS_LOCALE_ITEM = §fLocale
|
|
||||||
SETTINGS_LOCALE_CURRENT = §fCurrent locale §e{0}§8.
|
|
||||||
SETTINGS_LOCALE_LOCKED = §cLocked§f to the current locale§8.
|
|
||||||
SETTINGS_LOCALE_UNLOCKED = §aChanged§f by Client locale§8.
|
|
||||||
SETTINGS_PREFIX_ITEM = §fChat prefix
|
|
||||||
SETTINGS_PREFIX_SELECTED = §a> {0}
|
|
||||||
SETTINGS_PREFIX_UNSELECTED = §f> {0}
|
|
||||||
SETTINGS_PREFIX_SW = §eS§8W
|
|
||||||
SETTINGS_PREFIX_TEAM = §{0}{1}
|
|
||||||
|
|
||||||
COMMAND_SYSTEM_ERROR = §cError executing the command!
|
COMMAND_SYSTEM_ERROR = §cError executing the command!
|
||||||
COMMAND_HELP_HEAD=§7---=== (§e{0}§7) ===---
|
COMMAND_HELP_HEAD=§7---=== (§e{0}§7) ===---
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,6 @@
|
|||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
LOCALE = Deutsch
|
|
||||||
|
|
||||||
COMMAND_SYSTEM_ERROR = §cFehler beim Ausführen des Befehls!
|
COMMAND_SYSTEM_ERROR = §cFehler beim Ausführen des Befehls!
|
||||||
|
|
||||||
PREFIX=§eSteam§8War»
|
PREFIX=§eSteam§8War»
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.velocitycore.commands;
|
||||||
|
|
||||||
|
import de.steamwar.command.SWCommand;
|
||||||
|
import de.steamwar.linkage.Linked;
|
||||||
|
import de.steamwar.messages.PlayerChatter;
|
||||||
|
import de.steamwar.network.packets.server.LocaleInvalidationPacket;
|
||||||
|
import de.steamwar.velocitycore.network.NetworkSender;
|
||||||
|
|
||||||
|
@Linked
|
||||||
|
public class SetLocaleCommand extends SWCommand {
|
||||||
|
|
||||||
|
public SetLocaleCommand() {
|
||||||
|
super("setlocale", "setlanguage");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Register
|
||||||
|
public void genericCommand(PlayerChatter sender) {
|
||||||
|
sender.user().setLocale(sender.getPlayer().getPlayerSettings().getLocale(), true);
|
||||||
|
sender.withPlayer(player -> NetworkSender.send(player, new LocaleInvalidationPacket(sender.user().getId())));
|
||||||
|
sender.system("LOCK_LOCALE_CHANGED");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is a part of the SteamWar software.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.steamwar.velocitycore.commands;
|
|
||||||
|
|
||||||
import de.steamwar.command.SWCommand;
|
|
||||||
import de.steamwar.linkage.Linked;
|
|
||||||
import de.steamwar.messages.Message;
|
|
||||||
import de.steamwar.messages.PlayerChatter;
|
|
||||||
import de.steamwar.sql.SteamwarUser;
|
|
||||||
import de.steamwar.sql.Team;
|
|
||||||
import de.steamwar.sql.UserPerm;
|
|
||||||
import de.steamwar.velocitycore.inventory.SWInventory;
|
|
||||||
import de.steamwar.velocitycore.inventory.SWItem;
|
|
||||||
|
|
||||||
@Linked
|
|
||||||
public class SettingsCommand extends SWCommand {
|
|
||||||
|
|
||||||
public SettingsCommand() {
|
|
||||||
super("settings");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Register
|
|
||||||
public void genericCommand(PlayerChatter sender) {
|
|
||||||
SWInventory inventory = new SWInventory(sender, 9, new Message("SETTINGS_TITLE"));
|
|
||||||
SteamwarUser user = sender.user();
|
|
||||||
String localeItem = user.getManualLocale() ? "BOOK" : "BOOK_AND_QUILL";
|
|
||||||
SWItem localeSwItem = new SWItem(localeItem, new Message("SETTINGS_LOCALE_ITEM"));
|
|
||||||
localeSwItem.addLore(new Message("SETTINGS_LOCALE_CURRENT", new Message("LOCALE")));
|
|
||||||
if (user.getManualLocale()) {
|
|
||||||
localeSwItem.addLore(new Message("SETTINGS_LOCALE_LOCKED"));
|
|
||||||
} else {
|
|
||||||
localeSwItem.addLore(new Message("SETTINGS_LOCALE_UNLOCKED"));
|
|
||||||
}
|
|
||||||
inventory.addItem(2, localeSwItem, click -> {
|
|
||||||
if (user.getManualLocale()) {
|
|
||||||
user.unlockLocale();
|
|
||||||
} else {
|
|
||||||
user.lockLocale();
|
|
||||||
}
|
|
||||||
genericCommand(sender);
|
|
||||||
});
|
|
||||||
|
|
||||||
Team team = Team.byId(user.getTeam());
|
|
||||||
String chatPrefixItem = !user.hasPerm(UserPerm.TEAM) ? "BARRIER" : "NAME_TAG";
|
|
||||||
SWItem swItem = new SWItem(chatPrefixItem, new Message("SETTINGS_PREFIX_ITEM"));
|
|
||||||
if (user.hasPerm(UserPerm.TEAM)) {
|
|
||||||
swItem.addLore(new Message("SETTINGS_PREFIX_SELECTED", new Message("SETTINGS_PREFIX_SW")));
|
|
||||||
}
|
|
||||||
if (user.getTeam() != Team.PUBLIC) {
|
|
||||||
swItem.addLore(new Message("SETTINGS_PREFIX_UNSELECTED", new Message("SETTINGS_PREFIX_TEAM", team.getTeamColor(), team.getTeamKuerzel())));
|
|
||||||
}
|
|
||||||
inventory.addItem(6, swItem, click -> {
|
|
||||||
});
|
|
||||||
|
|
||||||
inventory.open();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -60,7 +60,7 @@ public class TeamCommand extends SWCommand {
|
|||||||
helpMessages(sender, "TEAM_HELP_HEADER", "TEAM_HELP_LIST", "TEAM_HELP_INFO");
|
helpMessages(sender, "TEAM_HELP_HEADER", "TEAM_HELP_LIST", "TEAM_HELP_INFO");
|
||||||
|
|
||||||
SteamwarUser user = sender.user();
|
SteamwarUser user = sender.user();
|
||||||
if(user.getTeam() == Team.PUBLIC) {
|
if(user.getTeam() == 0) {
|
||||||
helpMessages(sender, "TEAM_HELP_CREATE", "TEAM_HELP_JOIN");
|
helpMessages(sender, "TEAM_HELP_CREATE", "TEAM_HELP_JOIN");
|
||||||
}else{
|
}else{
|
||||||
helpMessages(sender, "TEAM_HELP_CHAT", "TEAM_HELP_EVENT", "TEAM_HELP_LEAVE");
|
helpMessages(sender, "TEAM_HELP_CHAT", "TEAM_HELP_EVENT", "TEAM_HELP_LEAVE");
|
||||||
@@ -162,7 +162,7 @@ public class TeamCommand extends SWCommand {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.setTeam(Team.PUBLIC);
|
user.setTeam(0);
|
||||||
|
|
||||||
if(teamSize == 1)
|
if(teamSize == 1)
|
||||||
team.disband(user);
|
team.disband(user);
|
||||||
@@ -182,7 +182,7 @@ public class TeamCommand extends SWCommand {
|
|||||||
if(notDuringEvent(sender))
|
if(notDuringEvent(sender))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(target.getTeam() != Team.PUBLIC){
|
if(target.getTeam() != 0){
|
||||||
sender.system("TEAM_INVITE_IN_TEAM");
|
sender.system("TEAM_INVITE_IN_TEAM");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -218,7 +218,7 @@ public class TeamCommand extends SWCommand {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
target.setTeam(Team.PUBLIC);
|
target.setTeam(0);
|
||||||
|
|
||||||
sender.system("TEAM_REMOVE_REMOVED");
|
sender.system("TEAM_REMOVE_REMOVED");
|
||||||
Chatter.of(target).system("TEAM_REMOVE_REMOVED_TARGET");
|
Chatter.of(target).system("TEAM_REMOVE_REMOVED_TARGET");
|
||||||
@@ -493,7 +493,7 @@ public class TeamCommand extends SWCommand {
|
|||||||
@Validator(value = "isNotInTeam", local = true)
|
@Validator(value = "isNotInTeam", local = true)
|
||||||
public TypeValidator<Chatter> isNotInTeamValidator() {
|
public TypeValidator<Chatter> isNotInTeamValidator() {
|
||||||
return (sender, value, messageSender) -> {
|
return (sender, value, messageSender) -> {
|
||||||
if (sender.user().getTeam() != Team.PUBLIC) {
|
if (sender.user().getTeam() != 0) {
|
||||||
messageSender.send("TEAM_IN_TEAM");
|
messageSender.send("TEAM_IN_TEAM");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -504,7 +504,7 @@ public class TeamCommand extends SWCommand {
|
|||||||
@Validator(value = "isInTeam", local = true)
|
@Validator(value = "isInTeam", local = true)
|
||||||
public TypeValidator<Chatter> isInTeamValidator() {
|
public TypeValidator<Chatter> isInTeamValidator() {
|
||||||
return (sender, value, messageSender) -> {
|
return (sender, value, messageSender) -> {
|
||||||
if (sender.user().getTeam() == Team.PUBLIC) {
|
if (sender.user().getTeam() == 0) {
|
||||||
messageSender.send("TEAM_NOT_IN_TEAM");
|
messageSender.send("TEAM_NOT_IN_TEAM");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -516,7 +516,7 @@ public class TeamCommand extends SWCommand {
|
|||||||
public TypeValidator<Chatter> isLeaderValidator() {
|
public TypeValidator<Chatter> isLeaderValidator() {
|
||||||
return (sender, value, messageSender) -> {
|
return (sender, value, messageSender) -> {
|
||||||
SteamwarUser user = sender.user();
|
SteamwarUser user = sender.user();
|
||||||
if (user.getTeam() == Team.PUBLIC) {
|
if (user.getTeam() == 0) {
|
||||||
messageSender.send("TEAM_NOT_IN_TEAM");
|
messageSender.send("TEAM_NOT_IN_TEAM");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ public class BanListener extends BasicListener {
|
|||||||
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
|
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
|
||||||
String ip = IPSanitizer.getTrueAddress(player).getHostAddress();
|
String ip = IPSanitizer.getTrueAddress(player).getHostAddress();
|
||||||
if (user.isPunished(Punishment.PunishmentType.Ban)) {
|
if (user.isPunished(Punishment.PunishmentType.Ban)) {
|
||||||
BannedUserIPs.banIP(user.getId(), ip);
|
if (!player.getUsername().startsWith(".")) {
|
||||||
|
BannedUserIPs.banIP(user.getId(), ip);
|
||||||
|
}
|
||||||
Chatter.of(event).system(PunishmentCommand.punishmentMessage(user, Punishment.PunishmentType.Ban));
|
Chatter.of(event).system(PunishmentCommand.punishmentMessage(user, Punishment.PunishmentType.Ban));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public class SettingsChangedListener extends BasicListener {
|
|||||||
VelocityCore.schedule(() -> {
|
VelocityCore.schedule(() -> {
|
||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
|
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
|
||||||
user.setJoinLocale(event.getPlayerSettings().getLocale());
|
user.setLocale(event.getPlayerSettings().getLocale(), false);
|
||||||
NetworkSender.send(player, new LocaleInvalidationPacket(user.getId()));
|
NetworkSender.send(player, new LocaleInvalidationPacket(user.getId()));
|
||||||
}).schedule();
|
}).schedule();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,61 +47,67 @@ data class UsernamePassword(val name: String, val password: String, val keepLogg
|
|||||||
|
|
||||||
fun Route.configureAuth() {
|
fun Route.configureAuth() {
|
||||||
route("/auth") {
|
route("/auth") {
|
||||||
val client = HttpClient(Java) {
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
json()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
val request = call.receive<UsernamePassword>()
|
|
||||||
|
|
||||||
SteamwarUser.clear()
|
|
||||||
val user = SteamwarUser.get(request.name)
|
|
||||||
val valid = user?.verifyPassword(request.password) ?: false
|
|
||||||
|
|
||||||
if (!valid) {
|
|
||||||
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid username or password", "invalid"))
|
|
||||||
return@post
|
|
||||||
}
|
|
||||||
|
|
||||||
call.sessions.set(SWUserSession(user.getId()))
|
|
||||||
call.respond(ResponseUser(user))
|
|
||||||
}
|
|
||||||
|
|
||||||
delete {
|
delete {
|
||||||
call.sessions.clear<SWUserSession>()
|
call.sessions.clear<SWUserSession>()
|
||||||
call.respond(HttpStatusCode.NoContent)
|
call.respond(HttpStatusCode.NoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
route("/discord") {
|
configureDiscordAuth()
|
||||||
post {
|
configurePasswordAuth()
|
||||||
val token = call.receiveText()
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val res = client.get("https://discord.com/api/v10/oauth2/@me") {
|
fun Route.configurePasswordAuth() {
|
||||||
headers {
|
post {
|
||||||
append("Authorization", "Bearer $token")
|
val request = call.receive<UsernamePassword>()
|
||||||
}
|
|
||||||
}
|
|
||||||
val resJson = Json.parseToJsonElement(res.bodyAsText()).jsonObject
|
|
||||||
val discordId = resJson["user"]?.jsonObject["id"]?.jsonPrimitive?.content
|
|
||||||
|
|
||||||
if (discordId == null) {
|
SteamwarUser.clear()
|
||||||
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid Discord token", "invalid"))
|
val user = SteamwarUser.get(request.name)
|
||||||
return@post
|
val valid = user?.verifyPassword(request.password) ?: false
|
||||||
}
|
|
||||||
|
|
||||||
SteamwarUser.clear()
|
if (!valid) {
|
||||||
val user = SteamwarUser.get(discordId.toLong())
|
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid username or password", "invalid"))
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
if (user == null) {
|
call.sessions.set(SWUserSession(user.getId()))
|
||||||
call.respond(HttpStatusCode.Forbidden, ResponseError("Discord account not linked", "not_linked"))
|
call.respond(ResponseUser(user))
|
||||||
return@post
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
call.sessions.set(SWUserSession(user.getId()))
|
fun Route.configureDiscordAuth() {
|
||||||
call.respond(ResponseUser(user))
|
val client = HttpClient(Java) {
|
||||||
}
|
install(ContentNegotiation) {
|
||||||
|
json()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
post("/discord") {
|
||||||
|
val token = call.receiveText()
|
||||||
|
|
||||||
|
val res = client.get("https://discord.com/api/v10/oauth2/@me") {
|
||||||
|
headers {
|
||||||
|
append("Authorization", "Bearer $token")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val resJson = Json.parseToJsonElement(res.bodyAsText()).jsonObject
|
||||||
|
val discordId = resJson["user"]?.jsonObject["id"]?.jsonPrimitive?.content
|
||||||
|
|
||||||
|
if (discordId == null) {
|
||||||
|
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid Discord token", "invalid"))
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
|
SteamwarUser.clear()
|
||||||
|
val user = SteamwarUser.get(discordId.toLong())
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.Forbidden, ResponseError("Discord account not linked", "not_linked"))
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
|
call.sessions.set(SWUserSession(user.getId()))
|
||||||
|
call.respond(ResponseUser(user))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ data class ResponseUser(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class ResponseUserList(val entries: List<ResponseUser>, val rows: Long)
|
data class ResponseUserList(val entries: List<ResponseUser>, val rows: Long)
|
||||||
|
|
||||||
private fun Query.addUserFilter(
|
fun Query.addUserFilter(
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
uuid: UUID? = null,
|
uuid: UUID? = null,
|
||||||
team: Set<Int>? = null,
|
team: Set<Int>? = null,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import io.ktor.server.request.*
|
|||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.jetbrains.exposed.v1.core.ResultRow
|
||||||
import java.sql.Timestamp
|
import java.sql.Timestamp
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@@ -60,6 +61,8 @@ data class ResponseEventFight(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class ResponseTeam(val id: Int, val name: String, val kuerzel: String, val color: String) {
|
data class ResponseTeam(val id: Int, val name: String, val kuerzel: String, val color: String) {
|
||||||
constructor(team: Team) : this(team.teamId, team.teamName, team.teamKuerzel, team.teamColor)
|
constructor(team: Team) : this(team.teamId, team.teamName, team.teamKuerzel, team.teamColor)
|
||||||
|
|
||||||
|
constructor(row: ResultRow) : this(row[TeamTable.id].value, row[TeamTable.name], row[TeamTable.kuerzel], row[TeamTable.color])
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|||||||
@@ -19,6 +19,12 @@
|
|||||||
|
|
||||||
package de.steamwar.routes
|
package de.steamwar.routes
|
||||||
|
|
||||||
|
import de.steamwar.routes.v2.configureAuthV2
|
||||||
|
import de.steamwar.routes.v2.configureGameModeRoutes
|
||||||
|
import de.steamwar.routes.v2.configureSchematicsV2Route
|
||||||
|
import de.steamwar.routes.v2.configureSteamWarRoute
|
||||||
|
import de.steamwar.routes.v2.configureTeamRoutes
|
||||||
|
import de.steamwar.routes.v2.configureUsersRouteV2
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.auth.*
|
import io.ktor.server.auth.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
@@ -34,6 +40,17 @@ fun Application.configureRoutes() {
|
|||||||
configureSchematic()
|
configureSchematic()
|
||||||
configureAuth()
|
configureAuth()
|
||||||
configureAuditLog()
|
configureAuditLog()
|
||||||
|
route("/v2") {
|
||||||
|
configureAuditLog()
|
||||||
|
configurePage()
|
||||||
|
configureEventsRoute()
|
||||||
|
configureAuthV2()
|
||||||
|
configureTeamRoutes()
|
||||||
|
configureSteamWarRoute()
|
||||||
|
configureUsersRouteV2()
|
||||||
|
configureSchematicsV2Route()
|
||||||
|
configureGameModeRoutes()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,8 +43,31 @@ import java.util.*
|
|||||||
import java.util.zip.GZIPInputStream
|
import java.util.zip.GZIPInputStream
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ResponseSchematic(val name: String, val id: Int, val type: String?, val owner: Int, val item: String, val lastUpdate: Long, val rank: Int, val replaceColor: Boolean, val allowReplay: Boolean) {
|
data class ResponseSchematic(
|
||||||
constructor(node: SchematicNode) : this(node.name, node.getId(), node.schemtype?.name(), node.owner, node.item, node.lastUpdate.time, node.rank, node.replaceColor(), node.allowReplay())
|
val name: String,
|
||||||
|
val id: Int,
|
||||||
|
val type: String?,
|
||||||
|
val owner: Int,
|
||||||
|
val item: String,
|
||||||
|
val lastUpdate: Long,
|
||||||
|
val replaceColor: Boolean,
|
||||||
|
val allowReplay: Boolean,
|
||||||
|
val members: List<ResponseUser>
|
||||||
|
) {
|
||||||
|
constructor(node: SchematicNode) : this(
|
||||||
|
node.name,
|
||||||
|
node.getId(),
|
||||||
|
node.schemtype.name(),
|
||||||
|
node.owner,
|
||||||
|
node.item,
|
||||||
|
node.lastUpdate.time,
|
||||||
|
node.replaceColor(),
|
||||||
|
node.allowReplay(),
|
||||||
|
node.members.map {
|
||||||
|
ResponseUser(
|
||||||
|
SteamwarUser.byId(it.member)!!
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -72,9 +95,12 @@ fun Route.configureSchematic() {
|
|||||||
val node = call.receiveSchematic() ?: return@get
|
val node = call.receiveSchematic() ?: return@get
|
||||||
|
|
||||||
val user = call.principal<SWAuthPrincipal>()?.user
|
val user = call.principal<SWAuthPrincipal>()?.user
|
||||||
if(user != null && !node.accessibleByUser(user)) {
|
if (user != null && !node.accessibleByUser(user)) {
|
||||||
call.respond(HttpStatusCode.Forbidden)
|
call.respond(HttpStatusCode.Forbidden)
|
||||||
SWException.log("User ${user.userName} tried to download schematic ${node.name} without permission", user.id.toString())
|
SWException.log(
|
||||||
|
"User ${user.userName} tried to download schematic ${node.name} without permission",
|
||||||
|
user.id.toString()
|
||||||
|
)
|
||||||
return@get
|
return@get
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,8 +109,15 @@ fun Route.configureSchematic() {
|
|||||||
return@get
|
return@get
|
||||||
}
|
}
|
||||||
|
|
||||||
call.response.header("Content-Disposition", "attachment; filename=\"${node.name}${data.nodeFormat.fileEnding}\"")
|
call.response.header(
|
||||||
call.respondBytes(data.schemData(false).readAllBytes(), contentType = ContentType.Application.OctetStream, status = HttpStatusCode.OK)
|
"Content-Disposition",
|
||||||
|
"attachment; filename=\"${node.name}${data.nodeFormat.fileEnding}\""
|
||||||
|
)
|
||||||
|
call.respondBytes(
|
||||||
|
data.schemData(false).readAllBytes(),
|
||||||
|
contentType = ContentType.Application.OctetStream,
|
||||||
|
status = HttpStatusCode.OK
|
||||||
|
)
|
||||||
}
|
}
|
||||||
get("/info") {
|
get("/info") {
|
||||||
val node = call.receiveSchematic() ?: return@get
|
val node = call.receiveSchematic() ?: return@get
|
||||||
@@ -100,18 +133,22 @@ fun Route.configureSchematic() {
|
|||||||
val schemName = file.name.substringBeforeLast(".")
|
val schemName = file.name.substringBeforeLast(".")
|
||||||
|
|
||||||
if (SchematicNode.invalidSchemName(arrayOf(schemName))) {
|
if (SchematicNode.invalidSchemName(arrayOf(schemName))) {
|
||||||
call.respond(HttpStatusCode.BadRequest, ResponseError(
|
call.respond(
|
||||||
error = "INVALID_NAME"
|
HttpStatusCode.BadRequest, ResponseError(
|
||||||
))
|
error = "INVALID_NAME"
|
||||||
|
)
|
||||||
|
)
|
||||||
return@post
|
return@post
|
||||||
}
|
}
|
||||||
|
|
||||||
val schemType = file.name.substringAfterLast(".")
|
val schemType = file.name.substringAfterLast(".")
|
||||||
|
|
||||||
if (schemType != "schem" && schemType != "schematic") {
|
if (schemType != "schem" && schemType != "schematic") {
|
||||||
call.respond(HttpStatusCode.BadRequest, ResponseError(
|
call.respond(
|
||||||
error = "INVALID_SUFFIX"
|
HttpStatusCode.BadRequest, ResponseError(
|
||||||
))
|
error = "INVALID_SUFFIX"
|
||||||
|
)
|
||||||
|
)
|
||||||
return@post
|
return@post
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,22 +183,27 @@ fun Route.configureSchematic() {
|
|||||||
.value
|
.value
|
||||||
|
|
||||||
if (fawe.equals("2.12.3-SNAPSHOT")) {
|
if (fawe.equals("2.12.3-SNAPSHOT")) {
|
||||||
SWException.log("Schematic with Bugged Version Uploaded", """
|
SWException.log(
|
||||||
|
"Schematic with Bugged Version Uploaded", """
|
||||||
Schematic=$schemName
|
Schematic=$schemName
|
||||||
User=${user.userName}
|
User=${user.userName}
|
||||||
Id=${user.id}
|
Id=${user.id}
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} catch (_: Exception) {}
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeData.saveFromStream(node, content.inputStream(), version)
|
NodeData.saveFromStream(node, content.inputStream(), version)
|
||||||
|
|
||||||
call.respond(ResponseSchematic(node))
|
call.respond(ResponseSchematic(node))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
call.respond(HttpStatusCode.BadRequest, ResponseError(
|
call.respond(
|
||||||
error = e.message ?: "GENERIC", code = "UPLOAD_ERROR"
|
HttpStatusCode.BadRequest, ResponseError(
|
||||||
))
|
error = e.message ?: "GENERIC", code = "UPLOAD_ERROR"
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +220,7 @@ suspend fun ApplicationCall.receiveSchematic(fieldName: String = "code", delete:
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if(dl.timestamp.toInstant().plus(Duration.of(5, ChronoUnit.MINUTES)).isBefore(Instant.now())) {
|
if (dl.timestamp.toInstant().plus(Duration.of(5, ChronoUnit.MINUTES)).isBefore(Instant.now())) {
|
||||||
respond(HttpStatusCode.Gone)
|
respond(HttpStatusCode.Gone)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.routes.v2
|
||||||
|
|
||||||
|
import de.steamwar.plugins.SWUserSession
|
||||||
|
import de.steamwar.routes.configureDiscordAuth
|
||||||
|
import de.steamwar.routes.configurePasswordAuth
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.delete
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
import io.ktor.server.sessions.clear
|
||||||
|
import io.ktor.server.sessions.sessions
|
||||||
|
|
||||||
|
fun Route.configureAuthV2() {
|
||||||
|
route("/auth") {
|
||||||
|
delete {
|
||||||
|
call.sessions.clear<SWUserSession>()
|
||||||
|
call.respond(HttpStatusCode.NoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
configureDiscordAuth()
|
||||||
|
route("/legacy") {
|
||||||
|
configurePasswordAuth()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.routes.v2
|
||||||
|
|
||||||
|
import de.steamwar.ResponseError
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
import org.bspfsystems.yamlconfiguration.file.YamlConfiguration
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.io.nameWithoutExtension
|
||||||
|
|
||||||
|
fun Route.configureGameModeRoutes() {
|
||||||
|
route("/gamemodes") {
|
||||||
|
get {
|
||||||
|
call.respond(
|
||||||
|
File("/configs/GameModes/").listFiles()!!
|
||||||
|
.filter { it.name.endsWith(".yml") && !it.name.endsWith(".kits.yml") }
|
||||||
|
.map { it.nameWithoutExtension })
|
||||||
|
}
|
||||||
|
get("/{gamemode}/maps") {
|
||||||
|
val gamemode = call.parameters["gamemode"]
|
||||||
|
if (gamemode == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid gamemode"))
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
val file = File("/configs/GameModes/$gamemode.yml")
|
||||||
|
if (!file.exists()) {
|
||||||
|
call.respond(HttpStatusCode.NotFound, ResponseError("Gamemode not found"))
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
call.respond(YamlConfiguration.loadConfiguration(file).getStringList("Server.Maps"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.routes.v2
|
||||||
|
|
||||||
|
import com.sun.tools.jdeprscan.Main.call
|
||||||
|
import de.steamwar.ResponseError
|
||||||
|
import de.steamwar.plugins.SWAuthPrincipal
|
||||||
|
import de.steamwar.plugins.SWPermissionCheck
|
||||||
|
import de.steamwar.routes.ResponseSchematic
|
||||||
|
import de.steamwar.routes.ResponseSchematicType
|
||||||
|
import de.steamwar.routes.UploadSchematic
|
||||||
|
import de.steamwar.routes.nbt
|
||||||
|
import de.steamwar.routes.receiveSchematic
|
||||||
|
import de.steamwar.sql.NodeData
|
||||||
|
import de.steamwar.sql.NodeData.SchematicFormat
|
||||||
|
import de.steamwar.sql.NodeDownload
|
||||||
|
import de.steamwar.sql.SWException
|
||||||
|
import de.steamwar.sql.SchematicNode
|
||||||
|
import de.steamwar.sql.SchematicType
|
||||||
|
import dev.dewy.nbt.tags.collection.CompoundTag
|
||||||
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.ApplicationCall
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.auth.authentication
|
||||||
|
import io.ktor.server.auth.principal
|
||||||
|
import io.ktor.server.request.receive
|
||||||
|
import io.ktor.server.response.header
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.response.respondBytes
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.routing.post
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
import java.io.BufferedInputStream
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.util.Base64
|
||||||
|
import java.util.zip.GZIPInputStream
|
||||||
|
|
||||||
|
data class ListedSchematicNode(val name: String, val id: Int, val type: String)
|
||||||
|
|
||||||
|
fun Route.configureSchematicsV2Route() {
|
||||||
|
route("/schematics") {
|
||||||
|
get("/types") {
|
||||||
|
call.respond(SchematicType.values().filter { !it.check() }
|
||||||
|
.map { ResponseSchematicType(it.name(), it.toDB()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/list/{path...}") {
|
||||||
|
val path = call.parameters.getAll("path")?.joinToString("/") ?: "/"
|
||||||
|
|
||||||
|
val user = call.authentication.principal<SWAuthPrincipal>()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.Unauthorized)
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
val node = SchematicNode.getNodeFromPath(user.user, path)
|
||||||
|
call.respond(SchematicNode.list(user.user, node?.id?.value).map { ListedSchematicNode(it.name, it.id.value, it.schemtype.toDB()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/download/{code}") {
|
||||||
|
val node = call.receiveSchematic() ?: return@get
|
||||||
|
|
||||||
|
val user = call.principal<SWAuthPrincipal>()?.user
|
||||||
|
if(user != null && !node.accessibleByUser(user)) {
|
||||||
|
call.respond(HttpStatusCode.Forbidden)
|
||||||
|
SWException.log("User ${user.userName} tried to download schematic ${node.name} without permission", user.id.toString())
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
val data = NodeData.getLatest(node) ?: run {
|
||||||
|
call.respond(HttpStatusCode.InternalServerError)
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
call.response.header("Content-Disposition", "attachment; filename=\"${node.name}${data.nodeFormat.fileEnding}\"")
|
||||||
|
call.respondBytes(data.schemData(false).readAllBytes(), contentType = ContentType.Application.OctetStream, status = HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/upload") {
|
||||||
|
val user = call.principal<SWAuthPrincipal>()?.user
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.Unauthorized)
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = call.receive<UploadSchematic>()
|
||||||
|
val schemName = file.name.substringBeforeLast(".")
|
||||||
|
|
||||||
|
if (SchematicNode.invalidSchemName(arrayOf(schemName))) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError(
|
||||||
|
error = "INVALID_NAME"
|
||||||
|
))
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
|
val schemType = file.name.substringAfterLast(".")
|
||||||
|
|
||||||
|
if (schemType != "schem" && schemType != "schematic") {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError(
|
||||||
|
error = "INVALID_SUFFIX"
|
||||||
|
))
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
|
var node = SchematicNode.getSchematicNode(user.getId(), schemName, null as Int?)
|
||||||
|
if (node == null) {
|
||||||
|
node = SchematicNode.createSchematic(user.getId(), schemName, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val content = Base64.getDecoder().decode(file.content)
|
||||||
|
|
||||||
|
var schem = nbt.fromStream(DataInputStream(BufferedInputStream(GZIPInputStream(content.inputStream()))))
|
||||||
|
|
||||||
|
if (schem.size() == 1) schem = schem.first() as CompoundTag
|
||||||
|
|
||||||
|
val version = schem.let {
|
||||||
|
if (it.contains("Materials"))
|
||||||
|
return@let SchematicFormat.MCEDIT
|
||||||
|
else if (it.contains("Blocks"))
|
||||||
|
return@let SchematicFormat.SPONGE_V3
|
||||||
|
else
|
||||||
|
return@let SchematicFormat.SPONGE_V2
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version == SchematicFormat.SPONGE_V3) {
|
||||||
|
try {
|
||||||
|
val fawe = schem.getCompound("Metadata")
|
||||||
|
.getCompound("WorldEdit")
|
||||||
|
.getString("Version")
|
||||||
|
.value
|
||||||
|
|
||||||
|
if (fawe.equals("2.12.3-SNAPSHOT")) {
|
||||||
|
SWException.log("Schematic with Bugged Version Uploaded", """
|
||||||
|
Schematic=$schemName
|
||||||
|
User=${user.userName}
|
||||||
|
Id=${user.id}
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeData.saveFromStream(node, content.inputStream(), version)
|
||||||
|
|
||||||
|
call.respond(ResponseSchematic(node))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError(
|
||||||
|
error = e.message ?: "GENERIC", code = "UPLOAD_ERROR"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
route("/{id}") {
|
||||||
|
install(SWPermissionCheck) {
|
||||||
|
mustAuth = true
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
val node = call.receiveSchem() ?: return@get
|
||||||
|
call.respond(ResponseSchematic(node))
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/download") {
|
||||||
|
val node = call.receiveSchem() ?: return@post
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.OK, NodeDownload.getLink(node))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun ApplicationCall.receiveSchem(): SchematicNode? {
|
||||||
|
val schemId = parameters["id"]?.toIntOrNull()
|
||||||
|
if (schemId == null) {
|
||||||
|
respond(HttpStatusCode.BadRequest)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val schem = SchematicNode.getSchematicNode(schemId)
|
||||||
|
if (schem == null) {
|
||||||
|
respond(HttpStatusCode.NotFound)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(principal<SWAuthPrincipal>()?.user?.let { schem.accessibleByUser(it) } ?: false)) {
|
||||||
|
respond(HttpStatusCode.Forbidden)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return schem
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.routes.v2
|
||||||
|
|
||||||
|
import de.steamwar.ResponseError
|
||||||
|
import de.steamwar.routes.ResponseUser
|
||||||
|
import de.steamwar.sql.SteamwarUser
|
||||||
|
import de.steamwar.sql.UserPerm
|
||||||
|
import de.steamwar.util.fetchData
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
|
||||||
|
fun Route.configureSteamWarRoute() {
|
||||||
|
route("/steamwar") {
|
||||||
|
get {
|
||||||
|
try {
|
||||||
|
val server = fetchData(InetSocketAddress("steamwar.de", 25565), 100)
|
||||||
|
call.respond(server)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
call.respond(HttpStatusCode.InternalServerError, ResponseError(e.message ?: "Unknown error"))
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/team") {
|
||||||
|
call.respond(
|
||||||
|
listOf(
|
||||||
|
UserPerm.PREFIX_ADMIN,
|
||||||
|
UserPerm.PREFIX_DEVELOPER,
|
||||||
|
UserPerm.PREFIX_MODERATOR,
|
||||||
|
UserPerm.PREFIX_SUPPORTER,
|
||||||
|
UserPerm.PREFIX_BUILDER
|
||||||
|
).associateWith { SteamwarUser.getUsersWithPerm(it) }.mapKeys { UserPerm.prefixes[it.key]!!.chatPrefix }
|
||||||
|
.mapValues { it.value.map { ResponseUser(it) } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.routes.v2
|
||||||
|
|
||||||
|
import de.steamwar.routes.ResponseEvent
|
||||||
|
import de.steamwar.routes.ResponseTeam
|
||||||
|
import de.steamwar.routes.ResponseUser
|
||||||
|
import de.steamwar.sql.SteamwarUser
|
||||||
|
import de.steamwar.sql.Team
|
||||||
|
import de.steamwar.sql.TeamTable
|
||||||
|
import de.steamwar.sql.TeamTeilnahme
|
||||||
|
import de.steamwar.sql.internal.useDb
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.jetbrains.exposed.v1.core.like
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.Query
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.andWhere
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||||
|
|
||||||
|
fun Query.addTeamFilter(teamName: String?, teamKuerzel: String?): Query {
|
||||||
|
teamName?.let { andWhere { TeamTable.name like "%$it%" } }
|
||||||
|
teamKuerzel?.let { andWhere { TeamTable.kuerzel like "%$it%" } }
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Route.configureTeamRoutes() {
|
||||||
|
route("/teams") {
|
||||||
|
get {
|
||||||
|
val teamName = call.request.queryParameters["name"]
|
||||||
|
val teamKuerzel = call.request.queryParameters["kuerzel"]
|
||||||
|
|
||||||
|
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 100
|
||||||
|
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 0
|
||||||
|
|
||||||
|
call.respond(useDb {
|
||||||
|
TeamTable.selectAll().addTeamFilter(teamName, teamKuerzel).limit(limit).offset((page * limit).toLong())
|
||||||
|
.map { ResponseTeam(it) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/{team}") {
|
||||||
|
@Serializable
|
||||||
|
data class TeamResponse(
|
||||||
|
val team: ResponseTeam,
|
||||||
|
val members: List<ResponseUser>,
|
||||||
|
val leaders: List<ResponseUser>,
|
||||||
|
val events: List<ResponseEvent>
|
||||||
|
)
|
||||||
|
|
||||||
|
val team = call.parameters["team"]?.toIntOrNull()?.let { Team.byId(it) }
|
||||||
|
if (team == null) {
|
||||||
|
call.respond(HttpStatusCode.NotFound)
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
call.respond(useDb {
|
||||||
|
TeamResponse(
|
||||||
|
ResponseTeam(team),
|
||||||
|
team.membersUser.map { ResponseUser(it) },
|
||||||
|
team.membersUser.filter { it.leader }.map { ResponseUser(it) },
|
||||||
|
TeamTeilnahme.getEvents(team.teamId).map { ResponseEvent(it) })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.routes.v2
|
||||||
|
|
||||||
|
import de.steamwar.ResponseError
|
||||||
|
import de.steamwar.data.getCachedSkin
|
||||||
|
import de.steamwar.plugins.SWAuthPrincipal
|
||||||
|
import de.steamwar.plugins.SWPermissionCheck
|
||||||
|
import de.steamwar.routes.ResponseTeam
|
||||||
|
import de.steamwar.routes.ResponseUser
|
||||||
|
import de.steamwar.routes.ResponseUserList
|
||||||
|
import de.steamwar.routes.UserStats
|
||||||
|
import de.steamwar.routes.addUserFilter
|
||||||
|
import de.steamwar.routes.catchException
|
||||||
|
import de.steamwar.sql.Punishment
|
||||||
|
import de.steamwar.sql.SteamwarUser
|
||||||
|
import de.steamwar.sql.SteamwarUserTable
|
||||||
|
import de.steamwar.sql.Team
|
||||||
|
import de.steamwar.sql.UserPerm
|
||||||
|
import de.steamwar.sql.internal.useDb
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.ApplicationCall
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.auth.principal
|
||||||
|
import io.ktor.server.request.receive
|
||||||
|
import io.ktor.server.response.header
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.response.respondFile
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.delete
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.routing.post
|
||||||
|
import io.ktor.server.routing.put
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||||
|
import java.sql.Timestamp
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ResponsePunishment(
|
||||||
|
val id: Int,
|
||||||
|
val type: Punishment.PunishmentType,
|
||||||
|
val reason: String,
|
||||||
|
val issuer: ResponseUser,
|
||||||
|
val startTime: Long,
|
||||||
|
val endTime: Long,
|
||||||
|
val perma: Boolean,
|
||||||
|
val active: Boolean
|
||||||
|
) {
|
||||||
|
constructor(punishment: Punishment) : this(
|
||||||
|
punishment.id.value,
|
||||||
|
punishment.type,
|
||||||
|
punishment.reason,
|
||||||
|
ResponseUser(SteamwarUser.byId(punishment.punisher)!!),
|
||||||
|
punishment.startTime.toInstant().toEpochMilli(),
|
||||||
|
punishment.endTime.toInstant().toEpochMilli(),
|
||||||
|
punishment.perma,
|
||||||
|
punishment.isCurrent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CreatePunishment(
|
||||||
|
val type: Punishment.PunishmentType,
|
||||||
|
val reason: String,
|
||||||
|
val perma: Boolean,
|
||||||
|
val endTime: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Route.configureUsersRouteV2() {
|
||||||
|
get("/users/{user}/skin") {
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
val skin = getCachedSkin(user.uuid.toString())
|
||||||
|
call.response.header("X-Cache", if (skin.second) "HIT" else "MISS")
|
||||||
|
call.response.header("Cache-Control", "public, max-age=604800")
|
||||||
|
call.respondFile(skin.first)
|
||||||
|
}
|
||||||
|
get("/users/{user}/stats") {
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
val authUser = call.principal<SWAuthPrincipal>()
|
||||||
|
|
||||||
|
if (authUser == null || !(authUser.user.id == user.id || authUser.user.hasPerm(UserPerm.MODERATION))) {
|
||||||
|
call.respond(HttpStatusCode.Forbidden)
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
call.respond(UserStats(user))
|
||||||
|
}
|
||||||
|
route("/users") {
|
||||||
|
install(SWPermissionCheck) {
|
||||||
|
permission = UserPerm.MODERATION
|
||||||
|
}
|
||||||
|
get {
|
||||||
|
val name = call.request.queryParameters["name"]
|
||||||
|
val uuid = call.request.queryParameters["uuid"]?.let { catchException { UUID.fromString(it) } }
|
||||||
|
val team = call.request.queryParameters.getAll("team")?.map { it.toInt() }?.toSet()
|
||||||
|
|
||||||
|
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 100
|
||||||
|
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 0
|
||||||
|
|
||||||
|
val includePerms = call.request.queryParameters["includePerms"]?.toBoolean() ?: false
|
||||||
|
val includeId = call.request.queryParameters["includeId"]?.toBoolean() ?: false
|
||||||
|
|
||||||
|
call.respond(
|
||||||
|
useDb {
|
||||||
|
ResponseUserList(
|
||||||
|
SteamwarUserTable.selectAll().addUserFilter(name, uuid, team).limit(limit)
|
||||||
|
.offset((page * limit).toLong())
|
||||||
|
.map { ResponseUser(it, includeId, includePerms) },
|
||||||
|
SteamwarUserTable.selectAll().addUserFilter(name, uuid, team).count()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
route("/{user}") {
|
||||||
|
get {
|
||||||
|
@Serializable
|
||||||
|
data class UserResponse(val user: ResponseUser, val team: ResponseTeam)
|
||||||
|
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.NotFound)
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
call.respond(
|
||||||
|
useDb {
|
||||||
|
UserResponse(ResponseUser(user), ResponseTeam(Team.byId(user.team)))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
put("/prefix/{prefix}") {
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
|
||||||
|
return@put
|
||||||
|
}
|
||||||
|
|
||||||
|
val prefix = call.parameters["prefix"]?.let { name -> UserPerm.entries.find { it.name == name } }
|
||||||
|
if (prefix == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid prefix"))
|
||||||
|
return@put
|
||||||
|
}
|
||||||
|
|
||||||
|
user.perms().filter { it.name.startsWith("PREFIX_") }.forEach {
|
||||||
|
UserPerm.removePerm(user, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefix != UserPerm.PREFIX_NONE) {
|
||||||
|
UserPerm.addPerm(user, prefix)
|
||||||
|
}
|
||||||
|
call.respond(HttpStatusCode.Accepted)
|
||||||
|
}
|
||||||
|
|
||||||
|
route("/perms") {
|
||||||
|
get {
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
call.respond(user.perms())
|
||||||
|
}
|
||||||
|
route("/{perm}") {
|
||||||
|
put {
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
|
||||||
|
return@put
|
||||||
|
}
|
||||||
|
|
||||||
|
val perm = call.parameters["perm"]?.let { name -> UserPerm.entries.find { it.name == name } }
|
||||||
|
if (perm == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid perm"))
|
||||||
|
return@put
|
||||||
|
}
|
||||||
|
|
||||||
|
UserPerm.addPerm(user, perm)
|
||||||
|
call.respond(HttpStatusCode.Accepted)
|
||||||
|
}
|
||||||
|
delete {
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
|
||||||
|
return@delete
|
||||||
|
}
|
||||||
|
|
||||||
|
val perm = call.parameters["perm"]?.let { name -> UserPerm.entries.find { it.name == name } }
|
||||||
|
if (perm == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid perm"))
|
||||||
|
return@delete
|
||||||
|
}
|
||||||
|
|
||||||
|
UserPerm.removePerm(user, perm)
|
||||||
|
call.respond(HttpStatusCode.Accepted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
route("/punishments") {
|
||||||
|
get {
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
val punishments = user.punishments.toList()
|
||||||
|
|
||||||
|
call.respond(punishments.map { ResponsePunishment(it.second) })
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
|
val punishment = call.receive<CreatePunishment>()
|
||||||
|
val punisher = call.principal<SWAuthPrincipal>()?.user ?: return@post
|
||||||
|
|
||||||
|
user.punish(punishment.type, Timestamp.from(Instant.ofEpochMilli(punishment.endTime)), punishment.reason, punisher.getId(), punishment.perma)
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.Accepted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ApplicationCall.receiveUser(): SteamwarUser? {
|
||||||
|
val userString = parameters["user"] ?: return null
|
||||||
|
|
||||||
|
if (userString == "me") {
|
||||||
|
return principal<SWAuthPrincipal>()?.user
|
||||||
|
}
|
||||||
|
|
||||||
|
userString.toIntOrNull()?.let { return SteamwarUser.byId(it) }
|
||||||
|
userString.let { catchException { UUID.fromString(it) } }?.let { return SteamwarUser.get(it) }
|
||||||
|
return null
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user