Compare commits

...

29 Commits

Author SHA1 Message Date
Chaoscaot a8204a181b Add API V2
SteamWarCI Build failed
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-04-17 18:25:42 +02:00
Chaoscaot 2208dcc0fb Fix Set Parent
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-04-15 18:20:07 +02:00
YoyoNow b466216b3a Fix BlockFormListener
SteamWarCI Build successful
2026-04-13 20:33:11 +02:00
YoyoNow 5a862b251b Add BlockFormListener
SteamWarCI Build successful
2026-04-13 20:29:47 +02:00
YoyoNow 60a82a685d Improve FightSystem REDUCED_DEBUG_INFO on Test Arena
SteamWarCI Build successful
2026-04-13 20:18:43 +02:00
YoyoNow 573b0c14ae Improve FightSystem REDUCED_DEBUG_INFO on Test Arena
SteamWarCI Build successful
2026-04-13 20:03:41 +02:00
YoyoNow 82abe7e20f Fix OutsideWincondition
SteamWarCI Build successful
2026-04-05 12:34:11 +02:00
YoyoNow 34da59714e Fix IngameListener and StartCommand
SteamWarCI Build successful
2026-04-05 12:30:55 +02:00
YoyoNow 97071165cd Fix IngameListener, OutsideWincondition, TowerGenerator
SteamWarCI Build successful
2026-04-05 12:08:29 +02:00
YoyoNow 634465fbf1 Fix BanListener inserting bedrock ips
SteamWarCI Build successful
2026-04-04 12:08:17 +02:00
YoyoNow 2ad8cc3f4a Merge branch 'RemoveUnusedSQL'
SteamWarCI Build successful
2026-04-02 09:01:20 +02:00
YoyoNow e190fe0858 Fix FreezeListener
SteamWarCI Build successful
2026-04-01 19:43:31 +02:00
YoyoNow 569d91a0d3 Remove SelectAdjacent as it annoys most players
SteamWarCI Build successful
2026-03-29 14:15:01 +02:00
YoyoNow 487a15849a Add supress warnings
SteamWarCI Build successful
2026-03-29 13:12:09 +02:00
YoyoNow e110033315 Fix Replays for 1.21
SteamWarCI Build successful
2026-03-29 13:05:41 +02:00
YoyoNow c0b192e2bf Fix WorldEditWrapper not loading schematics in 1.21
SteamWarCI Build successful
2026-03-29 12:53:55 +02:00
YoyoNow 612254296c Fix WorldEditWrapper21
SteamWarCI Build successful
2026-03-29 11:55:16 +02:00
Chaoscaot 1dbcb122c2 Change Loader to use FastSchematicReaderV3
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-03-28 22:30:01 +01:00
Chaoscaot f2ee9dbeb3 Fix Schematic Tabcomplete
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-03-28 22:24:07 +01:00
YoyoNow 404ab2abfb Update GDPRQuery
SteamWarCI Build successful
2026-03-25 07:46:38 +01:00
YoyoNow 59a927c33c Remove Team.address and Team.port
SteamWarCI Build successful
Remove PollAnswer and UserElo from GDPRQuery
2026-03-24 22:11:19 +01:00
Chaoscaot 6c062216a1 Remove usage of EffectiveSchematicNode
SteamWarCI Build successful
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-03-24 20:07:17 +01:00
YoyoNow 72d62dfbe5 Fix AutoCheckerResult ignoring Water and Lava correctly
SteamWarCI Build successful
2026-03-22 15:23:46 +01:00
YoyoNow 76ecaccc41 Hotfix AutoChecker15 for now
SteamWarCI Build successful
2026-03-21 17:45:26 +01:00
YoyoNow 9587b9e1fd Fix Laufbau double click
SteamWarCI Build successful
2026-03-21 09:46:20 +01:00
YoyoNow 14dc807fd9 Fix SoulSand in LaufbauCommand
SteamWarCI Build successful
2026-03-21 09:39:17 +01:00
YoyoNow 63ad85f727 Fix TickManager21.stepTicks
SteamWarCI Build successful
2026-03-21 09:37:05 +01:00
YoyoNow 72e88502d2 Fix TeamCommand /team event ...
SteamWarCI Build successful
2026-03-21 09:26:02 +01:00
YoyoNow 71767ef6d9 Fix TeamCommand /team event ...
SteamWarCI Build successful
2026-03-21 09:20:57 +01:00
53 changed files with 1236 additions and 565 deletions
@@ -100,7 +100,6 @@ public class TickManager21 implements TickManager {
manager.setFrozen(true);
bukkitTask.cancel();
}, 1, 1);
manager.tick();
}
@Override
@@ -46,6 +46,16 @@ import org.bukkit.event.player.PlayerInteractEvent;
@Linked
public class FreezeListener implements Listener, ScoreboardElement {
@EventHandler
public void onBlockExplode(BlockExplodeEvent e) {
if (Region.getRegion(e.getBlock().getLocation()).getRegionData().get(Flag.FREEZE).isWithDefault(FreezeMode.INACTIVE)) return;
e.setCancelled(true);
BlockState state = e.getBlock().getState();
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
state.update(true, false);
}, 1L);
}
@EventHandler
public void onEntitySpawn(EntitySpawnEvent e) {
if (Region.getRegion(e.getLocation()).getRegionData().get(Flag.FREEZE).isWithDefault(FreezeMode.INACTIVE)) return;
@@ -150,11 +150,13 @@ public class SimulatorObserverGui extends SimulatorScrollGui<ObserverPhase> {
Consumer<Integer> setter = observerPhase::setTickOffset;
return new SWItem[] {
new SWItem(SWItem.getDye(getter.get() < max ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
setter.accept(Math.min(max, getter.get() + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED),
observer,
new SWItem(SWItem.getDye(getter.get() > min ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8:§e -5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
setter.accept(Math.max(min, getter.get() - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED),
@@ -32,6 +32,7 @@ import de.steamwar.inventory.SWItem;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import java.util.Arrays;
@@ -97,6 +98,7 @@ public class SimulatorObserverPhaseSettingsGui extends SimulatorBaseGui {
//Tick Offset
int offset = observer.getTickOffset();
inventory.setItem(10, new SWItem(SWItem.getDye(offset < max ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
observer.setTickOffset(Math.min(max, offset + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -113,6 +115,7 @@ public class SimulatorObserverPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(19, offsetItem);
inventory.setItem(28, new SWItem(SWItem.getDye(offset > min ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
observer.setTickOffset(Math.max(min, offset - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -120,6 +123,7 @@ public class SimulatorObserverPhaseSettingsGui extends SimulatorBaseGui {
//Order
int order = observer.getOrder();
inventory.setItem(13, new SWItem(SWItem.getDye(order < SimulatorPhase.ORDER_LIMIT ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
observer.setOrder(Math.min(SimulatorPhase.ORDER_LIMIT, order + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -138,6 +142,7 @@ public class SimulatorObserverPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(22, orderItem);
inventory.setItem(31, new SWItem(SWItem.getDye(order > -SimulatorPhase.ORDER_LIMIT ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
observer.setOrder(Math.max(-SimulatorPhase.ORDER_LIMIT, order - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -28,6 +28,7 @@ import de.steamwar.data.CMDs;
import de.steamwar.inventory.SWItem;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import java.util.Arrays;
@@ -67,6 +68,7 @@ public class SimulatorObserverSettingsGui extends SimulatorBaseGui {
// Base Tick
int baseTicks = observer.getBaseTick();
inventory.setItem(9, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
observer.changeBaseTicks(clickType.isShiftClick() ? 5 : 1);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -81,6 +83,7 @@ public class SimulatorObserverSettingsGui extends SimulatorBaseGui {
baseTick.getItemStack().setAmount(Math.max(1, Math.min(baseTicks, 64)));
inventory.setItem(18, baseTick);
inventory.setItem(27, new SWItem(SWItem.getDye(baseTicks > 0 ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
if (baseTicks - (clickType.isShiftClick() ? 5 : 1) < 0) {
observer.changeBaseTicks(-baseTicks);
} else {
@@ -91,6 +94,7 @@ public class SimulatorObserverSettingsGui extends SimulatorBaseGui {
//Pos X
inventory.setItem(15, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
observer.move(clickType.isShiftClick() ? 5 : 1, 0, 0);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -102,12 +106,14 @@ public class SimulatorObserverSettingsGui extends SimulatorBaseGui {
}, this).open();
}));
inventory.setItem(33, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
observer.move(clickType.isShiftClick() ? -5 : -1, 0, 0);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
//Pos Y
inventory.setItem(16, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
observer.move(0, clickType.isShiftClick() ? 5 : 1, 0);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED));
@@ -119,12 +125,14 @@ public class SimulatorObserverSettingsGui extends SimulatorBaseGui {
}, this).open();
}));
inventory.setItem(34, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
observer.move(0, clickType.isShiftClick() ? -5 : -1, 0);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
//Pos Z
inventory.setItem(17, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
observer.move(0, 0, clickType.isShiftClick() ? 5 : 1);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED));
@@ -136,6 +144,7 @@ public class SimulatorObserverSettingsGui extends SimulatorBaseGui {
}, this).open();
}));
inventory.setItem(35, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
observer.move(0, 0, clickType.isShiftClick() ? -5 : -1);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -165,11 +165,13 @@ public class SimulatorRedstoneGui extends SimulatorScrollGui<SimulatorRedstoneGu
Consumer<Integer> setter = redstoneSubPhase.place ? redstoneSubPhase.phase::setTickOffset : redstoneSubPhase.phase::setLifetime;
return new SWItem[] {
new SWItem(SWItem.getDye(getter.get() < max ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
setter.accept(Math.min(max, getter.get() + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED),
redstone,
new SWItem(SWItem.getDye(getter.get() > min ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8:§e -5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
setter.accept(Math.max(min, getter.get() - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED),
@@ -31,6 +31,7 @@ import de.steamwar.data.CMDs;
import de.steamwar.inventory.SWItem;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import java.util.Arrays;
@@ -98,6 +99,7 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
//Tick Offset
int offset = redstone.getTickOffset();
inventory.setItem(10, new SWItem(SWItem.getDye(offset < maxOffset ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
redstone.setTickOffset(Math.min(maxOffset, offset + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -114,6 +116,7 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(19, offsetItem);
inventory.setItem(28, new SWItem(SWItem.getDye(offset > min ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
redstone.setTickOffset(Math.max(min, offset - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -121,6 +124,7 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
//Lifetime
int lifetime = redstone.getLifetime();
inventory.setItem(11, new SWItem(SWItem.getDye(lifetime < maxLifetime ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
redstone.setLifetime(Math.min(maxLifetime, lifetime + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -137,6 +141,7 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(20, lifetimeItem);
inventory.setItem(29, new SWItem(SWItem.getDye(lifetime > 0 ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
redstone.setLifetime(Math.max(0, lifetime - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -144,6 +149,7 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
//Order
int order = redstone.getOrder();
inventory.setItem(13, new SWItem(SWItem.getDye(order < SimulatorPhase.ORDER_LIMIT ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
redstone.setOrder(Math.min(SimulatorPhase.ORDER_LIMIT, order + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -162,6 +168,7 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(22, orderItem);
inventory.setItem(31, new SWItem(SWItem.getDye(order > -SimulatorPhase.ORDER_LIMIT ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
redstone.setOrder(Math.max(-SimulatorPhase.ORDER_LIMIT, order - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -28,6 +28,7 @@ import de.steamwar.data.CMDs;
import de.steamwar.inventory.SWItem;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import java.util.Arrays;
@@ -66,6 +67,7 @@ public class SimulatorRedstoneSettingsGui extends SimulatorBaseGui {
// Base Tick
int baseTicks = redstone.getBaseTick();
inventory.setItem(9, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
redstone.changeBaseTicks(clickType.isShiftClick() ? 5 : 1);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -80,6 +82,7 @@ public class SimulatorRedstoneSettingsGui extends SimulatorBaseGui {
baseTick.getItemStack().setAmount(Math.max(1, Math.min(baseTicks, 64)));
inventory.setItem(18, baseTick);
inventory.setItem(27, new SWItem(SWItem.getDye(baseTicks > 0 ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
if (baseTicks - (clickType.isShiftClick() ? 5 : 1) < 0) {
redstone.changeBaseTicks(-baseTicks);
} else {
@@ -90,6 +93,7 @@ public class SimulatorRedstoneSettingsGui extends SimulatorBaseGui {
//Pos X
inventory.setItem(15, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
redstone.move(clickType.isShiftClick() ? 5 : 1, 0, 0);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -101,12 +105,14 @@ public class SimulatorRedstoneSettingsGui extends SimulatorBaseGui {
}, this).open();
}));
inventory.setItem(33, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
redstone.move(clickType.isShiftClick() ? -5 : -1, 0, 0);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
//Pos Y
inventory.setItem(16, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
redstone.move(0, clickType.isShiftClick() ? 5 : 1, 0);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -118,12 +124,14 @@ public class SimulatorRedstoneSettingsGui extends SimulatorBaseGui {
}, this).open();
}));
inventory.setItem(34, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
redstone.move(0, clickType.isShiftClick() ? -5 : -1, 0);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
//Pos Z
inventory.setItem(17, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
redstone.move(0, 0, clickType.isShiftClick() ? 5 : 1);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -135,6 +143,7 @@ public class SimulatorRedstoneSettingsGui extends SimulatorBaseGui {
}, this).open();
}));
inventory.setItem(35, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
redstone.move(0, 0, clickType.isShiftClick() ? -5 : -1);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -138,11 +138,13 @@ public class SimulatorTNTGui extends SimulatorScrollGui<TNTPhase> {
return new SWItem[]{
new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tntSetting.setCount(tntSetting.getCount() + (clickType.isShiftClick() ? 5 : 1));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED),
tnt,
new SWItem(SWItem.getDye(tntSetting.getCount() > 1 ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8:§e -5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tntSetting.setCount(Math.max(1, tntSetting.getCount() - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED),
@@ -31,6 +31,7 @@ import de.steamwar.data.CMDs;
import de.steamwar.inventory.SWItem;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import java.util.Arrays;
@@ -78,6 +79,7 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
//Count
int count = tnt.getCount();
inventory.setItem(9, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.setCount(count + (clickType.isShiftClick() ? 5 : 1));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -94,6 +96,7 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(18, countItem);
inventory.setItem(27, new SWItem(SWItem.getDye(count > 1 ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.setCount(Math.max(1, count - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -101,6 +104,7 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
//Tick Offset
int offset = tnt.getTickOffset();
inventory.setItem(10, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.setTickOffset(offset + (clickType.isShiftClick() ? 5 : 1));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -117,6 +121,7 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(19, offsetItem);
inventory.setItem(28, new SWItem(SWItem.getDye(offset > 0 ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.setTickOffset(Math.max(0, offset - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -124,6 +129,7 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
//Lifetime
int lifetime = tnt.getLifetime();
inventory.setItem(11, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.setLifetime(lifetime + (clickType.isShiftClick() ? 5 : 1));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -140,6 +146,7 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(20, lifetimeItem);
inventory.setItem(29, new SWItem(SWItem.getDye(lifetime > 0 ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.setLifetime(Math.max(1, lifetime - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -147,6 +154,7 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
//Order
int order = tnt.getOrder();
inventory.setItem(13, new SWItem(SWItem.getDye(order < SimulatorPhase.ORDER_LIMIT ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.setOrder(Math.min(SimulatorPhase.ORDER_LIMIT, order + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -165,30 +173,35 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(22, orderItem);
inventory.setItem(31, new SWItem(SWItem.getDye(order > -SimulatorPhase.ORDER_LIMIT ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.setOrder(Math.max(-SimulatorPhase.ORDER_LIMIT, order - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
//Jump
SWItem jumpX = new SWItem(tnt.isXJump() ? Material.LIME_WOOL : Material.RED_WOOL, "§7TNT §eJump X§8: " + (tnt.isZJump() ? "§aon" : "§coff"), clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.setXJump(!tnt.isXJump());
SimulatorWatcher.update(simulator);
});
inventory.setItem(33, jumpX);
SWItem jumpY = new SWItem(tnt.isYJump() ? Material.LIME_WOOL : Material.RED_WOOL, "§7TNT §eJump Y§8: " + (tnt.isYJump() ? "§aon" : "§coff"), clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.setYJump(!tnt.isYJump());
SimulatorWatcher.update(simulator);
});
inventory.setItem(16, jumpY);
SWItem jumpZ = new SWItem(tnt.isZJump() ? Material.LIME_WOOL : Material.RED_WOOL, "§7TNT §eJump Z§8: " + (tnt.isZJump() ? "§aon" : "§coff"), clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.setZJump(!tnt.isZJump());
SimulatorWatcher.update(simulator);
});
inventory.setItem(35, jumpZ);
SWItem jumpAll = new SWItem(Material.TNT, "§7TNT §eJump §8: " + (tnt.hasJump() ? "§aon" : "§coff"), clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.setJump(!tnt.hasJump());
SimulatorWatcher.update(simulator);
});
@@ -28,6 +28,7 @@ import de.steamwar.data.CMDs;
import de.steamwar.inventory.SWItem;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import java.util.ArrayList;
import java.util.Arrays;
@@ -75,6 +76,7 @@ public class SimulatorTNTSettingsGui extends SimulatorBaseGui {
// Base Tick
int baseTicks = tnt.getBaseTick();
inventory.setItem(9, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.changeBaseTicks(clickType.isShiftClick() ? 5 : 1);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -89,6 +91,7 @@ public class SimulatorTNTSettingsGui extends SimulatorBaseGui {
baseTick.getItemStack().setAmount(Math.max(1, Math.min(baseTicks, 64)));
inventory.setItem(18, baseTick);
inventory.setItem(27, new SWItem(SWItem.getDye(baseTicks > 0 ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
if (baseTicks - (clickType.isShiftClick() ? 5 : 1) < 0) {
tnt.changeBaseTicks(-baseTicks);
} else {
@@ -136,6 +139,7 @@ public class SimulatorTNTSettingsGui extends SimulatorBaseGui {
// Pos X
inventory.setItem(15, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+0.0625"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.move(clickType.isShiftClick() ? 0.0625 : 1, 0, 0);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -147,12 +151,14 @@ public class SimulatorTNTSettingsGui extends SimulatorBaseGui {
}, this).open();
}));
inventory.setItem(33, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-0.0625"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.move(clickType.isShiftClick() ? -0.0625 : -1, 0, 0);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Pos Y
inventory.setItem(16, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+0.0625"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.move(0, clickType.isShiftClick() ? 0.0625 : 1, 0);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -164,12 +170,14 @@ public class SimulatorTNTSettingsGui extends SimulatorBaseGui {
}, this).open();
}));
inventory.setItem(34, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-0.0625"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.move(0, clickType.isShiftClick() ? -0.0625 : -1, 0);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Pos Z
inventory.setItem(17, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+0.0625"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.move(0, 0, clickType.isShiftClick() ? 0.0625 : 1);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -181,6 +189,7 @@ public class SimulatorTNTSettingsGui extends SimulatorBaseGui {
}, this).open();
}));
inventory.setItem(35, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-0.0625"), false, clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.move(0, 0, clickType.isShiftClick() ? -0.0625 : -1);
SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -101,7 +101,7 @@ public class BlockBoundingBox {
addPixel(Material.END_STONE.createBlockData(), 0, 0, 0, 16, 16, 16, null);
addPixel(NMSWrapper.impl.pathMaterial().createBlockData(), 0, 0, 0, 16, 15, 16, createItem("LAUFBAU_BLOCK_GRASS_PATH", NMSWrapper.impl.pathMaterial()));
addPixel(Material.SOUL_SAND.createBlockData(), 0, 0, 0, 16, 14, 16, createItem("LAUFBAU_BLOCK_SOUL_SAND", Material.SOUL_SAND));
addPixel(Material.MUD.createBlockData(), 0, 0, 0, 16, 14, 16, createItem("LAUFBAU_BLOCK_SOUL_SAND", Material.SOUL_SAND));
Cocoa cocoaNorth = (Cocoa) Material.COCOA.createBlockData();
cocoaNorth.setAge(2);
@@ -25,12 +25,14 @@ import de.steamwar.bausystem.shared.Pair;
import de.steamwar.bausystem.utils.WorldEditUtils;
import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
@Linked
@MinVersion(19)
public class LaufbauCommand extends SWCommand {
public LaufbauCommand() {
@@ -1,189 +0,0 @@
/*
* 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.bausystem.features.worldedit;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.utils.FlatteningWrapper;
import de.steamwar.core.SWPlayer;
import de.steamwar.core.WorldEditRenderer;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.Vector;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
@Linked
@MinVersion(20)
public class SelectAdjacent implements Listener {
private Vector[] FACES = {
new Vector(1, 0, 0),
new Vector(-1, 0, 0),
new Vector(0, 1, 0),
new Vector(0, -1, 0),
new Vector(0, 0, 1),
new Vector(0, 0, -1),
new Vector(1, 1, 0),
new Vector(1, -1, 0),
new Vector(1, 0, 1),
new Vector(1, 0, -1),
new Vector(-1, 1, 0),
new Vector(-1, -1, 0),
new Vector(-1, 0, 1),
new Vector(-1, 0, -1),
new Vector(0, 1, 1),
new Vector(0, 1, -1),
new Vector(0, -1, 1),
new Vector(0, -1, -1),
};
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
if (!event.hasItem()) return;
if (event.getItem().getType() != Material.WOODEN_AXE) return;
if (!event.getPlayer().isSneaking()) return;
if (event.getAction() != Action.LEFT_CLICK_BLOCK) return;
Material material = event.getPlayer().getInventory().getItemInOffHand().getType();
Selector selector;
if (material.isAir()) {
selector = new Selector(event.getClickedBlock(), event.getPlayer(), __ -> true);
} else {
selector = new Selector(event.getClickedBlock(), event.getPlayer(), type -> type == material);
}
SWPlayer.of(event.getPlayer()).setComponent(selector);
}
private class Selector implements SWPlayer.Component {
private static final int MAX_BLOCKS = 500_000;
private int minX;
private int minY;
private int minZ;
private int maxX;
private int maxY;
private int maxZ;
private BukkitTask bukkitTask;
private Predicate<Material> predicate;
private Set<Location> seen = new HashSet<>();
private Set<Location> toCalc = new HashSet<>();
private Region.Area area;
public Selector(Block block, Player player, Predicate<Material> predicate) {
this.predicate = predicate;
toCalc.add(block.getLocation());
minX = block.getX();
minY = block.getY();
minZ = block.getZ();
maxX = block.getX();
maxY = block.getY();
maxZ = block.getZ();
Region region = Region.getRegion(block.getLocation());
area = Region.Area.EMPTY;
if (region.getBuildArea().inRegion(block.getLocation(), true)) {
area = region.getBuildArea();
} else if (region.getTestblockArea().inRegion(block.getLocation(), true)) {
area = region.getTestblockArea();
} else if (region.getArea().inRegion(block.getLocation(), true)) {
area = region.getArea();
}
bukkitTask = Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> {
run();
long volume = (long)(maxX - minX + 1) * (long)(maxY - minY + 1) * (long)(maxZ - minZ + 1);
player.sendTitle("", "§e" + volume + " §7Blocks", 0, 5, 0);
Point minPoint = new Point(minX, minY, minZ);
Point maxPoint = new Point(maxX, maxY, maxZ);
FlatteningWrapper.impl.setSelection(player, minPoint, maxPoint);
WorldEditRenderer.renderPlayer(player);
// boolean finished = toCalc.stream().allMatch(location -> {
// return location.getBlockX() >= minX && location.getBlockY() >= minY && location.getBlockZ() >= minZ &&
// location.getBlockX() <= maxX && location.getBlockY() <= maxY && location.getBlockZ() <= maxZ;
// });
if (toCalc.isEmpty() || seen.size() > MAX_BLOCKS) {
bukkitTask.cancel();
player.sendTitle("§aDone", "§e" + volume + " §7Blocks", 0, 20, 5);
SWPlayer.of(player).removeComponent(Selector.class);
}
}, 1, 1);
}
private void cancel() {
bukkitTask.cancel();
}
private void run() {
Set<Location> current = toCalc;
toCalc = new HashSet<>();
for (Location location : current) {
Block block = location.getBlock();
if (block.isEmpty() || block.isLiquid()) continue;
if (!predicate.test(block.getType())) continue;
seen.add(location);
if (!area.inRegion(block.getLocation(), true)) continue;
minX = Math.min(minX, location.getBlockX());
maxX = Math.max(maxX, location.getBlockX());
minY = Math.min(minY, location.getBlockY());
maxY = Math.max(maxY, location.getBlockY());
minZ = Math.min(minZ, location.getBlockZ());
maxZ = Math.max(maxZ, location.getBlockZ());
for (Vector face : FACES) {
Block next = block.getRelative(face.getBlockX(), face.getBlockY(), face.getBlockZ());
if (next.isEmpty() || next.isLiquid()) continue;
if (!predicate.test(next.getType())) continue;
Location loc = next.getLocation();
if (seen.contains(loc)) continue;
toCalc.add(loc);
}
}
}
@Override
public void onUnmount(SWPlayer player) {
cancel();
}
}
}
@@ -440,6 +440,13 @@ public final class GameModeConfig<M, W> {
*/
public final boolean DisableSnowMelt;
/**
* Disable ice forming
*
* @implSpec {@code false} by default
*/
public final boolean DisableIceForm;
/**
* Allow leaving the arena area as spectator
*
@@ -470,6 +477,7 @@ public final class GameModeConfig<M, W> {
BorderFromSchematic = loader.getInt("BorderFromSchematic", 21);
GroundWalkable = loader.getBoolean("GroundWalkable", true);
DisableSnowMelt = loader.getBoolean("DisableSnowMelt", false);
DisableIceForm = loader.getBoolean("DisableIceForm", false);
Leaveable = loader.getBoolean("Leaveable", false);
AllowMissiles = loader.getBoolean("AllowMissiles", !EnterStages.isEmpty());
NoFloor = loader.getBoolean("NoFloor", false);
@@ -88,8 +88,6 @@ class NodeData(id: EntityID<CompositeID>): CompositeEntity(id) {
schemData.inputStream.let { if(decompress) GZIPInputStream(it) else it }
}
fun schemData() = schemData(true)
override fun delete() = useDb { super.delete() }
enum class SchematicFormat(val fileEnding: String) {
@@ -94,7 +94,7 @@ class NodeMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
{ Optional.ofNullable(it?.value) })
private set
fun setParentId(id: Int?) {
fun setParentId(id: Int?) = useDb {
parent = Optional.ofNullable(id)
}
@@ -146,7 +146,91 @@ class SchematicNode(id: EntityID<Int>) : IntEntity(id) {
@JvmStatic
fun parentsOfNode(user: SteamwarUser, id: Int) = fromSql(
"WITH RECURSIVE R AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeId = ? UNION SELECT E.NodeId, E.ParentNode FROM R, EffectiveSchematicNode E WHERE R.ParentNode = E.NodeId AND E.EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, SN.NodeName, R.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank, SN.Config FROM R INNER JOIN SchematicNode SN ON SN.NodeId = R.NodeId",
"""
WITH RECURSIVE
ESN_R AS (
SELECT SchematicNode.NodeId AS NodeId,
NM.UserId AS EffectiveOwner,
NM.ParentId AS ParentNode
FROM SchematicNode
INNER JOIN NodeMember NM ON NM.NodeId = SchematicNode.NodeId
UNION ALL
SELECT S.NodeId AS NodeId,
ESN_R.EffectiveOwner AS EffectiveOwner,
S.ParentNode AS ParentNode
FROM SchematicNode S
INNER JOIN ESN_R ON S.ParentNode = ESN_R.NodeId
),
ESN_R2 AS (
SELECT SchematicNode.NodeId AS NodeId,
NM.UserId AS UserId,
SchematicNode.NodeOwner AS EffectiveOwner,
SchematicNode.ParentNode AS ParentNode
FROM SchematicNode
INNER JOIN NodeMember NM ON NM.NodeId = SchematicNode.NodeId
UNION ALL
SELECT S.NodeId AS NodeId,
ESN_R2.EffectiveOwner AS EffectiveOwner,
ESN_R2.UserId AS UserId,
S.ParentNode AS ParentNode
FROM SchematicNode S
INNER JOIN ESN_R2 ON S.ParentNode = ESN_R2.NodeId
WHERE S.NodeOwner <> ESN_R2.EffectiveOwner
),
ESN_R3 AS (
SELECT SchematicNode.NodeId AS NodeId,
SchematicNode.NodeOwner AS NodeOwner,
SchematicNode.NodeOwner AS EffectiveOwner,
SchematicNode.ParentNode AS ParentNode
FROM SchematicNode
UNION ALL
SELECT S.NodeId AS NodeId,
S.NodeOwner AS NodeOwner,
ESN_R3.EffectiveOwner AS EffectiveOwner,
S.ParentNode AS ParentNode
FROM SchematicNode S
INNER JOIN ESN_R3 ON S.ParentNode = ESN_R3.NodeId
WHERE ESN_R3.NodeOwner <> S.NodeOwner
),
ResolvedNodes AS (
SELECT ESN_R.NodeId AS NodeId,
ESN_R.EffectiveOwner AS EffectiveOwner,
ESN_R.ParentNode AS ParentNode
FROM ESN_R
UNION
SELECT ESN_R2.NodeId AS NodeId,
ESN_R2.UserId AS EffectiveOwner,
ESN_R2.ParentNode AS ParentNode
FROM ESN_R2
INNER JOIN SchematicNode SN2 ON ESN_R2.NodeId = SN2.NodeId
WHERE ESN_R2.ParentNode IS NOT NULL
AND SN2.NodeOwner <> ESN_R2.EffectiveOwner
UNION
SELECT ESN_R3.NodeId AS NodeId,
ESN_R3.EffectiveOwner AS EffectiveOwner,
ESN_R3.ParentNode AS ParentNode
FROM ESN_R3
WHERE ESN_R3.NodeOwner <> ESN_R3.EffectiveOwner
UNION
SELECT SchematicNode.NodeId AS NodeId,
SchematicNode.NodeOwner AS EffectiveOwner,
SchematicNode.ParentNode AS ParentNode
FROM SchematicNode
),
R AS (
SELECT NodeId, ParentNode
FROM ResolvedNodes
WHERE NodeId = ?
UNION
SELECT E.NodeId, E.ParentNode
FROM R
INNER JOIN ResolvedNodes E ON R.ParentNode = E.NodeId
WHERE E.EffectiveOwner = ?
)
SELECT SN.NodeId, SN.NodeOwner, SN.NodeName, R.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank, SN.Config
FROM R
INNER JOIN SchematicNode SN ON SN.NodeId = R.NodeId
""".trimIndent(),
listOf(
IntegerColumnType() to id,
IntegerColumnType() to user.getId()
@@ -284,7 +368,7 @@ class SchematicNode(id: EntityID<Int>) : IntEntity(id) {
val list = mutableListOf<String>()
if (s.contains("/")) {
val preTab = s.take(s.lastIndexOf("/") + 1)
val pa = getNodeFromPath(user, preTab) ?: return emptyList()
val pa = getNodeFromPath(user, preTab) ?: return mutableListOf()
val nodes: List<SchematicNode> = list(user, pa.getId())
val br = pa.generateBreadcrumbs(user)
nodes.forEach(Consumer { node: SchematicNode? -> list.add((if (sws) "/" else "") + br + node!!.name + (if (node.isDir()) "/" else "")) })
@@ -198,7 +198,7 @@ class SteamwarUser(id: EntityID<Int>): IntEntity(id) {
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 prefix by lazy { perms.firstOrNull { UserPerm.prefixes.containsKey(it) }?.let { UserPerm.prefixes[it]} ?: UserPerm.emptyPrefix }
+1 -2
View File
@@ -32,8 +32,6 @@ object TeamTable : IntIdTable("Team", "TeamID") {
val color = char("TeamColor", 1).default("8")
val name = varchar("TeamName", 16)
val deleted = bool("TeamDeleted").default(false)
val address = text("Address").nullable()
val port = ushort("Port").default(25565u)
}
class Team(id: EntityID<Int>) : IntEntity(id) {
@@ -68,6 +66,7 @@ class Team(id: EntityID<Int>) : IntEntity(id) {
var deleted by TeamTable.deleted
private set
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 disband(user: SteamwarUser) = useDb {
@@ -79,7 +79,7 @@ class TeamTeilnahme(id: EntityID<CompositeID>) : CompositeEntity(id) {
@JvmStatic
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
@@ -33,9 +33,6 @@ import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.OneShotStateDependent;
import de.steamwar.fightsystem.states.StateDependentListener;
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.SpigotLinker;
import de.steamwar.message.Message;
@@ -43,6 +40,7 @@ import de.steamwar.sql.NodeData;
import de.steamwar.sql.SchematicNode;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.GameRule;
import org.bukkit.plugin.java.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 OneShotStateDependent(ArenaMode.All, FightState.PreSchemSetup, () -> Fight.playSound(SWSound.BLOCK_NOTE_PLING.getSound(), 100.0f, 2.0f));
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();
hullHider = new HullHider();
@@ -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);
}
}
}
@@ -36,7 +36,6 @@ import de.steamwar.fightsystem.fight.FreezeWorld;
import de.steamwar.fightsystem.listener.FightScoreboard;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.utils.*;
import de.steamwar.sql.SchematicData;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.Team;
@@ -520,12 +519,12 @@ public class PacketProcessor implements Listener {
private void pasteEmbeddedSchem(FightTeam team) throws IOException {
int schemId = source.readInt();
Clipboard clipboard = SchematicData.clipboardFromStream(new FilterInputStream(source) {
Clipboard clipboard = WorldEditWrapper.impl.getClipboard(new FilterInputStream(source) {
@Override
public void close() {
// FAWE 1.12 calls close...
}
}, WorldEditWrapper.impl.getNativeFormat());
});
execSync(() -> team.pasteSchem(schemId, clipboard));
}
@@ -283,7 +283,7 @@ public interface Recorder {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try{
copy(NodeData.getLatest(SchematicNode.getSchematicNode(schemId)).schemData(), buffer);
copy(NodeData.getLatest(SchematicNode.getSchematicNode(schemId)).schemData(true), buffer);
}catch (EOFException e) {
Bukkit.getLogger().log(Level.INFO, "EOFException ignored");
} catch (IOException e) {
@@ -53,7 +53,7 @@ public class AutoChecker15 implements AutoChecker.IAutoChecker {
checkInventory(result, block, material, new BlockPos(x, y, z));
}
if(x == 0 || x == max.getBlockX() - 1 || y == max.getBlockY() - 1 || z == 0 || z == max.getBlockZ() - 1) {
if(x == min.getBlockX() || x == max.getBlockX() || y == max.getBlockY() || z == min.getBlockZ() || z == max.getBlockZ()) {
result.getDesignBlocks().computeIfAbsent(material, m -> new ArrayList<>()).add(new BlockPos(x, y, z));
}
}
@@ -20,9 +20,8 @@
package de.steamwar.schematicsystem.autocheck;
import de.steamwar.core.Core;
import de.steamwar.sql.GameModeConfig;
import de.steamwar.schematicsystem.SchematicSystem;
import de.steamwar.sql.SchematicType;
import de.steamwar.sql.GameModeConfig;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
@@ -102,7 +101,9 @@ public class AutoCheckerResult {
}
public boolean isDesignBlastResistanceOK() {
return blockScanResult.getDesignBlocks().keySet().stream().map(Material::getBlastResistance).noneMatch(i -> i > type.Schematic.MaxDesignBlastResistance);
return blockScanResult.getDesignBlocks().keySet().stream()
.filter(material -> material != Material.WATER && material != Material.LAVA)
.map(Material::getBlastResistance).noneMatch(i -> i > type.Schematic.MaxDesignBlastResistance);
}
public void sendErrorMessage(Player p, String schemName) {
@@ -148,6 +149,7 @@ public class AutoCheckerResult {
});
if(Core.getVersion() > 12) {
blockScanResult.getDesignBlocks().forEach((material, poss) -> {
if (material == Material.WATER || material == Material.LAVA) return;
if(material.getBlastResistance() > type.Schematic.MaxDesignBlastResistance) {
poss.forEach(pos -> {
SchematicSystem.MESSAGE.sendPrefixless("AUTO_CHECKER_RESULT_DESIGN_BLOCK", p, SchematicSystem.MESSAGE.parse("AUTO_CHECKER_RESULT_TELEPORT_HERE", p), tpCommandTo(pos), material.name(), pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
@@ -67,36 +67,31 @@ public class WorldEditWrapper14 implements WorldEditWrapper {
}
@Override
public void setPlayerClipboard(Player player, InputStream is, NodeData.SchematicFormat schemFormat) {
Clipboard clipboard = null;
try {
clipboard = getClipboard(is, schemFormat);
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
if (clipboard == null)
throw new NoClipboardException();
public void setPlayerClipboard(Player player, Clipboard clipboard) {
Actor actor = WorldEditWrapper.getWorldEditPlugin().wrapCommandSender(player);
WorldEditWrapper.getWorldEditPlugin().getWorldEdit().getSessionManager().get(actor).setClipboard(new ClipboardHolder(clipboard));
}
@Override
public Clipboard getClipboard(InputStream is, NodeData.SchematicFormat schemFormat) throws IOException {
try {
public Clipboard getClipboard(NodeData data) throws IOException {
InputStream is = data.schemData(true);
return readClipboard(is, data.getNodeFormat());
}
switch (schemFormat) {
case SPONGE_V2:
case SPONGE_V3:
return new SpongeSchematicReader(new NBTInputStream(is), this).read();
case MCEDIT:
return new MCEditSchematicReader(new NBTInputStream(is)).read();
default:
throw new IOException("This schematic format is currently not supported");
}
} catch (NullPointerException e) {
throw new NoClipboardException();
@Override
public Clipboard getClipboard(InputStream inputStream) throws IOException {
return readClipboard(inputStream, getNativeFormat());
}
private Clipboard readClipboard(InputStream is, NodeData.SchematicFormat format) throws IOException {
switch (format) {
case SPONGE_V2:
case SPONGE_V3:
return new SpongeSchematicReader(new NBTInputStream(is), this).read();
case MCEDIT:
return new MCEditSchematicReader(new NBTInputStream(is)).read();
default:
throw new IOException("This schematic format is currently not supported");
}
}
@@ -19,12 +19,13 @@
package de.steamwar.core;
import com.fastasyncworldedit.core.extent.clipboard.io.FastSchematicReaderV2;
import com.fastasyncworldedit.core.extent.clipboard.io.FastSchematicReaderV3;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.clipboard.io.BuiltInClipboardFormat;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter;
import com.sk89q.worldedit.extent.clipboard.io.MCEditSchematicReader;
import com.sk89q.worldedit.extent.clipboard.io.*;
import com.sk89q.worldedit.extent.clipboard.io.sponge.SpongeSchematicV1Reader;
import com.sk89q.worldedit.extent.clipboard.io.sponge.SpongeSchematicV2Reader;
import com.sk89q.worldedit.extent.clipboard.io.sponge.SpongeSchematicV3Reader;
import com.sk89q.worldedit.math.Vector3;
@@ -37,10 +38,12 @@ import org.bukkit.util.Vector;
import org.enginehub.linbus.stream.LinBinaryIO;
import java.io.DataInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class WorldEditWrapper21 implements WorldEditWrapper {
@@ -54,43 +57,68 @@ public class WorldEditWrapper21 implements WorldEditWrapper {
}
@Override
public void setPlayerClipboard(Player player, InputStream is, NodeData.SchematicFormat schemFormat) {
Clipboard clipboard = null;
try {
clipboard = getClipboard(is, schemFormat);
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
if (clipboard == null)
throw new SecurityException("Clipboard is null");
public void setPlayerClipboard(Player player, Clipboard clipboard) {
Actor actor = WorldEditWrapper.getWorldEditPlugin().wrapCommandSender(player);
WorldEditWrapper.getWorldEditPlugin().getWorldEdit().getSessionManager().get(actor).setClipboard(new ClipboardHolder(clipboard));
}
@Override
@SuppressWarnings("removal")
public Clipboard getClipboard(InputStream is, NodeData.SchematicFormat ignored) throws IOException {
ResetableInputStream ris = new ResetableInputStream(is);
for (NodeData.SchematicFormat schemFormat : NodeData.SchematicFormat.values()) {
try {
Clipboard clipboard = switch (schemFormat) {
case MCEDIT -> new MCEditSchematicReader(new NBTInputStream(ris)).read();
case SPONGE_V2 -> new SpongeSchematicV2Reader(LinBinaryIO.read(new DataInputStream(ris))).read();
case SPONGE_V3 -> new SpongeSchematicV3Reader(LinBinaryIO.read(new DataInputStream(ris))).read();
};
ris.close();
return clipboard;
} catch (Exception e) {
// Ignore
}
ris.reset();
public Clipboard getClipboard(NodeData data) throws IOException {
ResetableInputStream is = new ResetableInputStream(data.schemData(false));
for (ClipboardFormat clipboardFormat : ClipboardFormats.getAll()) {
FilterInputStream fis = new FilterInputStream(is) {
@Override
public void close() throws IOException {
// Ignore close call!
}
};
boolean canBeRead = clipboardFormat.isFormat(fis);
is.reset();
if (!canBeRead) continue;
return clipboardFormat.load(is);
}
throw new IOException("No clipboard found");
}
private class ResetableInputStream extends InputStream {
private static final Function<InputStream, ClipboardReader> FastV3 = FastSchematicReaderV3::new;
@SuppressWarnings("removal")
private static final Function<InputStream, ClipboardReader> FastV2 = inputStream -> new FastSchematicReaderV2(new NBTInputStream(inputStream));
@SuppressWarnings("removal")
private static final Function<InputStream, ClipboardReader> McEdit = inputStream -> new MCEditSchematicReader(new NBTInputStream(inputStream));
private static final Function<InputStream, ClipboardReader> SpongeV3 = inputStream -> new SpongeSchematicV3Reader(LinBinaryIO.read(new DataInputStream(inputStream)));
private static final Function<InputStream, ClipboardReader> SpongeV2 = inputStream -> new SpongeSchematicV2Reader(LinBinaryIO.read(new DataInputStream(inputStream)));
private static final Function<InputStream, ClipboardReader> SpongeV1 = inputStream -> new SpongeSchematicV1Reader(LinBinaryIO.read(new DataInputStream(inputStream)));
private static final Function<InputStream, ClipboardReader>[] READERS = new Function[]{
FastV3,
FastV2,
SpongeV3,
SpongeV2,
SpongeV1,
McEdit
};
@Override
public Clipboard getClipboard(InputStream inputStream) throws IOException {
ResetableInputStream is = new ResetableInputStream(inputStream);
for (Function<InputStream, ClipboardReader> reader : READERS) {
FilterInputStream fis = new FilterInputStream(is) {
@Override
public void close() throws IOException {
// Ignore close call!
}
};
try {
return reader.apply(fis).read();
} catch (Exception e) {
is.reset();
}
}
is.close();
throw new IOException("No clipboard found");
}
private static class ResetableInputStream extends InputStream {
private InputStream inputStream;
private int pointer = 0;
@@ -22,7 +22,10 @@ package de.steamwar.core;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.sk89q.jnbt.*;
import com.sk89q.worldedit.*;
import com.sk89q.worldedit.BlockVector;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.bukkit.BukkitWorld;
import com.sk89q.worldedit.extension.input.ParserContext;
@@ -57,22 +60,25 @@ public class WorldEditWrapper8 implements WorldEditWrapper {
}
@Override
public void setPlayerClipboard(Player player, InputStream is, NodeData.SchematicFormat schemFormat) {
public void setPlayerClipboard(Player player, Clipboard clipboard) {
WorldData world = new BukkitWorld(player.getWorld()).getWorldData();
Clipboard clipboard;
try {
clipboard = getClipboard(is, schemFormat);
} catch (IOException e) {
throw new RuntimeException(e);
}
Actor actor = WorldEditWrapper.getWorldEditPlugin().wrapCommandSender(player);
WorldEditWrapper.getWorldEditPlugin().getWorldEdit().getSessionManager().get(actor).setClipboard(new ClipboardHolder(clipboard, world));
}
@Override
public Clipboard getClipboard(InputStream is, NodeData.SchematicFormat schemFormat) throws IOException {
switch (schemFormat) {
public Clipboard getClipboard(NodeData data) throws IOException {
InputStream is = data.schemData(true);
return readClipboard(is, data.getNodeFormat());
}
@Override
public Clipboard getClipboard(InputStream inputStream) throws IOException {
return readClipboard(inputStream, getNativeFormat());
}
private Clipboard readClipboard(InputStream is, NodeData.SchematicFormat format) throws IOException {
switch (format) {
case MCEDIT:
return new SchematicReader(new NBTInputStream(is)).read(WorldEdit.getInstance().getServer().getWorlds().get(0).getWorldData());
case SPONGE_V2:
@@ -22,10 +22,10 @@ package de.steamwar.core;
import com.sk89q.worldedit.EmptyClipboardException;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.session.ClipboardHolder;
import de.steamwar.sql.NoClipboardException;
import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.session.ClipboardHolder;
import de.steamwar.sql.NoClipboardException;
import de.steamwar.sql.NodeData;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -38,8 +38,10 @@ public interface WorldEditWrapper {
WorldEditWrapper impl = VersionDependent.getVersionImpl(Core.getInstance());
InputStream getPlayerClipboard(Player player);
void setPlayerClipboard(Player player, InputStream is, NodeData.SchematicFormat schemFormat);
Clipboard getClipboard(InputStream is, NodeData.SchematicFormat schemFormat) throws IOException;
void setPlayerClipboard(Player player, Clipboard clipboard);
Clipboard getClipboard(NodeData data) throws IOException;
Clipboard getClipboard(InputStream inputStream) throws IOException;
Vector getOrigin(Clipboard clipboard);
Vector getMinimum(Region region);
@@ -29,14 +29,6 @@ import java.io.InputStream;
public class SchematicData {
public static Clipboard clipboardFromStream(InputStream is, NodeData.SchematicFormat schemFormat) {
try {
return WorldEditWrapper.impl.getClipboard(is, schemFormat);
} catch (IOException e) {
throw new SecurityException("Could not read schem", e);
}
}
private final NodeData data;
public SchematicData(SchematicNode node) {
@@ -60,11 +52,11 @@ public class SchematicData {
}
public Clipboard load() throws IOException, NoClipboardException {
return WorldEditWrapper.impl.getClipboard(data.schemData(), data.getNodeFormat());
return WorldEditWrapper.impl.getClipboard(data);
}
public void loadToPlayer(Player player) throws IOException, NoClipboardException {
WorldEditWrapper.impl.setPlayerClipboard(player, data.schemData(), data.getNodeFormat());
WorldEditWrapper.impl.setPlayerClipboard(player, WorldEditWrapper.impl.getClipboard(data));
}
public static void saveFromPlayer(Player player, SchematicNode node) throws IOException, NoClipboardException {
@@ -1,181 +0,0 @@
/*
* 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.teamserver.listener;
import com.sk89q.worldedit.bukkit.BukkitWorld;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.world.World;
import de.steamwar.core.WorldEditRenderer;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import de.steamwar.teamserver.Builder;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.Vector;
import java.util.*;
import java.util.function.Predicate;
@Linked
@MinVersion(20)
public class SelectAdjacent implements Listener {
private Vector[] FACES = {
new Vector(1, 0, 0),
new Vector(-1, 0, 0),
new Vector(0, 1, 0),
new Vector(0, -1, 0),
new Vector(0, 0, 1),
new Vector(0, 0, -1),
new Vector(1, 1, 0),
new Vector(1, -1, 0),
new Vector(1, 0, 1),
new Vector(1, 0, -1),
new Vector(-1, 1, 0),
new Vector(-1, -1, 0),
new Vector(-1, 0, 1),
new Vector(-1, 0, -1),
new Vector(0, 1, 1),
new Vector(0, 1, -1),
new Vector(0, -1, 1),
new Vector(0, -1, -1),
};
private Map<Player, Selector> selectors = new HashMap<>();
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
if (!event.hasItem()) return;
if (event.getItem().getType() != Material.WOODEN_AXE) return;
if (!event.getPlayer().isSneaking()) return;
if (event.getAction() != Action.LEFT_CLICK_BLOCK) return;
Selector selector = selectors.get(event.getPlayer());
if (selector != null) selector.cancel();
Material material = event.getPlayer().getInventory().getItemInOffHand().getType();
if (material.isAir()) {
selector = new Selector(event.getClickedBlock(), event.getPlayer(), __ -> true);
} else {
selector = new Selector(event.getClickedBlock(), event.getPlayer(), type -> type == material);
}
selectors.put(event.getPlayer(), selector);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
Selector selector = selectors.remove(event.getPlayer());
if (selector != null) selector.cancel();
}
private static final WorldEditPlugin WORLDEDIT_PLUGIN = Objects.requireNonNull((WorldEditPlugin) Bukkit.getPluginManager().getPlugin("WorldEdit"));
private static final World BUKKITWORLD = new BukkitWorld(Bukkit.getWorlds().get(0));
private class Selector {
private static final int MAX_BLOCKS = 1_000_000;
private int minX;
private int minY;
private int minZ;
private int maxX;
private int maxY;
private int maxZ;
private BukkitTask bukkitTask;
private Predicate<Material> predicate;
private Set<Location> seen = new HashSet<>();
private Set<Location> toCalc = new HashSet<>();
public Selector(Block block, Player player, Predicate<Material> predicate) {
this.predicate = predicate;
toCalc.add(block.getLocation());
minX = block.getX();
minY = block.getY();
minZ = block.getZ();
maxX = block.getX();
maxY = block.getY();
maxZ = block.getZ();
bukkitTask = Bukkit.getScheduler().runTaskTimer(Builder.getInstance(), () -> {
run();
long volume = (long)(maxX - minX + 1) * (long)(maxY - minY + 1) * (long)(maxZ - minZ + 1);
player.sendTitle("", "§e" + volume + " §7Blocks", 0, 5, 0);
WORLDEDIT_PLUGIN.getSession(player).setRegionSelector(BUKKITWORLD, new CuboidRegionSelector(BUKKITWORLD, BlockVector3.at(minX, minY, minZ), BlockVector3.at(maxX, maxY, maxZ)));
WorldEditRenderer.renderPlayer(player);
// boolean finished = toCalc.stream().allMatch(location -> {
// return location.getBlockX() >= minX && location.getBlockY() >= minY && location.getBlockZ() >= minZ &&
// location.getBlockX() <= maxX && location.getBlockY() <= maxY && location.getBlockZ() <= maxZ;
// });
if (toCalc.isEmpty() || seen.size() > MAX_BLOCKS) {
bukkitTask.cancel();
player.sendTitle("§aDone", "§e" + volume + " §7Blocks", 0, 20, 5);
}
}, 1, 1);
}
private void cancel() {
bukkitTask.cancel();
}
private void run() {
Set<Location> current = toCalc;
toCalc = new HashSet<>();
for (Location location : current) {
Block block = location.getBlock();
if (block.isEmpty() || block.isLiquid()) continue;
if (!predicate.test(block.getType())) continue;
seen.add(location);
minX = Math.min(minX, location.getBlockX());
maxX = Math.max(maxX, location.getBlockX());
minY = Math.min(minY, location.getBlockY());
maxY = Math.max(maxY, location.getBlockY());
minZ = Math.min(minZ, location.getBlockZ());
maxZ = Math.max(maxZ, location.getBlockZ());
for (Vector face : FACES) {
Block next = block.getRelative(face.getBlockX(), face.getBlockY(), face.getBlockZ());
if (next.isEmpty() || next.isLiquid()) continue;
if (!predicate.test(next.getType())) continue;
Location loc = next.getLocation();
if (seen.contains(loc)) continue;
toCalc.add(loc);
}
}
}
}
}
@@ -22,6 +22,7 @@ package de.steamwar.towerrun.commands;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeValidator;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.LinkedInstance;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
import de.steamwar.towerrun.TowerRun;
@@ -30,6 +31,8 @@ import org.bukkit.entity.Player;
@Linked
public class StartCommand extends SWCommand {
@LinkedInstance
private LobbyCountdown countdown;
public StartCommand() {
@@ -159,8 +159,8 @@ public class TowerGenerator {
noKeyFloors--;
if (!chestBlocks.isEmpty() && noKeyFloors < 0 && random.nextDouble() < config.keyChance) {
noKeyFloors = random.nextInt(config.maxNoKeyFloors - config.minNoKeyFloors) + config.minNoKeyFloors;
for (int i = 0; i < 2; i++) {
Container container = chestBlocks.get(random.nextInt(chestBlocks.size()));
for (int i = 0; i < 2 && !chestBlocks.isEmpty(); i++) {
Container container = chestBlocks.remove(random.nextInt(chestBlocks.size()));
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.PlayerDeathEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.*;
@@ -158,10 +159,16 @@ public class IngameListener extends GameStateBukkitListener {
public void onKeyUse(PlayerInteractEvent event) {
if (!event.hasItem()) return;
if (event.getItem().getType() != Material.LEVER) return;
event.setCancelled(true);
if (!event.hasBlock()) 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();
}
@@ -223,6 +230,8 @@ public class IngameListener extends GameStateBukkitListener {
shouldMelt(block.getRelative(0, 0, -1));
}
private static final Random RANDOM = new Random();
private void shouldMelt(Block block) {
if (block.getType().isBurnable()) return;
if (block.getType().isAir()) return;
@@ -269,7 +278,9 @@ public class IngameListener extends GameStateBukkitListener {
break;
}
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
@@ -42,6 +42,10 @@ public abstract class OutsideWincondition extends WinCondition {
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
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;
}
@@ -419,7 +419,6 @@ TEAM_NOT_IN_EVENT=§cThis is not possible during an event.
TEAM_HELP_HEADER=§7Manage your team with §e/team.
TEAM_HELP_LIST=§8/§7team list §8- §7List all teams.
TEAM_HELP_INFO=§8/§7team info §8- §7Get information on a team.
TEAM_HELP_TP=§8/§7team tp §8(§7Team§8) §8- §7Teleport to a team server.
TEAM_HELP_CREATE=§8/§7team create §8- §7Create your own team.
TEAM_HELP_JOIN=§8/§7team join §8- §7Join a team.
TEAM_HELP_CHAT=§8/§7teamchat §8- §7Send messages to your team.
@@ -391,7 +391,6 @@ TEAM_NOT_IN_EVENT=§cDies ist während eines Events nicht möglich.
TEAM_HELP_HEADER=§7Mit §e/team §7verwaltest du dein Team.
TEAM_HELP_LIST=§8/§7team list §8- §7Liste alle Teams auf.
TEAM_HELP_INFO=§8/§7team info §8- §7Informiere dich über ein Team.
TEAM_HELP_TP=§8/§7team tp §8(§7Team§8) §8- §7Teleportiert zum Teamserver.
TEAM_HELP_CREATE=§8/§7team create §8- §7Erstelle dein eigenes Team.
TEAM_HELP_JOIN=§8/§7team join §8- §7Trete einem Team bei.
TEAM_HELP_CHAT=§8/§7teamchat §8- §7Sende Nachrichten an dein Team.
@@ -19,14 +19,14 @@
package de.steamwar.velocitycore.commands;
import de.steamwar.linkage.Linked;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
import de.steamwar.sql.internal.Statement;
import de.steamwar.velocitycore.VelocityCore;
import java.io.*;
import java.util.zip.ZipEntry;
@@ -76,13 +76,11 @@ public class GDPRQuery extends SWCommand {
sqlCSV(user, out, bauweltMember, "BuildMember.csv");
sqlCSV(user, out, bauweltMembers, "BuildMembers.csv");
sqlCSV(user, out, checkedSchems, "SchematicChecksessions.csv");
sqlCSV(user, out, userElo, "UserElo.csv");
sqlCSV(user, out, fights, "Fights.csv");
sqlCSV(user, out, ignoredPlayers, "IgnoredPlayers.csv");
sqlCSV(user, out, ignoringPlayers, "IgnoringPlayers.csv");
sqlCSV(user, out, schematicMember, "SchematicMember.csv");
sqlCSV(user, out, schematicMembers, "SchematicMembers.csv");
sqlCSV(user, out, pollAnswers, "PollAnswers.csv");
sqlCSV(user, out, punishments, "Punishments.csv");
sqlCSV(user, out, sessions, "Sessions.csv");
sqlCSV(user, out, userData, "UserData.csv");
@@ -104,13 +102,11 @@ public class GDPRQuery extends SWCommand {
private static final Statement bauweltMember = new Statement("SELECT BauweltID AS Bauwelt, WorldEdit, World FROM BauweltMember WHERE MemberID = ?");
private static final Statement bauweltMembers = new Statement("SELECT u.UserName AS 'User', m.WorldEdit AS WorldEdit, m.World AS World FROM BauweltMember m INNER JOIN UserData u ON m.MemberID = u.id WHERE m.BauweltID = ?");
private static final Statement checkedSchems = new Statement("SELECT NodeName AS Schematic, StartTime, EndTime, DeclineReason AS Result FROM CheckedSchematic WHERE NodeOwner = ? ORDER BY StartTime ASC");
private static final Statement userElo = new Statement("SELECT GameMode, Elo, Season FROM Elo WHERE UserID = ?");
private static final Statement fights = new Statement("SELECT p.Team AS Team, p.Kit AS Kit, p.Kills AS Kills, p.IsOut AS Died, f.GameMode AS GameMode, f.Server AS Server, f.StartTime AS StartTime, f.Duration AS Duration, (f.BlueLeader = p.UserID) AS IsBlueLeader, (f.RedLeader = p.UserID) AS IsRedLeader, f.Win AS Winner, f.WinCondition AS WinCondition FROM Fight f INNER JOIN FightPlayer p ON f.FightID = p.FightID WHERE p.UserID = ? ORDER BY StartTime ASC");
private static final Statement ignoredPlayers = new Statement("SELECT u.UserName AS IgnoredPlayer FROM IgnoredPlayers i INNER JOIN UserData u ON i.Ignored = u.id WHERE Ignorer = ?");
private static final Statement ignoringPlayers = new Statement("SELECT Ignorer AS IgnoringPlayers FROM IgnoredPlayers WHERE Ignored = ?");
private static final Statement schematicMember = new Statement("SELECT s.NodeName AS SchematicName, u.UserName AS SchematicOwner FROM NodeMember m INNER JOIN SchematicNode s ON m.NodeId = s.NodeId INNER JOIN UserData u ON s.NodeOwner = u.id WHERE m.UserId = ?");
private static final Statement schematicMembers = new Statement("SELECT s.NodeName AS SchematicName, u.UserName AS Member FROM NodeMember m INNER JOIN SchematicNode s ON m.NodeId = s.NodeId INNER JOIN UserData u ON m.UserId = u.id WHERE s.NodeOwner = ?");
private static final Statement pollAnswers = new Statement("SELECT Question, Answer FROM PollAnswer WHERE UserID = ?");
private static final Statement punishments = new Statement("SELECT Type, StartTime, EndTime, Perma, Reason FROM Punishments WHERE UserId = ?");
private static final Statement sessions = new Statement("SELECT StartTime, EndTime FROM Session WHERE UserID = ?");
private static final Statement userData = new Statement("SELECT * FROM UserData WHERE id = ?");
@@ -19,12 +19,6 @@
package de.steamwar.velocitycore.commands;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.velocity.platform.VelocityViaConfig;
import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
@@ -33,19 +27,19 @@ import de.steamwar.linkage.Linked;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.persistent.Storage;
import de.steamwar.sql.*;
import de.steamwar.sql.Event;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.Team;
import de.steamwar.sql.TeamTeilnahme;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.inventory.SWItem;
import de.steamwar.velocitycore.inventory.SWListInv;
import lombok.val;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import java.net.*;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.*;
@@ -63,7 +57,7 @@ public class TeamCommand extends SWCommand {
@Register(noTabComplete = true)
public void help(Chatter sender, String... args){
helpMessages(sender, "TEAM_HELP_HEADER", "TEAM_HELP_LIST", "TEAM_HELP_INFO", "TEAM_HELP_TP");
helpMessages(sender, "TEAM_HELP_HEADER", "TEAM_HELP_LIST", "TEAM_HELP_INFO");
SteamwarUser user = sender.user();
if(user.getTeam() == 0) {
@@ -469,10 +463,14 @@ public class TeamCommand extends SWCommand {
@ClassMapper(Event.class)
public TypeMapper<Event> eventTypeMapper() {
return new TypeMapper<Event>() {
return new TypeMapper<>() {
@Override
public Event map(Chatter sender, PreviousArguments previousArguments, String s) {
return Event.get(s);
return Event.getComing()
.stream()
.filter(event -> event.getEventName().replace(" ", "").equalsIgnoreCase(s))
.findFirst()
.orElse(null);
}
@Override
@@ -487,7 +485,7 @@ public class TeamCommand extends SWCommand {
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
return Event.getComing().stream().map(Event::getEventName).toList();
return Event.getComing().stream().map(Event::getEventName).map(e -> e.replace(" ", "")).toList();
}
};
}
@@ -46,7 +46,9 @@ public class BanListener extends BasicListener {
SteamwarUser user = SteamwarUser.get(player.getUniqueId());
String ip = IPSanitizer.getTrueAddress(player).getHostAddress();
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));
return;
}
+51 -45
View File
@@ -47,61 +47,67 @@ data class UsernamePassword(val name: String, val password: String, val keepLogg
fun Route.configureAuth() {
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 {
call.sessions.clear<SWUserSession>()
call.respond(HttpStatusCode.NoContent)
}
route("/discord") {
post {
val token = call.receiveText()
configureDiscordAuth()
configurePasswordAuth()
}
}
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
fun Route.configurePasswordAuth() {
post {
val request = call.receive<UsernamePassword>()
if (discordId == null) {
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid Discord token", "invalid"))
return@post
}
SteamwarUser.clear()
val user = SteamwarUser.get(request.name)
val valid = user?.verifyPassword(request.password) ?: false
SteamwarUser.clear()
val user = SteamwarUser.get(discordId.toLong())
if (!valid) {
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid username or password", "invalid"))
return@post
}
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))
}
}
call.sessions.set(SWUserSession(user.getId()))
call.respond(ResponseUser(user))
}
fun Route.configureDiscordAuth() {
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
data class ResponseUserList(val entries: List<ResponseUser>, val rows: Long)
private fun Query.addUserFilter(
fun Query.addUserFilter(
name: String? = null,
uuid: UUID? = null,
team: Set<Int>? = null,
@@ -27,6 +27,7 @@ import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
import org.jetbrains.exposed.v1.core.ResultRow
import java.sql.Timestamp
import java.time.Instant
@@ -60,6 +61,8 @@ data class ResponseEventFight(
@Serializable
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(row: ResultRow) : this(row[TeamTable.id].value, row[TeamTable.name], row[TeamTable.kuerzel], row[TeamTable.color])
}
@Serializable
@@ -19,6 +19,12 @@
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.auth.*
import io.ktor.server.routing.*
@@ -34,6 +40,17 @@ fun Application.configureRoutes() {
configureSchematic()
configureAuth()
configureAuditLog()
route("/v2") {
configureAuditLog()
configurePage()
configureEventsRoute()
configureAuthV2()
configureTeamRoutes()
configureSteamWarRoute()
configureUsersRouteV2()
configureSchematicsV2Route()
configureGameModeRoutes()
}
}
}
}
@@ -43,8 +43,31 @@ import java.util.*
import java.util.zip.GZIPInputStream
@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) {
constructor(node: SchematicNode) : this(node.name, node.getId(), node.schemtype?.name(), node.owner, node.item, node.lastUpdate.time, node.rank, node.replaceColor(), node.allowReplay())
data class ResponseSchematic(
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
@@ -72,9 +95,12 @@ fun Route.configureSchematic() {
val node = call.receiveSchematic() ?: return@get
val user = call.principal<SWAuthPrincipal>()?.user
if(user != null && !node.accessibleByUser(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())
SWException.log(
"User ${user.userName} tried to download schematic ${node.name} without permission",
user.id.toString()
)
return@get
}
@@ -83,8 +109,15 @@ fun Route.configureSchematic() {
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)
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
)
}
get("/info") {
val node = call.receiveSchematic() ?: return@get
@@ -100,18 +133,22 @@ fun Route.configureSchematic() {
val schemName = file.name.substringBeforeLast(".")
if (SchematicNode.invalidSchemName(arrayOf(schemName))) {
call.respond(HttpStatusCode.BadRequest, ResponseError(
error = "INVALID_NAME"
))
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"
))
call.respond(
HttpStatusCode.BadRequest, ResponseError(
error = "INVALID_SUFFIX"
)
)
return@post
}
@@ -146,22 +183,27 @@ fun Route.configureSchematic() {
.value
if (fawe.equals("2.12.3-SNAPSHOT")) {
SWException.log("Schematic with Bugged Version Uploaded", """
SWException.log(
"Schematic with Bugged Version Uploaded", """
Schematic=$schemName
User=${user.userName}
Id=${user.id}
""".trimIndent())
""".trimIndent()
)
}
} catch (_: Exception) {}
} 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"
))
call.respond(
HttpStatusCode.BadRequest, ResponseError(
error = e.message ?: "GENERIC", code = "UPLOAD_ERROR"
)
)
}
}
}
@@ -178,7 +220,7 @@ suspend fun ApplicationCall.receiveSchematic(fieldName: String = "code", delete:
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)
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
}