diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCommand.java
index a8263ddb..0a55d692 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCommand.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCommand.java
@@ -38,7 +38,7 @@ import java.util.Collection;
public class SimulatorCommand extends SWCommand {
@LinkedInstance
- public SimulatorCursor simulatorCursor;
+ public SimulatorCursorManager simulatorCursorManager;
public SimulatorCommand() {
super("sim", "simulator");
@@ -47,12 +47,12 @@ public class SimulatorCommand extends SWCommand {
@Register(description = "SIMULATOR_HELP")
public void genericCommand(@Validator Player p) {
SWUtils.giveItemToPlayer(p, SimulatorStorage.getWand(p));
- simulatorCursor.calcCursor(p);
+ simulatorCursorManager.calcCursor(p);
}
@Register(value = "change", description = "SIMULATOR_CHANGE_HELP")
public void change(@Validator Player p) {
- if (!SimulatorCursor.isSimulatorItem(p.getInventory().getItemInMainHand()) && !SimulatorCursor.isSimulatorItem(p.getInventory().getItemInOffHand())) {
+ if (!SimulatorCursorManager.isSimulatorItem(p.getInventory().getItemInMainHand()) && !SimulatorCursorManager.isSimulatorItem(p.getInventory().getItemInOffHand())) {
BauSystem.MESSAGE.send("SIMULATOR_NO_SIM_IN_HAND", p);
return;
}
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursorManager.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursorManager.java
new file mode 100644
index 00000000..8a469160
--- /dev/null
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursorManager.java
@@ -0,0 +1,368 @@
+/*
+ * 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 .
+ */
+
+package de.steamwar.bausystem.features.simulator;
+
+import de.steamwar.bausystem.BauSystem;
+import de.steamwar.bausystem.Permission;
+import de.steamwar.bausystem.SWUtils;
+import de.steamwar.bausystem.features.simulator.data.Simulator;
+import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
+import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
+import de.steamwar.bausystem.features.simulator.data.observer.ObserverElement;
+import de.steamwar.bausystem.features.simulator.data.observer.ObserverPhase;
+import de.steamwar.bausystem.features.simulator.data.redstone.RedstoneElement;
+import de.steamwar.bausystem.features.simulator.data.redstone.RedstonePhase;
+import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
+import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
+import de.steamwar.bausystem.features.simulator.execute.SimulatorExecutor;
+import de.steamwar.bausystem.features.simulator.gui.SimulatorGroupGui;
+import de.steamwar.bausystem.features.simulator.gui.SimulatorGui;
+import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
+import de.steamwar.bausystem.utils.BauMemberUpdateEvent;
+import de.steamwar.bausystem.utils.ItemUtils;
+import de.steamwar.core.SWPlayer;
+import de.steamwar.cursor.Cursor;
+import de.steamwar.entity.REntity;
+import de.steamwar.entity.REntityServer;
+import de.steamwar.inventory.SWAnvilInv;
+import de.steamwar.linkage.Linked;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.Action;
+import org.bukkit.event.player.PlayerDropItemEvent;
+import org.bukkit.event.player.PlayerItemHeldEvent;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.event.player.PlayerToggleSneakEvent;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.util.Vector;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Linked
+public class SimulatorCursorManager implements Listener {
+
+ private static class SimulatorCursorComponent implements SWPlayer.Component {
+ private CursorType cursorType = CursorType.TNT;
+ private REntityServer emptyTargetServer;
+ private long lastSneakMillis;
+
+ private REntityServer getOrCreateEmptyTargetServer() {
+ if (emptyTargetServer == null) {
+ emptyTargetServer = new REntityServer();
+ }
+ return emptyTargetServer;
+ }
+
+ private boolean isDoubleSneak() {
+ long now = System.currentTimeMillis();
+ boolean doubleSneak = now - lastSneakMillis <= 200;
+ lastSneakMillis = doubleSneak ? 0 : now;
+ return doubleSneak;
+ }
+
+ @Override
+ public void onUnmount(SWPlayer player) {
+ player.removeComponent(Cursor.class);
+ if (emptyTargetServer != null) {
+ emptyTargetServer.close();
+ }
+ }
+ }
+
+ public static boolean isSimulatorItem(ItemStack itemStack) {
+ return ItemUtils.isItem(itemStack, "simulator");
+ }
+
+ private static boolean hasSimulatorItem(Player player) {
+ return isSimulatorItem(player.getInventory().getItemInMainHand()) || isSimulatorItem(player.getInventory().getItemInOffHand());
+ }
+
+ @EventHandler
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
+ Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> calcCursor(event.getPlayer()), 0);
+ }
+
+ @EventHandler
+ public void onPlayerDropItem(PlayerDropItemEvent event) {
+ if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
+ calcCursor(event.getPlayer());
+ }
+
+ @EventHandler
+ public void onPlayerItemHeld(PlayerItemHeldEvent event) {
+ Player player = event.getPlayer();
+ if (!Permission.BUILD.hasPermission(player)) return;
+
+ boolean hasSimulatorInNewMainHand = isSimulatorItem(player.getInventory().getItem(event.getNewSlot()));
+ boolean hasSimulatorInOffHand = isSimulatorItem(player.getInventory().getItemInOffHand());
+ if (!hasSimulatorInNewMainHand && !hasSimulatorInOffHand) {
+ if (deactivateCursor(player) || SimulatorWatcher.show(null, player)) {
+ SWUtils.sendToActionbar(player, "");
+ }
+ return;
+ }
+
+ Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> calcCursor(player), 1);
+ }
+
+ @EventHandler
+ public void onBauMemberUpdate(BauMemberUpdateEvent event) {
+ event.getChanged().forEach(SimulatorCursorManager::calcCursor);
+ }
+
+ @EventHandler
+ public void onPlayerQuit(PlayerQuitEvent event) {
+ deactivateCursor(event.getPlayer());
+ }
+
+ @EventHandler(priority = EventPriority.HIGH)
+ public void onPlayerToggleSneak(PlayerToggleSneakEvent event) {
+ if (!event.isSneaking()) return;
+ Player player = event.getPlayer();
+ if (!hasSimulatorItem(player)) {
+ return;
+ }
+ SimulatorCursorComponent component = getOrCreateComponent(player);
+ if (component.isDoubleSneak()) {
+ component.cursorType = component.cursorType == CursorType.TNT ? CursorType.REDSTONE_BLOCK : CursorType.TNT;
+ calcCursor(player);
+ }
+ }
+
+ public static CursorType getCursorType(Player player) {
+ return SWPlayer.of(player).getComponent(SimulatorCursorComponent.class)
+ .map(component -> component.cursorType)
+ .orElse(CursorType.TNT);
+ }
+
+ public static void setCursorType(Player player, CursorType cursorType) {
+ getOrCreateComponent(player).cursorType = cursorType;
+ calcCursor(player);
+ }
+
+ public static void calcCursor(Player player) {
+ if (!Permission.BUILD.hasPermission(player) || !hasSimulatorItem(player)) {
+ if (deactivateCursor(player) || SimulatorWatcher.show(null, player)) {
+ SWUtils.sendToActionbar(player, "");
+ }
+ return;
+ }
+
+ Simulator simulator = SimulatorStorage.getSimulator(player);
+ if (simulator != null && simulator.getStabGenerator() != null) {
+ deactivateCursor(player);
+ SimulatorWatcher.show(null, player);
+ SWUtils.sendToActionbar(player, "§cGenerating Stab");
+ return;
+ }
+
+ SimulatorCursorComponent component = getOrCreateComponent(player);
+ SimulatorWatcher.show(simulator, player);
+ Cursor cursor = getOrCreateCursor(player, simulator, component);
+ cursor.renderDeduplicated();
+ }
+
+ private static SimulatorCursorComponent getOrCreateComponent(Player player) {
+ return SWPlayer.of(player).getComponentOrDefault(SimulatorCursorComponent.class, SimulatorCursorComponent::new);
+ }
+
+ private static Cursor getOrCreateCursor(Player player, Simulator simulator, SimulatorCursorComponent component) {
+ REntityServer targetServer = simulator == null ? component.getOrCreateEmptyTargetServer() : SimulatorWatcher.getEntityServerOfSimulator(simulator);
+ SWPlayer swPlayer = SWPlayer.of(player);
+ Optional activeCursor = swPlayer.getComponent(Cursor.class);
+ CursorType type = component.cursorType;
+
+ Cursor cursor = activeCursor.orElse(null);
+ if (cursor == null || cursor.getTargetServer() != targetServer) {
+ swPlayer.removeComponent(Cursor.class);
+ cursor = new Cursor(
+ targetServer,
+ player,
+ Material.GLASS,
+ type.material,
+ type.cursorModes,
+ (location, hitEntity, action) -> handlePlayerClick(player, location, hitEntity, action),
+ (location, hitEntity) -> sendCursorActionbar(player, component, SimulatorStorage.getSimulator(player), location != null, hitEntity.isPresent())
+ );
+ } else {
+ cursor.setCursorMaterial(type.material);
+ cursor.setAllowedCursorModes(type.cursorModes);
+ }
+
+ return cursor;
+ }
+
+ private static synchronized boolean deactivateCursor(Player player) {
+ SWPlayer swPlayer = SWPlayer.of(player);
+ boolean hadSimulatorCursor = swPlayer.hasComponent(SimulatorCursorComponent.class);
+ boolean hadCursor = swPlayer.hasComponent(Cursor.class);
+ swPlayer.removeComponent(SimulatorCursorComponent.class);
+ if (!hadSimulatorCursor) {
+ swPlayer.removeComponent(Cursor.class);
+ }
+ return hadSimulatorCursor || hadCursor;
+ }
+
+ private static void sendCursorActionbar(Player player, SimulatorCursorComponent component, Simulator simulator, boolean hasCursorLocation, boolean hasHitEntity) {
+ if (!hasCursorLocation) {
+ SWUtils.sendToActionbar(player, simulator == null ? "§eSelect Simulator" : "§eOpen Simulator");
+ } else if (simulator == null) {
+ SWUtils.sendToActionbar(player, "§eCreate new Simulator");
+ } else if (hasHitEntity) {
+ SWUtils.sendToActionbar(player, "§eEdit Position");
+ } else {
+ SWUtils.sendToActionbar(player, "§eAdd new " + component.cursorType.name);
+ }
+ }
+
+ @Getter
+ @AllArgsConstructor
+ public enum CursorType {
+ TNT(Material.TNT, Material.GUNPOWDER, List.of(Cursor.CursorMode.FREE), "TNT", vector -> new TNTElement(vector).add(new TNTPhase())),
+ REDSTONE_BLOCK(Material.REDSTONE_BLOCK, Material.REDSTONE, List.of(Cursor.CursorMode.BLOCK_ALIGNED), "Redstone Block", vector -> new RedstoneElement(vector).add(new RedstonePhase())),
+ OBSERVER(Material.OBSERVER, Material.QUARTZ, List.of(Cursor.CursorMode.BLOCK_ALIGNED), "Observer", vector -> new ObserverElement(vector).add(new ObserverPhase())),
+ ;
+
+ public final Material material;
+ public final Material nonSelectedMaterial;
+ public final List cursorModes;
+ public final String name;
+ public final Function> elementFunction;
+ }
+
+ private static void handlePlayerClick(Player player, Location cursorLocation, Optional hitEntity, Action action) {
+ if (!Permission.BUILD.hasPermission(player)) return;
+ if (!hasSimulatorItem(player)) {
+ return;
+ }
+
+ Simulator simulator = SimulatorStorage.getSimulator(player);
+
+ if (action == Action.LEFT_CLICK_BLOCK || action == Action.LEFT_CLICK_AIR) {
+ if (simulator == null) {
+ return;
+ }
+ SimulatorExecutor.run(player, simulator, null);
+ return;
+ }
+
+ if (action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) {
+ return;
+ }
+
+ if (simulator == null) {
+ if (cursorLocation == null) {
+ SimulatorStorage.openSimulatorSelector(player);
+ } else {
+ SWAnvilInv anvilInv = new SWAnvilInv(player, "Name");
+ anvilInv.setCallback(s -> {
+ Simulator sim = SimulatorStorage.getSimulator(s);
+ if (sim != null) {
+ BauSystem.MESSAGE.send("SIMULATOR_NAME_ALREADY_EXISTS", player);
+ return;
+ }
+ if (!s.matches("[a-zA-Z_0-9-]+")) {
+ BauSystem.MESSAGE.send("SIMULATOR_NAME_INVALID", player);
+ return;
+ }
+ sim = new Simulator(s);
+ SimulatorStorage.addSimulator(s, sim);
+ createElement(player, cursorLocation, sim);
+ SimulatorStorage.setSimulator(player, sim);
+ });
+ anvilInv.open();
+ }
+ return;
+ }
+
+ if (cursorLocation == null) {
+ new SimulatorGui(player, simulator).open();
+ return;
+ }
+
+ if (hitEntity.isPresent()) {
+ openElement(player, simulator, hitEntity.get());
+ return;
+ }
+
+ createElement(player, cursorLocation, simulator);
+ }
+
+ private static void openElement(Player player, Simulator simulator, REntity hitEntity) {
+ Vector vector = new Vector(hitEntity.getX(), hitEntity.getY(), hitEntity.getZ());
+ List> elements = simulator.getGroups().stream().map(SimulatorGroup::getElements).flatMap(List::stream).filter(element -> {
+ return element.getWorldPos().distanceSquared(vector) < (1 / 16.0) * (1 / 16.0);
+ }).collect(Collectors.toList());
+
+ switch (elements.size()) {
+ case 0:
+ return;
+ case 1:
+ SimulatorElement> element = elements.get(0);
+ SimulatorGroup group1 = element.getGroup(simulator);
+ SimulatorBaseGui back = new SimulatorGui(player, simulator);
+ if (group1.getElements().size() > 1) {
+ back = new SimulatorGroupGui(player, simulator, group1, back);
+ }
+ element.open(player, simulator, group1, back);
+ break;
+ default:
+ List parents = elements.stream().map(e -> e.getGroup(simulator)).distinct().collect(Collectors.toList());
+ if (parents.size() == 1) {
+ SimulatorGui simulatorGui = new SimulatorGui(player, simulator);
+ new SimulatorGroupGui(player, simulator, parents.get(0), simulatorGui).open();
+ } else {
+ SimulatorGroup group2 = new SimulatorGroup();
+ group2.setMaterial(null);
+ group2.getElements().addAll(elements);
+ SimulatorGui simulatorGui = new SimulatorGui(player, simulator);
+ new SimulatorGroupGui(player, simulator, group2, simulatorGui).open();
+ }
+ break;
+ }
+ }
+
+ private static void createElement(Player player, Location cursorLocation, Simulator simulator) {
+ CursorType type = getCursorType(player);
+ Vector vector = cursorLocation.toVector();
+ if (type == CursorType.REDSTONE_BLOCK) {
+ vector.subtract(new Vector(0.5, 0, 0.5));
+ }
+ SimulatorElement> element = type.elementFunction.apply(vector);
+ SimulatorGroup group = new SimulatorGroup().add(element);
+ simulator.getGroups().add(group);
+ SimulatorGui simulatorGui = new SimulatorGui(player, simulator);
+ element.open(player, simulator, group, simulatorGui);
+ SimulatorWatcher.update(simulator);
+ calcCursor(player);
+ }
+}
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorStorage.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorStorage.java
index 09ac51da..5be1c526 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorStorage.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorStorage.java
@@ -59,7 +59,7 @@ public class SimulatorStorage implements Enable {
}
public static Simulator getSimulator(ItemStack itemStack) {
- if (!SimulatorCursor.isSimulatorItem(itemStack)) {
+ if (!SimulatorCursorManager.isSimulatorItem(itemStack)) {
return null;
}
String selection = ItemUtils.getTag(itemStack, simulatorSelection);
@@ -181,9 +181,9 @@ public class SimulatorStorage implements Enable {
ItemStack mainHand = player.getInventory().getItemInMainHand();
ItemStack offHand = player.getInventory().getItemInOffHand();
ItemStack itemStack;
- if (SimulatorCursor.isSimulatorItem(mainHand)) {
+ if (SimulatorCursorManager.isSimulatorItem(mainHand)) {
itemStack = mainHand;
- } else if (SimulatorCursor.isSimulatorItem(offHand)) {
+ } else if (SimulatorCursorManager.isSimulatorItem(offHand)) {
itemStack = offHand;
} else {
itemStack = null;
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/gui/SimulatorCursorSwitcherGui.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/gui/SimulatorCursorSwitcherGui.java
index 0344014e..e571723d 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/gui/SimulatorCursorSwitcherGui.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/gui/SimulatorCursorSwitcherGui.java
@@ -19,7 +19,7 @@
package de.steamwar.bausystem.features.simulator.gui;
-import de.steamwar.bausystem.features.simulator.SimulatorCursor;
+import de.steamwar.bausystem.features.simulator.SimulatorCursorManager;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.data.CMDs;
@@ -50,14 +50,14 @@ public class SimulatorCursorSwitcherGui extends SimulatorBaseGui {
}).setCustomModelData(CMDs.BACK));
int slot = 2;
- SimulatorCursor.CursorType currentType = SimulatorCursor.getCursorType(player);
- for (SimulatorCursor.CursorType type : SimulatorCursor.CursorType.values()) {
+ SimulatorCursorManager.CursorType currentType = SimulatorCursorManager.getCursorType(player);
+ for (SimulatorCursorManager.CursorType type : SimulatorCursorManager.CursorType.values()) {
boolean selected = type == currentType;
SWItem swItem = new SWItem(selected ? type.material : type.nonSelectedMaterial, "§e" + type.name)
.setCustomModelData(selected ? 0 : CMDs.Simulator.NEW_PHASE)
.setLore(Collections.singletonList("§eClick to select"))
.setCallback(click -> {
- SimulatorCursor.setCursorType(player, type);
+ SimulatorCursorManager.setCursorType(player, type);
player.closeInventory();
});
inventory.setItem(slot, swItem);
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/gui/SimulatorGui.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/gui/SimulatorGui.java
index 533078cf..255c7a2a 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/gui/SimulatorGui.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/gui/SimulatorGui.java
@@ -19,7 +19,7 @@
package de.steamwar.bausystem.features.simulator.gui;
-import de.steamwar.bausystem.features.simulator.SimulatorCursor;
+import de.steamwar.bausystem.features.simulator.SimulatorCursorManager;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorElement;
@@ -50,7 +50,7 @@ public class SimulatorGui extends SimulatorPageGui {
inventory.setItem(4, simulator.toItem(player, clickType -> {
new SimulatorMaterialGui(player, simulator, simulator::getMaterial, simulator::setMaterial, this).open();
}));
- SimulatorCursor.CursorType cursorType = SimulatorCursor.getCursorType(player);
+ SimulatorCursorManager.CursorType cursorType = SimulatorCursorManager.getCursorType(player);
inventory.setItem(48, new SWItem(cursorType.material, "§7Placing §8-§e " + cursorType.name, clickType -> {
new SimulatorCursorSwitcherGui(player, simulator, this).open();
}));