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); manager.setFrozen(true);
bukkitTask.cancel(); bukkitTask.cancel();
}, 1, 1); }, 1, 1);
manager.tick();
} }
@Override @Override
@@ -46,6 +46,16 @@ import org.bukkit.event.player.PlayerInteractEvent;
@Linked @Linked
public class FreezeListener implements Listener, ScoreboardElement { 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 @EventHandler
public void onEntitySpawn(EntitySpawnEvent e) { public void onEntitySpawn(EntitySpawnEvent e) {
if (Region.getRegion(e.getLocation()).getRegionData().get(Flag.FREEZE).isWithDefault(FreezeMode.INACTIVE)) return; 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; Consumer<Integer> setter = observerPhase::setTickOffset;
return new SWItem[] { return new SWItem[] {
new SWItem(SWItem.getDye(getter.get() < max ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> { 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))); setter.accept(Math.min(max, getter.get() + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED), }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED),
observer, observer,
new SWItem(SWItem.getDye(getter.get() > min ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8:§e -5"), false, clickType -> { 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))); setter.accept(Math.max(min, getter.get() - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED), }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED),
@@ -32,6 +32,7 @@ import de.steamwar.inventory.SWItem;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import java.util.Arrays; import java.util.Arrays;
@@ -97,6 +98,7 @@ public class SimulatorObserverPhaseSettingsGui extends SimulatorBaseGui {
//Tick Offset //Tick Offset
int offset = observer.getTickOffset(); 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 -> { 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))); observer.setTickOffset(Math.min(max, offset + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -113,6 +115,7 @@ public class SimulatorObserverPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(19, offsetItem); 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 -> { 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))); observer.setTickOffset(Math.max(min, offset - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -120,6 +123,7 @@ public class SimulatorObserverPhaseSettingsGui extends SimulatorBaseGui {
//Order //Order
int order = observer.getOrder(); 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 -> { 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))); observer.setOrder(Math.min(SimulatorPhase.ORDER_LIMIT, order + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -138,6 +142,7 @@ public class SimulatorObserverPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(22, orderItem); 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 -> { 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))); observer.setOrder(Math.max(-SimulatorPhase.ORDER_LIMIT, order - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -28,6 +28,7 @@ import de.steamwar.data.CMDs;
import de.steamwar.inventory.SWItem; import de.steamwar.inventory.SWItem;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import java.util.Arrays; import java.util.Arrays;
@@ -67,6 +68,7 @@ public class SimulatorObserverSettingsGui extends SimulatorBaseGui {
// Base Tick // Base Tick
int baseTicks = observer.getBaseTick(); int baseTicks = observer.getBaseTick();
inventory.setItem(9, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> { 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); observer.changeBaseTicks(clickType.isShiftClick() ? 5 : 1);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).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))); baseTick.getItemStack().setAmount(Math.max(1, Math.min(baseTicks, 64)));
inventory.setItem(18, baseTick); 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 -> { 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) { if (baseTicks - (clickType.isShiftClick() ? 5 : 1) < 0) {
observer.changeBaseTicks(-baseTicks); observer.changeBaseTicks(-baseTicks);
} else { } else {
@@ -91,6 +94,7 @@ public class SimulatorObserverSettingsGui extends SimulatorBaseGui {
//Pos X //Pos X
inventory.setItem(15, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> { 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); observer.move(clickType.isShiftClick() ? 5 : 1, 0, 0);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -102,12 +106,14 @@ public class SimulatorObserverSettingsGui extends SimulatorBaseGui {
}, this).open(); }, this).open();
})); }));
inventory.setItem(33, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> { 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); observer.move(clickType.isShiftClick() ? -5 : -1, 0, 0);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
//Pos Y //Pos Y
inventory.setItem(16, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> { 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); observer.move(0, clickType.isShiftClick() ? 5 : 1, 0);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED));
@@ -119,12 +125,14 @@ public class SimulatorObserverSettingsGui extends SimulatorBaseGui {
}, this).open(); }, this).open();
})); }));
inventory.setItem(34, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> { 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); observer.move(0, clickType.isShiftClick() ? -5 : -1, 0);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
//Pos Z //Pos Z
inventory.setItem(17, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> { 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); observer.move(0, 0, clickType.isShiftClick() ? 5 : 1);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.ENABLED_OR_DISABLED));
@@ -136,6 +144,7 @@ public class SimulatorObserverSettingsGui extends SimulatorBaseGui {
}, this).open(); }, this).open();
})); }));
inventory.setItem(35, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> { 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); observer.move(0, 0, clickType.isShiftClick() ? -5 : -1);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).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; Consumer<Integer> setter = redstoneSubPhase.place ? redstoneSubPhase.phase::setTickOffset : redstoneSubPhase.phase::setLifetime;
return new SWItem[] { return new SWItem[] {
new SWItem(SWItem.getDye(getter.get() < max ? 10 : 8), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> { 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))); setter.accept(Math.min(max, getter.get() + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED), }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED),
redstone, redstone,
new SWItem(SWItem.getDye(getter.get() > min ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8:§e -5"), false, clickType -> { 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))); setter.accept(Math.max(min, getter.get() - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED), }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED),
@@ -31,6 +31,7 @@ import de.steamwar.data.CMDs;
import de.steamwar.inventory.SWItem; import de.steamwar.inventory.SWItem;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import java.util.Arrays; import java.util.Arrays;
@@ -98,6 +99,7 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
//Tick Offset //Tick Offset
int offset = redstone.getTickOffset(); 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 -> { 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))); redstone.setTickOffset(Math.min(maxOffset, offset + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -114,6 +116,7 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(19, offsetItem); 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 -> { 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))); redstone.setTickOffset(Math.max(min, offset - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -121,6 +124,7 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
//Lifetime //Lifetime
int lifetime = redstone.getLifetime(); 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 -> { 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))); redstone.setLifetime(Math.min(maxLifetime, lifetime + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -137,6 +141,7 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(20, lifetimeItem); 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 -> { 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))); redstone.setLifetime(Math.max(0, lifetime - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -144,6 +149,7 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
//Order //Order
int order = redstone.getOrder(); 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 -> { 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))); redstone.setOrder(Math.min(SimulatorPhase.ORDER_LIMIT, order + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -162,6 +168,7 @@ public class SimulatorRedstonePhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(22, orderItem); 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 -> { 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))); redstone.setOrder(Math.max(-SimulatorPhase.ORDER_LIMIT, order - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -28,6 +28,7 @@ import de.steamwar.data.CMDs;
import de.steamwar.inventory.SWItem; import de.steamwar.inventory.SWItem;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import java.util.Arrays; import java.util.Arrays;
@@ -66,6 +67,7 @@ public class SimulatorRedstoneSettingsGui extends SimulatorBaseGui {
// Base Tick // Base Tick
int baseTicks = redstone.getBaseTick(); int baseTicks = redstone.getBaseTick();
inventory.setItem(9, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> { 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); redstone.changeBaseTicks(clickType.isShiftClick() ? 5 : 1);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).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))); baseTick.getItemStack().setAmount(Math.max(1, Math.min(baseTicks, 64)));
inventory.setItem(18, baseTick); 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 -> { 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) { if (baseTicks - (clickType.isShiftClick() ? 5 : 1) < 0) {
redstone.changeBaseTicks(-baseTicks); redstone.changeBaseTicks(-baseTicks);
} else { } else {
@@ -90,6 +93,7 @@ public class SimulatorRedstoneSettingsGui extends SimulatorBaseGui {
//Pos X //Pos X
inventory.setItem(15, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> { 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); redstone.move(clickType.isShiftClick() ? 5 : 1, 0, 0);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -101,12 +105,14 @@ public class SimulatorRedstoneSettingsGui extends SimulatorBaseGui {
}, this).open(); }, this).open();
})); }));
inventory.setItem(33, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> { 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); redstone.move(clickType.isShiftClick() ? -5 : -1, 0, 0);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
//Pos Y //Pos Y
inventory.setItem(16, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> { 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); redstone.move(0, clickType.isShiftClick() ? 5 : 1, 0);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -118,12 +124,14 @@ public class SimulatorRedstoneSettingsGui extends SimulatorBaseGui {
}, this).open(); }, this).open();
})); }));
inventory.setItem(34, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> { 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); redstone.move(0, clickType.isShiftClick() ? -5 : -1, 0);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
//Pos Z //Pos Z
inventory.setItem(17, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> { 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); redstone.move(0, 0, clickType.isShiftClick() ? 5 : 1);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -135,6 +143,7 @@ public class SimulatorRedstoneSettingsGui extends SimulatorBaseGui {
}, this).open(); }, this).open();
})); }));
inventory.setItem(35, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-5"), false, clickType -> { 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); redstone.move(0, 0, clickType.isShiftClick() ? -5 : -1);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -138,11 +138,13 @@ public class SimulatorTNTGui extends SimulatorScrollGui<TNTPhase> {
return new SWItem[]{ return new SWItem[]{
new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8:§e +5"), false, clickType -> { 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)); tntSetting.setCount(tntSetting.getCount() + (clickType.isShiftClick() ? 5 : 1));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED), }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED),
tnt, tnt,
new SWItem(SWItem.getDye(tntSetting.getCount() > 1 ? 1 : 8), "§e-1", Arrays.asList("§7Shift§8:§e -5"), false, clickType -> { 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))); tntSetting.setCount(Math.max(1, tntSetting.getCount() - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED), }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED),
@@ -31,6 +31,7 @@ import de.steamwar.data.CMDs;
import de.steamwar.inventory.SWItem; import de.steamwar.inventory.SWItem;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import java.util.Arrays; import java.util.Arrays;
@@ -78,6 +79,7 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
//Count //Count
int count = tnt.getCount(); int count = tnt.getCount();
inventory.setItem(9, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> { 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)); tnt.setCount(count + (clickType.isShiftClick() ? 5 : 1));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -94,6 +96,7 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(18, countItem); 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 -> { 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))); tnt.setCount(Math.max(1, count - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -101,6 +104,7 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
//Tick Offset //Tick Offset
int offset = tnt.getTickOffset(); int offset = tnt.getTickOffset();
inventory.setItem(10, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> { 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)); tnt.setTickOffset(offset + (clickType.isShiftClick() ? 5 : 1));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -117,6 +121,7 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(19, offsetItem); 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 -> { 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))); tnt.setTickOffset(Math.max(0, offset - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -124,6 +129,7 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
//Lifetime //Lifetime
int lifetime = tnt.getLifetime(); int lifetime = tnt.getLifetime();
inventory.setItem(11, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> { 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)); tnt.setLifetime(lifetime + (clickType.isShiftClick() ? 5 : 1));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -140,6 +146,7 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(20, lifetimeItem); 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 -> { 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))); tnt.setLifetime(Math.max(1, lifetime - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
@@ -147,6 +154,7 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
//Order //Order
int order = tnt.getOrder(); 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 -> { 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))); tnt.setOrder(Math.min(SimulatorPhase.ORDER_LIMIT, order + (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -165,30 +173,35 @@ public class SimulatorTNTPhaseSettingsGui extends SimulatorBaseGui {
inventory.setItem(22, orderItem); 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 -> { 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))); tnt.setOrder(Math.max(-SimulatorPhase.ORDER_LIMIT, order - (clickType.isShiftClick() ? 5 : 1)));
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
//Jump //Jump
SWItem jumpX = new SWItem(tnt.isXJump() ? Material.LIME_WOOL : Material.RED_WOOL, "§7TNT §eJump X§8: " + (tnt.isZJump() ? "§aon" : "§coff"), clickType -> { 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()); tnt.setXJump(!tnt.isXJump());
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}); });
inventory.setItem(33, jumpX); 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 -> { 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()); tnt.setYJump(!tnt.isYJump());
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}); });
inventory.setItem(16, jumpY); 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 -> { 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()); tnt.setZJump(!tnt.isZJump());
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}); });
inventory.setItem(35, jumpZ); inventory.setItem(35, jumpZ);
SWItem jumpAll = new SWItem(Material.TNT, "§7TNT §eJump §8: " + (tnt.hasJump() ? "§aon" : "§coff"), clickType -> { SWItem jumpAll = new SWItem(Material.TNT, "§7TNT §eJump §8: " + (tnt.hasJump() ? "§aon" : "§coff"), clickType -> {
if (clickType == ClickType.DOUBLE_CLICK) return;
tnt.setJump(!tnt.hasJump()); tnt.setJump(!tnt.hasJump());
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}); });
@@ -28,6 +28,7 @@ import de.steamwar.data.CMDs;
import de.steamwar.inventory.SWItem; import de.steamwar.inventory.SWItem;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -75,6 +76,7 @@ public class SimulatorTNTSettingsGui extends SimulatorBaseGui {
// Base Tick // Base Tick
int baseTicks = tnt.getBaseTick(); int baseTicks = tnt.getBaseTick();
inventory.setItem(9, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+5"), false, clickType -> { 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); tnt.changeBaseTicks(clickType.isShiftClick() ? 5 : 1);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).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))); baseTick.getItemStack().setAmount(Math.max(1, Math.min(baseTicks, 64)));
inventory.setItem(18, baseTick); 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 -> { 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) { if (baseTicks - (clickType.isShiftClick() ? 5 : 1) < 0) {
tnt.changeBaseTicks(-baseTicks); tnt.changeBaseTicks(-baseTicks);
} else { } else {
@@ -136,6 +139,7 @@ public class SimulatorTNTSettingsGui extends SimulatorBaseGui {
// Pos X // Pos X
inventory.setItem(15, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+0.0625"), false, clickType -> { 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); tnt.move(clickType.isShiftClick() ? 0.0625 : 1, 0, 0);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -147,12 +151,14 @@ public class SimulatorTNTSettingsGui extends SimulatorBaseGui {
}, this).open(); }, this).open();
})); }));
inventory.setItem(33, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-0.0625"), false, clickType -> { 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); tnt.move(clickType.isShiftClick() ? -0.0625 : -1, 0, 0);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Pos Y // Pos Y
inventory.setItem(16, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+0.0625"), false, clickType -> { 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); tnt.move(0, clickType.isShiftClick() ? 0.0625 : 1, 0);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -164,12 +170,14 @@ public class SimulatorTNTSettingsGui extends SimulatorBaseGui {
}, this).open(); }, this).open();
})); }));
inventory.setItem(34, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-0.0625"), false, clickType -> { 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); tnt.move(0, clickType.isShiftClick() ? -0.0625 : -1, 0);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED));
// Pos Z // Pos Z
inventory.setItem(17, new SWItem(SWItem.getDye(10), "§e+1", Arrays.asList("§7Shift§8: §e+0.0625"), false, clickType -> { 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); tnt.move(0, 0, clickType.isShiftClick() ? 0.0625 : 1);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED)); }).setCustomModelData(CMDs.Simulator.INCREMENT_OR_DISABLED));
@@ -181,6 +189,7 @@ public class SimulatorTNTSettingsGui extends SimulatorBaseGui {
}, this).open(); }, this).open();
})); }));
inventory.setItem(35, new SWItem(SWItem.getDye(1), "§e-1", Arrays.asList("§7Shift§8: §e-0.0625"), false, clickType -> { 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); tnt.move(0, 0, clickType.isShiftClick() ? -0.0625 : -1);
SimulatorWatcher.update(simulator); SimulatorWatcher.update(simulator);
}).setCustomModelData(CMDs.Simulator.DECREMENT_OR_DISABLED)); }).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(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(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(); Cocoa cocoaNorth = (Cocoa) Material.COCOA.createBlockData();
cocoaNorth.setAge(2); cocoaNorth.setAge(2);
@@ -25,12 +25,14 @@ import de.steamwar.bausystem.shared.Pair;
import de.steamwar.bausystem.utils.WorldEditUtils; import de.steamwar.bausystem.utils.WorldEditUtils;
import de.steamwar.command.SWCommand; import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.linkage.MinVersion;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
@Linked @Linked
@MinVersion(19)
public class LaufbauCommand extends SWCommand { public class LaufbauCommand extends SWCommand {
public LaufbauCommand() { 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; public final boolean DisableSnowMelt;
/**
* Disable ice forming
*
* @implSpec {@code false} by default
*/
public final boolean DisableIceForm;
/** /**
* Allow leaving the arena area as spectator * Allow leaving the arena area as spectator
* *
@@ -470,6 +477,7 @@ public final class GameModeConfig<M, W> {
BorderFromSchematic = loader.getInt("BorderFromSchematic", 21); BorderFromSchematic = loader.getInt("BorderFromSchematic", 21);
GroundWalkable = loader.getBoolean("GroundWalkable", true); GroundWalkable = loader.getBoolean("GroundWalkable", true);
DisableSnowMelt = loader.getBoolean("DisableSnowMelt", false); DisableSnowMelt = loader.getBoolean("DisableSnowMelt", false);
DisableIceForm = loader.getBoolean("DisableIceForm", false);
Leaveable = loader.getBoolean("Leaveable", false); Leaveable = loader.getBoolean("Leaveable", false);
AllowMissiles = loader.getBoolean("AllowMissiles", !EnterStages.isEmpty()); AllowMissiles = loader.getBoolean("AllowMissiles", !EnterStages.isEmpty());
NoFloor = loader.getBoolean("NoFloor", false); NoFloor = loader.getBoolean("NoFloor", false);
@@ -88,8 +88,6 @@ class NodeData(id: EntityID<CompositeID>): CompositeEntity(id) {
schemData.inputStream.let { if(decompress) GZIPInputStream(it) else it } schemData.inputStream.let { if(decompress) GZIPInputStream(it) else it }
} }
fun schemData() = schemData(true)
override fun delete() = useDb { super.delete() } override fun delete() = useDb { super.delete() }
enum class SchematicFormat(val fileEnding: String) { enum class SchematicFormat(val fileEnding: String) {
@@ -94,7 +94,7 @@ class NodeMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
{ Optional.ofNullable(it?.value) }) { Optional.ofNullable(it?.value) })
private set private set
fun setParentId(id: Int?) { fun setParentId(id: Int?) = useDb {
parent = Optional.ofNullable(id) parent = Optional.ofNullable(id)
} }
@@ -146,7 +146,91 @@ class SchematicNode(id: EntityID<Int>) : IntEntity(id) {
@JvmStatic @JvmStatic
fun parentsOfNode(user: SteamwarUser, id: Int) = fromSql( 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( listOf(
IntegerColumnType() to id, IntegerColumnType() to id,
IntegerColumnType() to user.getId() IntegerColumnType() to user.getId()
@@ -284,7 +368,7 @@ class SchematicNode(id: EntityID<Int>) : IntEntity(id) {
val list = mutableListOf<String>() val list = mutableListOf<String>()
if (s.contains("/")) { if (s.contains("/")) {
val preTab = s.take(s.lastIndexOf("/") + 1) 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 nodes: List<SchematicNode> = list(user, pa.getId())
val br = pa.generateBreadcrumbs(user) val br = pa.generateBreadcrumbs(user)
nodes.forEach(Consumer { node: SchematicNode? -> list.add((if (sws) "/" else "") + br + node!!.name + (if (node.isDir()) "/" else "")) }) 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 var discordId by SteamwarUserTable.discordId
private val punishments by lazy { Punishment.getPunishmentsOfPlayer(id.value) } val punishments by lazy { Punishment.getPunishmentsOfPlayer(id.value) }
private val perms by lazy { UserPerm.getPerms(id.value) } private val perms by lazy { UserPerm.getPerms(id.value) }
private val prefix by lazy { perms.firstOrNull { UserPerm.prefixes.containsKey(it) }?.let { UserPerm.prefixes[it]} ?: UserPerm.emptyPrefix } private val prefix by lazy { perms.firstOrNull { UserPerm.prefixes.containsKey(it) }?.let { UserPerm.prefixes[it]} ?: UserPerm.emptyPrefix }
+1 -2
View File
@@ -32,8 +32,6 @@ object TeamTable : IntIdTable("Team", "TeamID") {
val color = char("TeamColor", 1).default("8") val color = char("TeamColor", 1).default("8")
val name = varchar("TeamName", 16) val name = varchar("TeamName", 16)
val deleted = bool("TeamDeleted").default(false) val deleted = bool("TeamDeleted").default(false)
val address = text("Address").nullable()
val port = ushort("Port").default(25565u)
} }
class Team(id: EntityID<Int>) : IntEntity(id) { class Team(id: EntityID<Int>) : IntEntity(id) {
@@ -68,6 +66,7 @@ class Team(id: EntityID<Int>) : IntEntity(id) {
var deleted by TeamTable.deleted var deleted by TeamTable.deleted
private set private set
val members by lazy { useDb { SteamwarUserTable.select(SteamwarUserTable.id).where { SteamwarUserTable.team eq teamId }.map { it[SteamwarUserTable.id].value } } } val members by lazy { useDb { SteamwarUserTable.select(SteamwarUserTable.id).where { SteamwarUserTable.team eq teamId }.map { it[SteamwarUserTable.id].value } } }
val membersUser by lazy { useDb { SteamwarUser.find { SteamwarUserTable.team eq teamId }.toList() } }
fun size() = useDb { SteamwarUser.find { SteamwarUserTable.team eq teamId }.count().toInt() } fun size() = useDb { SteamwarUser.find { SteamwarUserTable.team eq teamId }.count().toInt() }
fun disband(user: SteamwarUser) = useDb { fun disband(user: SteamwarUser) = useDb {
@@ -79,7 +79,7 @@ class TeamTeilnahme(id: EntityID<CompositeID>) : CompositeEntity(id) {
@JvmStatic @JvmStatic
fun getEvents(teamId: Int) = useDb { fun getEvents(teamId: Int) = useDb {
find { TeamTeilnahmeTable.teamId eq teamId }.map { Event.byId(it.eventId.value) }.toSet() find { TeamTeilnahmeTable.teamId eq teamId }.mapNotNull { Event.byId(it.eventId.value) }.toSet()
} }
@JvmStatic @JvmStatic
@@ -33,9 +33,6 @@ import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.OneShotStateDependent; import de.steamwar.fightsystem.states.OneShotStateDependent;
import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.fightsystem.utils.*; import de.steamwar.fightsystem.utils.*;
import de.steamwar.fightsystem.winconditions.Wincondition;
import de.steamwar.fightsystem.winconditions.WinconditionComparisonTimeout;
import de.steamwar.fightsystem.winconditions.Winconditions;
import de.steamwar.linkage.AbstractLinker; import de.steamwar.linkage.AbstractLinker;
import de.steamwar.linkage.SpigotLinker; import de.steamwar.linkage.SpigotLinker;
import de.steamwar.message.Message; import de.steamwar.message.Message;
@@ -43,6 +40,7 @@ import de.steamwar.sql.NodeData;
import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SchematicNode;
import lombok.Getter; import lombok.Getter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.GameRule;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
public class FightSystem extends JavaPlugin { public class FightSystem extends JavaPlugin {
@@ -100,6 +98,11 @@ public class FightSystem extends JavaPlugin {
new StateDependentListener(ArenaMode.All, FightState.All, BountifulWrapper.impl.newDenyArrowPickupListener()); new StateDependentListener(ArenaMode.All, FightState.All, BountifulWrapper.impl.newDenyArrowPickupListener());
new OneShotStateDependent(ArenaMode.All, FightState.PreSchemSetup, () -> Fight.playSound(SWSound.BLOCK_NOTE_PLING.getSound(), 100.0f, 2.0f)); new OneShotStateDependent(ArenaMode.All, FightState.PreSchemSetup, () -> Fight.playSound(SWSound.BLOCK_NOTE_PLING.getSound(), 100.0f, 2.0f));
new OneShotStateDependent(ArenaMode.Test, FightState.All, WorldEditRendererCUIEditor::new); new OneShotStateDependent(ArenaMode.Test, FightState.All, WorldEditRendererCUIEditor::new);
try {
Bukkit.getWorlds().get(0).setGameRule(GameRule.REDUCED_DEBUG_INFO, ArenaMode.AntiTest.contains(Config.mode));
} catch (Exception e) {
// Ignore if failed!
}
techHider = new TechHiderWrapper(); techHider = new TechHiderWrapper();
hullHider = new HullHider(); hullHider = new HullHider();
@@ -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.listener.FightScoreboard;
import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.utils.*; import de.steamwar.fightsystem.utils.*;
import de.steamwar.sql.SchematicData;
import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.Team; import de.steamwar.sql.Team;
@@ -520,12 +519,12 @@ public class PacketProcessor implements Listener {
private void pasteEmbeddedSchem(FightTeam team) throws IOException { private void pasteEmbeddedSchem(FightTeam team) throws IOException {
int schemId = source.readInt(); int schemId = source.readInt();
Clipboard clipboard = SchematicData.clipboardFromStream(new FilterInputStream(source) { Clipboard clipboard = WorldEditWrapper.impl.getClipboard(new FilterInputStream(source) {
@Override @Override
public void close() { public void close() {
// FAWE 1.12 calls close... // FAWE 1.12 calls close...
} }
}, WorldEditWrapper.impl.getNativeFormat()); });
execSync(() -> team.pasteSchem(schemId, clipboard)); execSync(() -> team.pasteSchem(schemId, clipboard));
} }
@@ -283,7 +283,7 @@ public interface Recorder {
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try{ try{
copy(NodeData.getLatest(SchematicNode.getSchematicNode(schemId)).schemData(), buffer); copy(NodeData.getLatest(SchematicNode.getSchematicNode(schemId)).schemData(true), buffer);
}catch (EOFException e) { }catch (EOFException e) {
Bukkit.getLogger().log(Level.INFO, "EOFException ignored"); Bukkit.getLogger().log(Level.INFO, "EOFException ignored");
} catch (IOException e) { } catch (IOException e) {
@@ -53,7 +53,7 @@ public class AutoChecker15 implements AutoChecker.IAutoChecker {
checkInventory(result, block, material, new BlockPos(x, y, z)); 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)); result.getDesignBlocks().computeIfAbsent(material, m -> new ArrayList<>()).add(new BlockPos(x, y, z));
} }
} }
@@ -20,9 +20,8 @@
package de.steamwar.schematicsystem.autocheck; package de.steamwar.schematicsystem.autocheck;
import de.steamwar.core.Core; import de.steamwar.core.Core;
import de.steamwar.sql.GameModeConfig;
import de.steamwar.schematicsystem.SchematicSystem; import de.steamwar.schematicsystem.SchematicSystem;
import de.steamwar.sql.SchematicType; import de.steamwar.sql.GameModeConfig;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.ToString; import lombok.ToString;
@@ -102,7 +101,9 @@ public class AutoCheckerResult {
} }
public boolean isDesignBlastResistanceOK() { 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) { public void sendErrorMessage(Player p, String schemName) {
@@ -148,6 +149,7 @@ public class AutoCheckerResult {
}); });
if(Core.getVersion() > 12) { if(Core.getVersion() > 12) {
blockScanResult.getDesignBlocks().forEach((material, poss) -> { blockScanResult.getDesignBlocks().forEach((material, poss) -> {
if (material == Material.WATER || material == Material.LAVA) return;
if(material.getBlastResistance() > type.Schematic.MaxDesignBlastResistance) { if(material.getBlastResistance() > type.Schematic.MaxDesignBlastResistance) {
poss.forEach(pos -> { 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()); 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 @Override
public void setPlayerClipboard(Player player, InputStream is, NodeData.SchematicFormat schemFormat) { public void setPlayerClipboard(Player player, Clipboard clipboard) {
Clipboard clipboard = null;
try {
clipboard = getClipboard(is, schemFormat);
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
if (clipboard == null)
throw new NoClipboardException();
Actor actor = WorldEditWrapper.getWorldEditPlugin().wrapCommandSender(player); Actor actor = WorldEditWrapper.getWorldEditPlugin().wrapCommandSender(player);
WorldEditWrapper.getWorldEditPlugin().getWorldEdit().getSessionManager().get(actor).setClipboard(new ClipboardHolder(clipboard)); WorldEditWrapper.getWorldEditPlugin().getWorldEdit().getSessionManager().get(actor).setClipboard(new ClipboardHolder(clipboard));
} }
@Override @Override
public Clipboard getClipboard(InputStream is, NodeData.SchematicFormat schemFormat) throws IOException { public Clipboard getClipboard(NodeData data) throws IOException {
try { InputStream is = data.schemData(true);
return readClipboard(is, data.getNodeFormat());
}
switch (schemFormat) { @Override
case SPONGE_V2: public Clipboard getClipboard(InputStream inputStream) throws IOException {
case SPONGE_V3: return readClipboard(inputStream, getNativeFormat());
return new SpongeSchematicReader(new NBTInputStream(is), this).read(); }
case MCEDIT:
return new MCEditSchematicReader(new NBTInputStream(is)).read(); private Clipboard readClipboard(InputStream is, NodeData.SchematicFormat format) throws IOException {
default: switch (format) {
throw new IOException("This schematic format is currently not supported"); case SPONGE_V2:
} case SPONGE_V3:
} catch (NullPointerException e) { return new SpongeSchematicReader(new NBTInputStream(is), this).read();
throw new NoClipboardException(); 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; 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.jnbt.NBTInputStream;
import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.clipboard.io.BuiltInClipboardFormat; import com.sk89q.worldedit.extent.clipboard.io.*;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter; import com.sk89q.worldedit.extent.clipboard.io.sponge.SpongeSchematicV1Reader;
import com.sk89q.worldedit.extent.clipboard.io.MCEditSchematicReader;
import com.sk89q.worldedit.extent.clipboard.io.sponge.SpongeSchematicV2Reader; import com.sk89q.worldedit.extent.clipboard.io.sponge.SpongeSchematicV2Reader;
import com.sk89q.worldedit.extent.clipboard.io.sponge.SpongeSchematicV3Reader; import com.sk89q.worldedit.extent.clipboard.io.sponge.SpongeSchematicV3Reader;
import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.math.Vector3;
@@ -37,10 +38,12 @@ import org.bukkit.util.Vector;
import org.enginehub.linbus.stream.LinBinaryIO; import org.enginehub.linbus.stream.LinBinaryIO;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.FilterInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Function;
public class WorldEditWrapper21 implements WorldEditWrapper { public class WorldEditWrapper21 implements WorldEditWrapper {
@@ -54,43 +57,68 @@ public class WorldEditWrapper21 implements WorldEditWrapper {
} }
@Override @Override
public void setPlayerClipboard(Player player, InputStream is, NodeData.SchematicFormat schemFormat) { public void setPlayerClipboard(Player player, Clipboard clipboard) {
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");
Actor actor = WorldEditWrapper.getWorldEditPlugin().wrapCommandSender(player); Actor actor = WorldEditWrapper.getWorldEditPlugin().wrapCommandSender(player);
WorldEditWrapper.getWorldEditPlugin().getWorldEdit().getSessionManager().get(actor).setClipboard(new ClipboardHolder(clipboard)); WorldEditWrapper.getWorldEditPlugin().getWorldEdit().getSessionManager().get(actor).setClipboard(new ClipboardHolder(clipboard));
} }
@Override @Override
@SuppressWarnings("removal") public Clipboard getClipboard(NodeData data) throws IOException {
public Clipboard getClipboard(InputStream is, NodeData.SchematicFormat ignored) throws IOException { ResetableInputStream is = new ResetableInputStream(data.schemData(false));
ResetableInputStream ris = new ResetableInputStream(is); for (ClipboardFormat clipboardFormat : ClipboardFormats.getAll()) {
for (NodeData.SchematicFormat schemFormat : NodeData.SchematicFormat.values()) { FilterInputStream fis = new FilterInputStream(is) {
try { @Override
Clipboard clipboard = switch (schemFormat) { public void close() throws IOException {
case MCEDIT -> new MCEditSchematicReader(new NBTInputStream(ris)).read(); // Ignore close call!
case SPONGE_V2 -> new SpongeSchematicV2Reader(LinBinaryIO.read(new DataInputStream(ris))).read(); }
case SPONGE_V3 -> new SpongeSchematicV3Reader(LinBinaryIO.read(new DataInputStream(ris))).read(); };
}; boolean canBeRead = clipboardFormat.isFormat(fis);
ris.close(); is.reset();
return clipboard; if (!canBeRead) continue;
} catch (Exception e) { return clipboardFormat.load(is);
// Ignore
}
ris.reset();
} }
throw new IOException("No clipboard found"); 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 InputStream inputStream;
private int pointer = 0; private int pointer = 0;
@@ -22,7 +22,10 @@ package de.steamwar.core;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.sk89q.jnbt.*; 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.blocks.BaseBlock;
import com.sk89q.worldedit.bukkit.BukkitWorld; import com.sk89q.worldedit.bukkit.BukkitWorld;
import com.sk89q.worldedit.extension.input.ParserContext; import com.sk89q.worldedit.extension.input.ParserContext;
@@ -57,22 +60,25 @@ public class WorldEditWrapper8 implements WorldEditWrapper {
} }
@Override @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(); 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); Actor actor = WorldEditWrapper.getWorldEditPlugin().wrapCommandSender(player);
WorldEditWrapper.getWorldEditPlugin().getWorldEdit().getSessionManager().get(actor).setClipboard(new ClipboardHolder(clipboard, world)); WorldEditWrapper.getWorldEditPlugin().getWorldEdit().getSessionManager().get(actor).setClipboard(new ClipboardHolder(clipboard, world));
} }
@Override @Override
public Clipboard getClipboard(InputStream is, NodeData.SchematicFormat schemFormat) throws IOException { public Clipboard getClipboard(NodeData data) throws IOException {
switch (schemFormat) { 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: case MCEDIT:
return new SchematicReader(new NBTInputStream(is)).read(WorldEdit.getInstance().getServer().getWorlds().get(0).getWorldData()); return new SchematicReader(new NBTInputStream(is)).read(WorldEdit.getInstance().getServer().getWorlds().get(0).getWorldData());
case SPONGE_V2: case SPONGE_V2:
@@ -22,10 +22,10 @@ package de.steamwar.core;
import com.sk89q.worldedit.EmptyClipboardException; import com.sk89q.worldedit.EmptyClipboardException;
import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.extent.clipboard.Clipboard; 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.math.transform.Transform;
import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.session.ClipboardHolder;
import de.steamwar.sql.NoClipboardException;
import de.steamwar.sql.NodeData; import de.steamwar.sql.NodeData;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -38,8 +38,10 @@ public interface WorldEditWrapper {
WorldEditWrapper impl = VersionDependent.getVersionImpl(Core.getInstance()); WorldEditWrapper impl = VersionDependent.getVersionImpl(Core.getInstance());
InputStream getPlayerClipboard(Player player); 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 getOrigin(Clipboard clipboard);
Vector getMinimum(Region region); Vector getMinimum(Region region);
@@ -29,14 +29,6 @@ import java.io.InputStream;
public class SchematicData { 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; private final NodeData data;
public SchematicData(SchematicNode node) { public SchematicData(SchematicNode node) {
@@ -60,11 +52,11 @@ public class SchematicData {
} }
public Clipboard load() throws IOException, NoClipboardException { 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 { 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 { 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.SWCommand;
import de.steamwar.command.TypeValidator; import de.steamwar.command.TypeValidator;
import de.steamwar.linkage.Linked; import de.steamwar.linkage.Linked;
import de.steamwar.linkage.LinkedInstance;
import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm; import de.steamwar.sql.UserPerm;
import de.steamwar.towerrun.TowerRun; import de.steamwar.towerrun.TowerRun;
@@ -30,6 +31,8 @@ import org.bukkit.entity.Player;
@Linked @Linked
public class StartCommand extends SWCommand { public class StartCommand extends SWCommand {
@LinkedInstance
private LobbyCountdown countdown; private LobbyCountdown countdown;
public StartCommand() { public StartCommand() {
@@ -159,8 +159,8 @@ public class TowerGenerator {
noKeyFloors--; noKeyFloors--;
if (!chestBlocks.isEmpty() && noKeyFloors < 0 && random.nextDouble() < config.keyChance) { if (!chestBlocks.isEmpty() && noKeyFloors < 0 && random.nextDouble() < config.keyChance) {
noKeyFloors = random.nextInt(config.maxNoKeyFloors - config.minNoKeyFloors) + config.minNoKeyFloors; noKeyFloors = random.nextInt(config.maxNoKeyFloors - config.minNoKeyFloors) + config.minNoKeyFloors;
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2 && !chestBlocks.isEmpty(); i++) {
Container container = chestBlocks.get(random.nextInt(chestBlocks.size())); Container container = chestBlocks.remove(random.nextInt(chestBlocks.size()));
keys.add(container.getLocation()); keys.add(container.getLocation());
} }
@@ -42,6 +42,7 @@ import org.bukkit.event.entity.EntityRegainHealthEvent;
import org.bukkit.event.entity.ItemSpawnEvent; import org.bukkit.event.entity.ItemSpawnEvent;
import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import java.util.*; import java.util.*;
@@ -158,10 +159,16 @@ public class IngameListener extends GameStateBukkitListener {
public void onKeyUse(PlayerInteractEvent event) { public void onKeyUse(PlayerInteractEvent event) {
if (!event.hasItem()) return; if (!event.hasItem()) return;
if (event.getItem().getType() != Material.LEVER) return; if (event.getItem().getType() != Material.LEVER) return;
event.setCancelled(true);
if (!event.hasBlock()) return; if (!event.hasBlock()) return;
if (event.getClickedBlock().getType() != Material.IRON_DOOR) return; if (event.getClickedBlock().getType() != Material.IRON_DOOR) return;
event.getPlayer().getInventory().setItemInMainHand(null); if (event.getHand() == null) return;
event.setCancelled(true);
ItemStack itemStack = event.getItem();
itemStack.setAmount(event.getItem().getAmount() - 1);
switch (event.getHand()) {
case OFF_HAND -> event.getPlayer().getInventory().setItemInOffHand(itemStack);
case HAND -> event.getPlayer().getInventory().setItemInMainHand(itemStack);
}
event.getClickedBlock().breakNaturally(); event.getClickedBlock().breakNaturally();
} }
@@ -223,6 +230,8 @@ public class IngameListener extends GameStateBukkitListener {
shouldMelt(block.getRelative(0, 0, -1)); shouldMelt(block.getRelative(0, 0, -1));
} }
private static final Random RANDOM = new Random();
private void shouldMelt(Block block) { private void shouldMelt(Block block) {
if (block.getType().isBurnable()) return; if (block.getType().isBurnable()) return;
if (block.getType().isAir()) return; if (block.getType().isAir()) return;
@@ -269,7 +278,9 @@ public class IngameListener extends GameStateBukkitListener {
break; break;
} }
Pos pos = new Pos(block.getLocation().getBlockX(), block.getLocation().getBlockY(), block.getLocation().getBlockZ()); Pos pos = new Pos(block.getLocation().getBlockX(), block.getLocation().getBlockY(), block.getLocation().getBlockZ());
blocksToMelt.putIfAbsent(pos, time + meltingTime + 1); int delay = meltingTime + 1 + RANDOM.nextInt(30*20)-30*10;
if (delay < 0) delay = meltingTime + 1;
blocksToMelt.putIfAbsent(pos, time + delay);
} }
@EventHandler @EventHandler
@@ -42,6 +42,10 @@ public abstract class OutsideWincondition extends WinCondition {
@EventHandler @EventHandler
public void onPlayerMove(PlayerMoveEvent event) { public void onPlayerMove(PlayerMoveEvent event) {
if (event.getTo().getY() > WorldConfig.ESCAPE_HEIGHT) { if (event.getTo().getY() > WorldConfig.ESCAPE_HEIGHT) {
if (event.getTo().getY() > WorldConfig.ESCAPE_HEIGHT + 10 && Arrays.stream(WorldConfig.REGIONS).noneMatch(region -> region.contains(event.getTo().toVector()))) {
TowerRunPlayer tPlayer = TowerRunPlayer.get(event.getPlayer());
tPlayer.player().damage(Integer.MAX_VALUE);
}
return; return;
} }
@@ -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_HEADER=§7Manage your team with §e/team.
TEAM_HELP_LIST=§8/§7team list §8- §7List all teams. TEAM_HELP_LIST=§8/§7team list §8- §7List all teams.
TEAM_HELP_INFO=§8/§7team info §8- §7Get information on a team. 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_CREATE=§8/§7team create §8- §7Create your own team.
TEAM_HELP_JOIN=§8/§7team join §8- §7Join a team. TEAM_HELP_JOIN=§8/§7team join §8- §7Join a team.
TEAM_HELP_CHAT=§8/§7teamchat §8- §7Send messages to your 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_HEADER=§7Mit §e/team §7verwaltest du dein Team.
TEAM_HELP_LIST=§8/§7team list §8- §7Liste alle Teams auf. 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_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_CREATE=§8/§7team create §8- §7Erstelle dein eigenes Team.
TEAM_HELP_JOIN=§8/§7team join §8- §7Trete einem Team bei. TEAM_HELP_JOIN=§8/§7team join §8- §7Trete einem Team bei.
TEAM_HELP_CHAT=§8/§7teamchat §8- §7Sende Nachrichten an dein Team. TEAM_HELP_CHAT=§8/§7teamchat §8- §7Sende Nachrichten an dein Team.
@@ -19,14 +19,14 @@
package de.steamwar.velocitycore.commands; package de.steamwar.velocitycore.commands;
import de.steamwar.linkage.Linked;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.command.SWCommand; import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked;
import de.steamwar.messages.Chatter; import de.steamwar.messages.Chatter;
import de.steamwar.messages.PlayerChatter; import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm; import de.steamwar.sql.UserPerm;
import de.steamwar.sql.internal.Statement; import de.steamwar.sql.internal.Statement;
import de.steamwar.velocitycore.VelocityCore;
import java.io.*; import java.io.*;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
@@ -76,13 +76,11 @@ public class GDPRQuery extends SWCommand {
sqlCSV(user, out, bauweltMember, "BuildMember.csv"); sqlCSV(user, out, bauweltMember, "BuildMember.csv");
sqlCSV(user, out, bauweltMembers, "BuildMembers.csv"); sqlCSV(user, out, bauweltMembers, "BuildMembers.csv");
sqlCSV(user, out, checkedSchems, "SchematicChecksessions.csv"); sqlCSV(user, out, checkedSchems, "SchematicChecksessions.csv");
sqlCSV(user, out, userElo, "UserElo.csv");
sqlCSV(user, out, fights, "Fights.csv"); sqlCSV(user, out, fights, "Fights.csv");
sqlCSV(user, out, ignoredPlayers, "IgnoredPlayers.csv"); sqlCSV(user, out, ignoredPlayers, "IgnoredPlayers.csv");
sqlCSV(user, out, ignoringPlayers, "IgnoringPlayers.csv"); sqlCSV(user, out, ignoringPlayers, "IgnoringPlayers.csv");
sqlCSV(user, out, schematicMember, "SchematicMember.csv"); sqlCSV(user, out, schematicMember, "SchematicMember.csv");
sqlCSV(user, out, schematicMembers, "SchematicMembers.csv"); sqlCSV(user, out, schematicMembers, "SchematicMembers.csv");
sqlCSV(user, out, pollAnswers, "PollAnswers.csv");
sqlCSV(user, out, punishments, "Punishments.csv"); sqlCSV(user, out, punishments, "Punishments.csv");
sqlCSV(user, out, sessions, "Sessions.csv"); sqlCSV(user, out, sessions, "Sessions.csv");
sqlCSV(user, out, userData, "UserData.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 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 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 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 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 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 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 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 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 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 sessions = new Statement("SELECT StartTime, EndTime FROM Session WHERE UserID = ?");
private static final Statement userData = new Statement("SELECT * FROM UserData WHERE id = ?"); private static final Statement userData = new Statement("SELECT * FROM UserData WHERE id = ?");
@@ -19,12 +19,6 @@
package de.steamwar.velocitycore.commands; 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.PreviousArguments;
import de.steamwar.command.SWCommand; import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper; import de.steamwar.command.TypeMapper;
@@ -33,19 +27,19 @@ import de.steamwar.linkage.Linked;
import de.steamwar.messages.Chatter; import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message; import de.steamwar.messages.Message;
import de.steamwar.messages.PlayerChatter; import de.steamwar.messages.PlayerChatter;
import de.steamwar.persistent.Storage; import de.steamwar.sql.Event;
import de.steamwar.sql.*; import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.Team;
import de.steamwar.sql.TeamTeilnahme;
import de.steamwar.velocitycore.VelocityCore; import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.discord.DiscordBot; import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.inventory.SWItem; import de.steamwar.velocitycore.inventory.SWItem;
import de.steamwar.velocitycore.inventory.SWListInv; import de.steamwar.velocitycore.inventory.SWListInv;
import lombok.val;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import java.net.*;
import java.time.Instant; import java.time.Instant;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
@@ -63,7 +57,7 @@ public class TeamCommand extends SWCommand {
@Register(noTabComplete = true) @Register(noTabComplete = true)
public void help(Chatter sender, String... args){ 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(); SteamwarUser user = sender.user();
if(user.getTeam() == 0) { if(user.getTeam() == 0) {
@@ -469,10 +463,14 @@ public class TeamCommand extends SWCommand {
@ClassMapper(Event.class) @ClassMapper(Event.class)
public TypeMapper<Event> eventTypeMapper() { public TypeMapper<Event> eventTypeMapper() {
return new TypeMapper<Event>() { return new TypeMapper<>() {
@Override @Override
public Event map(Chatter sender, PreviousArguments previousArguments, String s) { 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 @Override
@@ -487,7 +485,7 @@ public class TeamCommand extends SWCommand {
@Override @Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) { 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()); SteamwarUser user = SteamwarUser.get(player.getUniqueId());
String ip = IPSanitizer.getTrueAddress(player).getHostAddress(); String ip = IPSanitizer.getTrueAddress(player).getHostAddress();
if (user.isPunished(Punishment.PunishmentType.Ban)) { if (user.isPunished(Punishment.PunishmentType.Ban)) {
BannedUserIPs.banIP(user.getId(), ip); if (!player.getUsername().startsWith(".")) {
BannedUserIPs.banIP(user.getId(), ip);
}
Chatter.of(event).system(PunishmentCommand.punishmentMessage(user, Punishment.PunishmentType.Ban)); Chatter.of(event).system(PunishmentCommand.punishmentMessage(user, Punishment.PunishmentType.Ban));
return; return;
} }
+51 -45
View File
@@ -47,61 +47,67 @@ data class UsernamePassword(val name: String, val password: String, val keepLogg
fun Route.configureAuth() { fun Route.configureAuth() {
route("/auth") { route("/auth") {
val client = HttpClient(Java) {
install(ContentNegotiation) {
json()
}
}
post {
val request = call.receive<UsernamePassword>()
SteamwarUser.clear()
val user = SteamwarUser.get(request.name)
val valid = user?.verifyPassword(request.password) ?: false
if (!valid) {
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid username or password", "invalid"))
return@post
}
call.sessions.set(SWUserSession(user.getId()))
call.respond(ResponseUser(user))
}
delete { delete {
call.sessions.clear<SWUserSession>() call.sessions.clear<SWUserSession>()
call.respond(HttpStatusCode.NoContent) call.respond(HttpStatusCode.NoContent)
} }
route("/discord") { configureDiscordAuth()
post { configurePasswordAuth()
val token = call.receiveText() }
}
val res = client.get("https://discord.com/api/v10/oauth2/@me") { fun Route.configurePasswordAuth() {
headers { post {
append("Authorization", "Bearer $token") val request = call.receive<UsernamePassword>()
}
}
val resJson = Json.parseToJsonElement(res.bodyAsText()).jsonObject
val discordId = resJson["user"]?.jsonObject["id"]?.jsonPrimitive?.content
if (discordId == null) { SteamwarUser.clear()
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid Discord token", "invalid")) val user = SteamwarUser.get(request.name)
return@post val valid = user?.verifyPassword(request.password) ?: false
}
SteamwarUser.clear() if (!valid) {
val user = SteamwarUser.get(discordId.toLong()) call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid username or password", "invalid"))
return@post
}
if (user == null) { call.sessions.set(SWUserSession(user.getId()))
call.respond(HttpStatusCode.Forbidden, ResponseError("Discord account not linked", "not_linked")) call.respond(ResponseUser(user))
return@post }
} }
call.sessions.set(SWUserSession(user.getId())) fun Route.configureDiscordAuth() {
call.respond(ResponseUser(user)) val client = HttpClient(Java) {
} install(ContentNegotiation) {
json()
} }
} }
}
post("/discord") {
val token = call.receiveText()
val res = client.get("https://discord.com/api/v10/oauth2/@me") {
headers {
append("Authorization", "Bearer $token")
}
}
val resJson = Json.parseToJsonElement(res.bodyAsText()).jsonObject
val discordId = resJson["user"]?.jsonObject["id"]?.jsonPrimitive?.content
if (discordId == null) {
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid Discord token", "invalid"))
return@post
}
SteamwarUser.clear()
val user = SteamwarUser.get(discordId.toLong())
if (user == null) {
call.respond(HttpStatusCode.Forbidden, ResponseError("Discord account not linked", "not_linked"))
return@post
}
call.sessions.set(SWUserSession(user.getId()))
call.respond(ResponseUser(user))
}
}
@@ -75,7 +75,7 @@ data class ResponseUser(
@Serializable @Serializable
data class ResponseUserList(val entries: List<ResponseUser>, val rows: Long) data class ResponseUserList(val entries: List<ResponseUser>, val rows: Long)
private fun Query.addUserFilter( fun Query.addUserFilter(
name: String? = null, name: String? = null,
uuid: UUID? = null, uuid: UUID? = null,
team: Set<Int>? = null, team: Set<Int>? = null,
@@ -27,6 +27,7 @@ import io.ktor.server.request.*
import io.ktor.server.response.* import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import org.jetbrains.exposed.v1.core.ResultRow
import java.sql.Timestamp import java.sql.Timestamp
import java.time.Instant import java.time.Instant
@@ -60,6 +61,8 @@ data class ResponseEventFight(
@Serializable @Serializable
data class ResponseTeam(val id: Int, val name: String, val kuerzel: String, val color: String) { data class ResponseTeam(val id: Int, val name: String, val kuerzel: String, val color: String) {
constructor(team: Team) : this(team.teamId, team.teamName, team.teamKuerzel, team.teamColor) constructor(team: Team) : this(team.teamId, team.teamName, team.teamKuerzel, team.teamColor)
constructor(row: ResultRow) : this(row[TeamTable.id].value, row[TeamTable.name], row[TeamTable.kuerzel], row[TeamTable.color])
} }
@Serializable @Serializable
@@ -19,6 +19,12 @@
package de.steamwar.routes package de.steamwar.routes
import de.steamwar.routes.v2.configureAuthV2
import de.steamwar.routes.v2.configureGameModeRoutes
import de.steamwar.routes.v2.configureSchematicsV2Route
import de.steamwar.routes.v2.configureSteamWarRoute
import de.steamwar.routes.v2.configureTeamRoutes
import de.steamwar.routes.v2.configureUsersRouteV2
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.auth.* import io.ktor.server.auth.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
@@ -34,6 +40,17 @@ fun Application.configureRoutes() {
configureSchematic() configureSchematic()
configureAuth() configureAuth()
configureAuditLog() configureAuditLog()
route("/v2") {
configureAuditLog()
configurePage()
configureEventsRoute()
configureAuthV2()
configureTeamRoutes()
configureSteamWarRoute()
configureUsersRouteV2()
configureSchematicsV2Route()
configureGameModeRoutes()
}
} }
} }
} }
@@ -43,8 +43,31 @@ import java.util.*
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
@Serializable @Serializable
data class ResponseSchematic(val name: String, val id: Int, val type: String?, val owner: Int, val item: String, val lastUpdate: Long, val rank: Int, val replaceColor: Boolean, val allowReplay: Boolean) { data class ResponseSchematic(
constructor(node: SchematicNode) : this(node.name, node.getId(), node.schemtype?.name(), node.owner, node.item, node.lastUpdate.time, node.rank, node.replaceColor(), node.allowReplay()) val name: String,
val id: Int,
val type: String?,
val owner: Int,
val item: String,
val lastUpdate: Long,
val replaceColor: Boolean,
val allowReplay: Boolean,
val members: List<ResponseUser>
) {
constructor(node: SchematicNode) : this(
node.name,
node.getId(),
node.schemtype.name(),
node.owner,
node.item,
node.lastUpdate.time,
node.replaceColor(),
node.allowReplay(),
node.members.map {
ResponseUser(
SteamwarUser.byId(it.member)!!
)
})
} }
@Serializable @Serializable
@@ -72,9 +95,12 @@ fun Route.configureSchematic() {
val node = call.receiveSchematic() ?: return@get val node = call.receiveSchematic() ?: return@get
val user = call.principal<SWAuthPrincipal>()?.user val user = call.principal<SWAuthPrincipal>()?.user
if(user != null && !node.accessibleByUser(user)) { if (user != null && !node.accessibleByUser(user)) {
call.respond(HttpStatusCode.Forbidden) call.respond(HttpStatusCode.Forbidden)
SWException.log("User ${user.userName} tried to download schematic ${node.name} without permission", user.id.toString()) SWException.log(
"User ${user.userName} tried to download schematic ${node.name} without permission",
user.id.toString()
)
return@get return@get
} }
@@ -83,8 +109,15 @@ fun Route.configureSchematic() {
return@get return@get
} }
call.response.header("Content-Disposition", "attachment; filename=\"${node.name}${data.nodeFormat.fileEnding}\"") call.response.header(
call.respondBytes(data.schemData(false).readAllBytes(), contentType = ContentType.Application.OctetStream, status = HttpStatusCode.OK) "Content-Disposition",
"attachment; filename=\"${node.name}${data.nodeFormat.fileEnding}\""
)
call.respondBytes(
data.schemData(false).readAllBytes(),
contentType = ContentType.Application.OctetStream,
status = HttpStatusCode.OK
)
} }
get("/info") { get("/info") {
val node = call.receiveSchematic() ?: return@get val node = call.receiveSchematic() ?: return@get
@@ -100,18 +133,22 @@ fun Route.configureSchematic() {
val schemName = file.name.substringBeforeLast(".") val schemName = file.name.substringBeforeLast(".")
if (SchematicNode.invalidSchemName(arrayOf(schemName))) { if (SchematicNode.invalidSchemName(arrayOf(schemName))) {
call.respond(HttpStatusCode.BadRequest, ResponseError( call.respond(
error = "INVALID_NAME" HttpStatusCode.BadRequest, ResponseError(
)) error = "INVALID_NAME"
)
)
return@post return@post
} }
val schemType = file.name.substringAfterLast(".") val schemType = file.name.substringAfterLast(".")
if (schemType != "schem" && schemType != "schematic") { if (schemType != "schem" && schemType != "schematic") {
call.respond(HttpStatusCode.BadRequest, ResponseError( call.respond(
error = "INVALID_SUFFIX" HttpStatusCode.BadRequest, ResponseError(
)) error = "INVALID_SUFFIX"
)
)
return@post return@post
} }
@@ -146,22 +183,27 @@ fun Route.configureSchematic() {
.value .value
if (fawe.equals("2.12.3-SNAPSHOT")) { if (fawe.equals("2.12.3-SNAPSHOT")) {
SWException.log("Schematic with Bugged Version Uploaded", """ SWException.log(
"Schematic with Bugged Version Uploaded", """
Schematic=$schemName Schematic=$schemName
User=${user.userName} User=${user.userName}
Id=${user.id} Id=${user.id}
""".trimIndent()) """.trimIndent()
)
} }
} catch (_: Exception) {} } catch (_: Exception) {
}
} }
NodeData.saveFromStream(node, content.inputStream(), version) NodeData.saveFromStream(node, content.inputStream(), version)
call.respond(ResponseSchematic(node)) call.respond(ResponseSchematic(node))
} catch (e: Exception) { } catch (e: Exception) {
call.respond(HttpStatusCode.BadRequest, ResponseError( call.respond(
error = e.message ?: "GENERIC", code = "UPLOAD_ERROR" HttpStatusCode.BadRequest, ResponseError(
)) error = e.message ?: "GENERIC", code = "UPLOAD_ERROR"
)
)
} }
} }
} }
@@ -178,7 +220,7 @@ suspend fun ApplicationCall.receiveSchematic(fieldName: String = "code", delete:
return null return null
} }
if(dl.timestamp.toInstant().plus(Duration.of(5, ChronoUnit.MINUTES)).isBefore(Instant.now())) { if (dl.timestamp.toInstant().plus(Duration.of(5, ChronoUnit.MINUTES)).isBefore(Instant.now())) {
respond(HttpStatusCode.Gone) respond(HttpStatusCode.Gone)
return null return null
} }
@@ -0,0 +1,46 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.routes.v2
import de.steamwar.plugins.SWUserSession
import de.steamwar.routes.configureDiscordAuth
import de.steamwar.routes.configurePasswordAuth
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.call
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.delete
import io.ktor.server.routing.route
import io.ktor.server.sessions.clear
import io.ktor.server.sessions.sessions
fun Route.configureAuthV2() {
route("/auth") {
delete {
call.sessions.clear<SWUserSession>()
call.respond(HttpStatusCode.NoContent)
}
configureDiscordAuth()
route("/legacy") {
configurePasswordAuth()
}
}
}
@@ -0,0 +1,55 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.routes.v2
import de.steamwar.ResponseError
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.call
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.route
import org.bspfsystems.yamlconfiguration.file.YamlConfiguration
import java.io.File
import kotlin.io.nameWithoutExtension
fun Route.configureGameModeRoutes() {
route("/gamemodes") {
get {
call.respond(
File("/configs/GameModes/").listFiles()!!
.filter { it.name.endsWith(".yml") && !it.name.endsWith(".kits.yml") }
.map { it.nameWithoutExtension })
}
get("/{gamemode}/maps") {
val gamemode = call.parameters["gamemode"]
if (gamemode == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid gamemode"))
return@get
}
val file = File("/configs/GameModes/$gamemode.yml")
if (!file.exists()) {
call.respond(HttpStatusCode.NotFound, ResponseError("Gamemode not found"))
return@get
}
call.respond(YamlConfiguration.loadConfiguration(file).getStringList("Server.Maps"))
}
}
}
@@ -0,0 +1,210 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.routes.v2
import com.sun.tools.jdeprscan.Main.call
import de.steamwar.ResponseError
import de.steamwar.plugins.SWAuthPrincipal
import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.routes.ResponseSchematic
import de.steamwar.routes.ResponseSchematicType
import de.steamwar.routes.UploadSchematic
import de.steamwar.routes.nbt
import de.steamwar.routes.receiveSchematic
import de.steamwar.sql.NodeData
import de.steamwar.sql.NodeData.SchematicFormat
import de.steamwar.sql.NodeDownload
import de.steamwar.sql.SWException
import de.steamwar.sql.SchematicNode
import de.steamwar.sql.SchematicType
import dev.dewy.nbt.tags.collection.CompoundTag
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.auth.authentication
import io.ktor.server.auth.principal
import io.ktor.server.request.receive
import io.ktor.server.response.header
import io.ktor.server.response.respond
import io.ktor.server.response.respondBytes
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.route
import java.io.BufferedInputStream
import java.io.DataInputStream
import java.util.Base64
import java.util.zip.GZIPInputStream
data class ListedSchematicNode(val name: String, val id: Int, val type: String)
fun Route.configureSchematicsV2Route() {
route("/schematics") {
get("/types") {
call.respond(SchematicType.values().filter { !it.check() }
.map { ResponseSchematicType(it.name(), it.toDB()) })
}
get("/list/{path...}") {
val path = call.parameters.getAll("path")?.joinToString("/") ?: "/"
val user = call.authentication.principal<SWAuthPrincipal>()
if (user == null) {
call.respond(HttpStatusCode.Unauthorized)
return@get
}
val node = SchematicNode.getNodeFromPath(user.user, path)
call.respond(SchematicNode.list(user.user, node?.id?.value).map { ListedSchematicNode(it.name, it.id.value, it.schemtype.toDB()) })
}
get("/download/{code}") {
val node = call.receiveSchematic() ?: return@get
val user = call.principal<SWAuthPrincipal>()?.user
if(user != null && !node.accessibleByUser(user)) {
call.respond(HttpStatusCode.Forbidden)
SWException.log("User ${user.userName} tried to download schematic ${node.name} without permission", user.id.toString())
return@get
}
val data = NodeData.getLatest(node) ?: run {
call.respond(HttpStatusCode.InternalServerError)
return@get
}
call.response.header("Content-Disposition", "attachment; filename=\"${node.name}${data.nodeFormat.fileEnding}\"")
call.respondBytes(data.schemData(false).readAllBytes(), contentType = ContentType.Application.OctetStream, status = HttpStatusCode.OK)
}
post("/upload") {
val user = call.principal<SWAuthPrincipal>()?.user
if (user == null) {
call.respond(HttpStatusCode.Unauthorized)
return@post
}
val file = call.receive<UploadSchematic>()
val schemName = file.name.substringBeforeLast(".")
if (SchematicNode.invalidSchemName(arrayOf(schemName))) {
call.respond(HttpStatusCode.BadRequest, ResponseError(
error = "INVALID_NAME"
))
return@post
}
val schemType = file.name.substringAfterLast(".")
if (schemType != "schem" && schemType != "schematic") {
call.respond(HttpStatusCode.BadRequest, ResponseError(
error = "INVALID_SUFFIX"
))
return@post
}
var node = SchematicNode.getSchematicNode(user.getId(), schemName, null as Int?)
if (node == null) {
node = SchematicNode.createSchematic(user.getId(), schemName, null)
}
try {
val content = Base64.getDecoder().decode(file.content)
var schem = nbt.fromStream(DataInputStream(BufferedInputStream(GZIPInputStream(content.inputStream()))))
if (schem.size() == 1) schem = schem.first() as CompoundTag
val version = schem.let {
if (it.contains("Materials"))
return@let SchematicFormat.MCEDIT
else if (it.contains("Blocks"))
return@let SchematicFormat.SPONGE_V3
else
return@let SchematicFormat.SPONGE_V2
}
if (version == SchematicFormat.SPONGE_V3) {
try {
val fawe = schem.getCompound("Metadata")
.getCompound("WorldEdit")
.getString("Version")
.value
if (fawe.equals("2.12.3-SNAPSHOT")) {
SWException.log("Schematic with Bugged Version Uploaded", """
Schematic=$schemName
User=${user.userName}
Id=${user.id}
""".trimIndent())
}
} catch (_: Exception) {}
}
NodeData.saveFromStream(node, content.inputStream(), version)
call.respond(ResponseSchematic(node))
} catch (e: Exception) {
call.respond(HttpStatusCode.BadRequest, ResponseError(
error = e.message ?: "GENERIC", code = "UPLOAD_ERROR"
))
}
}
route("/{id}") {
install(SWPermissionCheck) {
mustAuth = true
}
get {
val node = call.receiveSchem() ?: return@get
call.respond(ResponseSchematic(node))
}
post("/download") {
val node = call.receiveSchem() ?: return@post
call.respond(HttpStatusCode.OK, NodeDownload.getLink(node))
}
}
}
}
suspend fun ApplicationCall.receiveSchem(): SchematicNode? {
val schemId = parameters["id"]?.toIntOrNull()
if (schemId == null) {
respond(HttpStatusCode.BadRequest)
return null
}
val schem = SchematicNode.getSchematicNode(schemId)
if (schem == null) {
respond(HttpStatusCode.NotFound)
return null
}
if (!(principal<SWAuthPrincipal>()?.user?.let { schem.accessibleByUser(it) } ?: false)) {
respond(HttpStatusCode.Forbidden)
return null
}
return schem
}
@@ -0,0 +1,60 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.routes.v2
import de.steamwar.ResponseError
import de.steamwar.routes.ResponseUser
import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.UserPerm
import de.steamwar.util.fetchData
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.call
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.route
import java.net.InetSocketAddress
fun Route.configureSteamWarRoute() {
route("/steamwar") {
get {
try {
val server = fetchData(InetSocketAddress("steamwar.de", 25565), 100)
call.respond(server)
} catch (e: Exception) {
e.printStackTrace()
call.respond(HttpStatusCode.InternalServerError, ResponseError(e.message ?: "Unknown error"))
return@get
}
}
get("/team") {
call.respond(
listOf(
UserPerm.PREFIX_ADMIN,
UserPerm.PREFIX_DEVELOPER,
UserPerm.PREFIX_MODERATOR,
UserPerm.PREFIX_SUPPORTER,
UserPerm.PREFIX_BUILDER
).associateWith { SteamwarUser.getUsersWithPerm(it) }.mapKeys { UserPerm.prefixes[it.key]!!.chatPrefix }
.mapValues { it.value.map { ResponseUser(it) } })
}
}
}
@@ -0,0 +1,87 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.routes.v2
import de.steamwar.routes.ResponseEvent
import de.steamwar.routes.ResponseTeam
import de.steamwar.routes.ResponseUser
import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.Team
import de.steamwar.sql.TeamTable
import de.steamwar.sql.TeamTeilnahme
import de.steamwar.sql.internal.useDb
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.call
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.route
import kotlinx.serialization.Serializable
import org.jetbrains.exposed.v1.core.like
import org.jetbrains.exposed.v1.jdbc.Query
import org.jetbrains.exposed.v1.jdbc.andWhere
import org.jetbrains.exposed.v1.jdbc.selectAll
fun Query.addTeamFilter(teamName: String?, teamKuerzel: String?): Query {
teamName?.let { andWhere { TeamTable.name like "%$it%" } }
teamKuerzel?.let { andWhere { TeamTable.kuerzel like "%$it%" } }
return this
}
fun Route.configureTeamRoutes() {
route("/teams") {
get {
val teamName = call.request.queryParameters["name"]
val teamKuerzel = call.request.queryParameters["kuerzel"]
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 100
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 0
call.respond(useDb {
TeamTable.selectAll().addTeamFilter(teamName, teamKuerzel).limit(limit).offset((page * limit).toLong())
.map { ResponseTeam(it) }
})
}
get("/{team}") {
@Serializable
data class TeamResponse(
val team: ResponseTeam,
val members: List<ResponseUser>,
val leaders: List<ResponseUser>,
val events: List<ResponseEvent>
)
val team = call.parameters["team"]?.toIntOrNull()?.let { Team.byId(it) }
if (team == null) {
call.respond(HttpStatusCode.NotFound)
return@get
}
call.respond(useDb {
TeamResponse(
ResponseTeam(team),
team.membersUser.map { ResponseUser(it) },
team.membersUser.filter { it.leader }.map { ResponseUser(it) },
TeamTeilnahme.getEvents(team.teamId).map { ResponseEvent(it) })
})
}
}
}
@@ -0,0 +1,274 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.routes.v2
import de.steamwar.ResponseError
import de.steamwar.data.getCachedSkin
import de.steamwar.plugins.SWAuthPrincipal
import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.routes.ResponseTeam
import de.steamwar.routes.ResponseUser
import de.steamwar.routes.ResponseUserList
import de.steamwar.routes.UserStats
import de.steamwar.routes.addUserFilter
import de.steamwar.routes.catchException
import de.steamwar.sql.Punishment
import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.SteamwarUserTable
import de.steamwar.sql.Team
import de.steamwar.sql.UserPerm
import de.steamwar.sql.internal.useDb
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.auth.principal
import io.ktor.server.request.receive
import io.ktor.server.response.header
import io.ktor.server.response.respond
import io.ktor.server.response.respondFile
import io.ktor.server.routing.Route
import io.ktor.server.routing.delete
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.put
import io.ktor.server.routing.route
import kotlinx.serialization.Serializable
import org.jetbrains.exposed.v1.jdbc.selectAll
import java.sql.Timestamp
import java.time.Instant
import java.util.UUID
@Serializable
data class ResponsePunishment(
val id: Int,
val type: Punishment.PunishmentType,
val reason: String,
val issuer: ResponseUser,
val startTime: Long,
val endTime: Long,
val perma: Boolean,
val active: Boolean
) {
constructor(punishment: Punishment) : this(
punishment.id.value,
punishment.type,
punishment.reason,
ResponseUser(SteamwarUser.byId(punishment.punisher)!!),
punishment.startTime.toInstant().toEpochMilli(),
punishment.endTime.toInstant().toEpochMilli(),
punishment.perma,
punishment.isCurrent()
)
}
@Serializable
data class CreatePunishment(
val type: Punishment.PunishmentType,
val reason: String,
val perma: Boolean,
val endTime: Long
)
fun Route.configureUsersRouteV2() {
get("/users/{user}/skin") {
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
return@get
}
val skin = getCachedSkin(user.uuid.toString())
call.response.header("X-Cache", if (skin.second) "HIT" else "MISS")
call.response.header("Cache-Control", "public, max-age=604800")
call.respondFile(skin.first)
}
get("/users/{user}/stats") {
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
return@get
}
val authUser = call.principal<SWAuthPrincipal>()
if (authUser == null || !(authUser.user.id == user.id || authUser.user.hasPerm(UserPerm.MODERATION))) {
call.respond(HttpStatusCode.Forbidden)
return@get
}
call.respond(UserStats(user))
}
route("/users") {
install(SWPermissionCheck) {
permission = UserPerm.MODERATION
}
get {
val name = call.request.queryParameters["name"]
val uuid = call.request.queryParameters["uuid"]?.let { catchException { UUID.fromString(it) } }
val team = call.request.queryParameters.getAll("team")?.map { it.toInt() }?.toSet()
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 100
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 0
val includePerms = call.request.queryParameters["includePerms"]?.toBoolean() ?: false
val includeId = call.request.queryParameters["includeId"]?.toBoolean() ?: false
call.respond(
useDb {
ResponseUserList(
SteamwarUserTable.selectAll().addUserFilter(name, uuid, team).limit(limit)
.offset((page * limit).toLong())
.map { ResponseUser(it, includeId, includePerms) },
SteamwarUserTable.selectAll().addUserFilter(name, uuid, team).count()
)
}
)
}
route("/{user}") {
get {
@Serializable
data class UserResponse(val user: ResponseUser, val team: ResponseTeam)
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.NotFound)
return@get
}
call.respond(
useDb {
UserResponse(ResponseUser(user), ResponseTeam(Team.byId(user.team)))
}
)
}
put("/prefix/{prefix}") {
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
return@put
}
val prefix = call.parameters["prefix"]?.let { name -> UserPerm.entries.find { it.name == name } }
if (prefix == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid prefix"))
return@put
}
user.perms().filter { it.name.startsWith("PREFIX_") }.forEach {
UserPerm.removePerm(user, it)
}
if (prefix != UserPerm.PREFIX_NONE) {
UserPerm.addPerm(user, prefix)
}
call.respond(HttpStatusCode.Accepted)
}
route("/perms") {
get {
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
return@get
}
call.respond(user.perms())
}
route("/{perm}") {
put {
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
return@put
}
val perm = call.parameters["perm"]?.let { name -> UserPerm.entries.find { it.name == name } }
if (perm == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid perm"))
return@put
}
UserPerm.addPerm(user, perm)
call.respond(HttpStatusCode.Accepted)
}
delete {
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
return@delete
}
val perm = call.parameters["perm"]?.let { name -> UserPerm.entries.find { it.name == name } }
if (perm == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid perm"))
return@delete
}
UserPerm.removePerm(user, perm)
call.respond(HttpStatusCode.Accepted)
}
}
}
}
route("/punishments") {
get {
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
return@get
}
val punishments = user.punishments.toList()
call.respond(punishments.map { ResponsePunishment(it.second) })
}
post {
val user = call.receiveUser()
if (user == null) {
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
return@post
}
val punishment = call.receive<CreatePunishment>()
val punisher = call.principal<SWAuthPrincipal>()?.user ?: return@post
user.punish(punishment.type, Timestamp.from(Instant.ofEpochMilli(punishment.endTime)), punishment.reason, punisher.getId(), punishment.perma)
call.respond(HttpStatusCode.Accepted)
}
}
}
}
fun ApplicationCall.receiveUser(): SteamwarUser? {
val userString = parameters["user"] ?: return null
if (userString == "me") {
return principal<SWAuthPrincipal>()?.user
}
userString.toIntOrNull()?.let { return SteamwarUser.byId(it) }
userString.let { catchException { UUID.fromString(it) } }?.let { return SteamwarUser.get(it) }
return null
}