Update from Git Manager GUI
This commit is contained in:
@@ -1,11 +1,13 @@
|
|||||||
package viper;
|
package viper;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.NamespacedKey;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockFace;
|
||||||
import org.bukkit.block.data.Lightable;
|
import org.bukkit.block.data.Lightable;
|
||||||
import org.bukkit.block.data.type.NoteBlock;
|
import org.bukkit.block.data.type.NoteBlock;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
@@ -24,11 +26,16 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class ButtonControl extends JavaPlugin {
|
public class ButtonControl extends JavaPlugin {
|
||||||
|
private static final String TIMED_CONTAINER_MODE_SIMULTANEOUS = "simultaneous";
|
||||||
|
private static final String TIMED_CONTAINER_MODE_SEQUENTIAL = "sequential";
|
||||||
|
|
||||||
private ConfigManager configManager;
|
private ConfigManager configManager;
|
||||||
private DataManager dataManager;
|
private DataManager dataManager;
|
||||||
|
|
||||||
@@ -42,6 +49,27 @@ public class ButtonControl extends JavaPlugin {
|
|||||||
// Actionbar-Status pro Spieler für die Namensanzeige
|
// Actionbar-Status pro Spieler für die Namensanzeige
|
||||||
private final Map<java.util.UUID, String> lastControllerActionbar = new HashMap<>();
|
private final Map<java.util.UUID, String> lastControllerActionbar = new HashMap<>();
|
||||||
|
|
||||||
|
// Undo-System: letzte Aktion pro Controller (wird nach 5 Minuten gelöscht)
|
||||||
|
private final Map<String, UndoAction> lastActions = new HashMap<>();
|
||||||
|
|
||||||
|
// Secret-Wall Runtime (offene Wände + Originalzustand)
|
||||||
|
private final Map<String, List<SecretBlockSnapshot>> openSecretWalls = new HashMap<>();
|
||||||
|
|
||||||
|
// Knarrherz-Override: aktivierte Herzen werden zyklisch auf "active=true" gehalten
|
||||||
|
private final Set<String> forcedActiveCreakingHearts = new HashSet<>();
|
||||||
|
|
||||||
|
// Geöffnete Gitter (AIR) mit Originalmaterial für die Wiederherstellung
|
||||||
|
private final Map<String, Material> openGrates = new HashMap<>();
|
||||||
|
|
||||||
|
// Laufende Zeitplan-Shows für Werfer/Spender pro Controller
|
||||||
|
private final Map<String, Integer> timedContainerTasks = new HashMap<>();
|
||||||
|
private final Map<String, Integer> timedContainerTaskShotDelays = new HashMap<>();
|
||||||
|
private final Map<String, String> timedContainerTaskModes = new HashMap<>();
|
||||||
|
private final Map<String, Integer> timedContainerNextIndices = new HashMap<>();
|
||||||
|
|
||||||
|
// Secret-Editor: Spieler -> ausgewählter Controller
|
||||||
|
private final Map<java.util.UUID, String> selectedSecretController = new HashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
configManager = new ConfigManager(this);
|
configManager = new ConfigManager(this);
|
||||||
@@ -89,13 +117,20 @@ public class ButtonControl extends JavaPlugin {
|
|||||||
getServer().getScheduler().runTaskTimer(this, this::checkDaylightSensors, 0L, 20L * 10);
|
getServer().getScheduler().runTaskTimer(this, this::checkDaylightSensors, 0L, 20L * 10);
|
||||||
getServer().getScheduler().runTaskTimer(this, this::checkMotionSensors, 0L, 10L);
|
getServer().getScheduler().runTaskTimer(this, this::checkMotionSensors, 0L, 10L);
|
||||||
getServer().getScheduler().runTaskTimer(this, this::checkTimedControllers, 0L, 20L * 5);
|
getServer().getScheduler().runTaskTimer(this, this::checkTimedControllers, 0L, 20L * 5);
|
||||||
|
getServer().getScheduler().runTaskTimer(this, this::enforceCreakingHeartStates, 1L, 1L);
|
||||||
getServer().getScheduler().runTaskTimer(this, this::updateControllerNameActionBar, 0L, 5L);
|
getServer().getScheduler().runTaskTimer(this, this::updateControllerNameActionBar, 0L, 5L);
|
||||||
|
|
||||||
|
// Undo-Actions nach 5 Minuten aufräumen
|
||||||
|
getServer().getScheduler().runTaskTimer(this, this::cleanupOldUndoActions, 0L, 20L * 60 * 5);
|
||||||
|
|
||||||
getLogger().info("ButtonControl v" + getDescription().getVersion() + " wurde erfolgreich aktiviert!");
|
getLogger().info("ButtonControl v" + getDescription().getVersion() + " wurde erfolgreich aktiviert!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
|
stopAllTimedContainerTasks();
|
||||||
|
openGrates.clear();
|
||||||
|
forcedActiveCreakingHearts.clear();
|
||||||
if (dataManager != null) {
|
if (dataManager != null) {
|
||||||
dataManager.shutdown();
|
dataManager.shutdown();
|
||||||
}
|
}
|
||||||
@@ -237,12 +272,19 @@ public class ButtonControl extends JavaPlugin {
|
|||||||
Location tl = parseLocation(ts);
|
Location tl = parseLocation(ts);
|
||||||
if (tl == null) continue;
|
if (tl == null) continue;
|
||||||
Block tb = tl.getBlock();
|
Block tb = tl.getBlock();
|
||||||
if (tb.getType() == Material.REDSTONE_LAMP) {
|
if (isLamp(tb.getType()) && tb.getBlockData() instanceof Lightable) {
|
||||||
Lightable lamp = (Lightable) tb.getBlockData();
|
Lightable lamp = (Lightable) tb.getBlockData();
|
||||||
lamp.setLit(!isDay);
|
lamp.setLit(!isDay);
|
||||||
tb.setBlockData(lamp);
|
tb.setBlockData(lamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Secret Wall: bei Tag öffnen, bei Nacht schließen
|
||||||
|
if (isDay) {
|
||||||
|
triggerSecretWall(buttonId, false);
|
||||||
|
} else {
|
||||||
|
closeSecretWall(buttonId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,13 +300,20 @@ public class ButtonControl extends JavaPlugin {
|
|||||||
* Anzeige: ticksToTime() wandelt in "HH:MM" um (Tag beginnt um 06:00).
|
* Anzeige: ticksToTime() wandelt in "HH:MM" um (Tag beginnt um 06:00).
|
||||||
*/
|
*/
|
||||||
public void checkTimedControllers() {
|
public void checkTimedControllers() {
|
||||||
|
Set<String> activeScheduleButtons = new HashSet<>();
|
||||||
|
|
||||||
for (String controllerLoc : dataManager.getAllPlacedControllers()) {
|
for (String controllerLoc : dataManager.getAllPlacedControllers()) {
|
||||||
String buttonId = dataManager.getButtonIdForPlacedController(controllerLoc);
|
String buttonId = dataManager.getButtonIdForPlacedController(controllerLoc);
|
||||||
if (buttonId == null) continue;
|
if (buttonId == null) continue;
|
||||||
|
|
||||||
long openTime = dataManager.getScheduleOpenTime(buttonId);
|
long openTime = dataManager.getScheduleOpenTime(buttonId);
|
||||||
long closeTime = dataManager.getScheduleCloseTime(buttonId);
|
long closeTime = dataManager.getScheduleCloseTime(buttonId);
|
||||||
if (openTime < 0 || closeTime < 0) continue;
|
if (openTime < 0 || closeTime < 0) {
|
||||||
|
stopTimedContainerTask(buttonId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
activeScheduleButtons.add(buttonId);
|
||||||
|
|
||||||
Location loc = parseLocation(controllerLoc);
|
Location loc = parseLocation(controllerLoc);
|
||||||
if (loc == null) continue;
|
if (loc == null) continue;
|
||||||
@@ -281,14 +330,160 @@ public class ButtonControl extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Boolean lastState = timedControllerLastState.get(controllerLoc);
|
Boolean lastState = timedControllerLastState.get(controllerLoc);
|
||||||
|
List<String> connected = dataManager.getConnectedBlocks(buttonId);
|
||||||
|
if (connected != null && !connected.isEmpty()) {
|
||||||
|
updateTimedContainerAutomation(buttonId, connected, shouldBeOpen);
|
||||||
|
} else {
|
||||||
|
stopTimedContainerTask(buttonId);
|
||||||
|
}
|
||||||
|
|
||||||
if (lastState != null && lastState == shouldBeOpen) continue;
|
if (lastState != null && lastState == shouldBeOpen) continue;
|
||||||
|
|
||||||
timedControllerLastState.put(controllerLoc, shouldBeOpen);
|
timedControllerLastState.put(controllerLoc, shouldBeOpen);
|
||||||
List<String> connected = dataManager.getConnectedBlocks(buttonId);
|
|
||||||
if (connected != null && !connected.isEmpty()) {
|
if (connected != null && !connected.isEmpty()) {
|
||||||
setOpenables(connected, shouldBeOpen);
|
setOpenables(connected, shouldBeOpen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Falls Zeitpläne entfernt wurden, zugehörige Show-Tasks sauber stoppen.
|
||||||
|
List<String> inactiveTaskButtons = new ArrayList<>(timedContainerTasks.keySet());
|
||||||
|
for (String buttonId : inactiveTaskButtons) {
|
||||||
|
if (!activeScheduleButtons.contains(buttonId)) {
|
||||||
|
stopTimedContainerTask(buttonId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTimedContainerAutomation(String buttonId, List<String> connected, boolean activeWindow) {
|
||||||
|
if (!containsTimedContainer(connected)) {
|
||||||
|
stopTimedContainerTask(buttonId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!activeWindow) {
|
||||||
|
stopTimedContainerTask(buttonId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int configuredDelay = dataManager.getScheduleShotDelayTicks(buttonId);
|
||||||
|
int shotDelay = configuredDelay >= 0
|
||||||
|
? Math.max(0, configuredDelay)
|
||||||
|
: Math.max(0, configManager.getConfig().getInt(
|
||||||
|
"timed-container-shot-delay-ticks",
|
||||||
|
configManager.getConfig().getInt("timed-container-interval-ticks", 40)));
|
||||||
|
String configuredMode = normalizeTimedContainerMode(dataManager.getScheduleTriggerMode(buttonId));
|
||||||
|
String taskMode = configuredMode != null
|
||||||
|
? configuredMode
|
||||||
|
: normalizeTimedContainerMode(configManager.getConfig().getString(
|
||||||
|
"timed-container-trigger-mode",
|
||||||
|
TIMED_CONTAINER_MODE_SIMULTANEOUS));
|
||||||
|
int taskPeriod = Math.max(1, shotDelay);
|
||||||
|
|
||||||
|
Integer existingTaskId = timedContainerTasks.get(buttonId);
|
||||||
|
if (existingTaskId != null) {
|
||||||
|
Integer existingDelay = timedContainerTaskShotDelays.get(buttonId);
|
||||||
|
String existingMode = timedContainerTaskModes.get(buttonId);
|
||||||
|
if (existingDelay != null && existingDelay == taskPeriod && taskMode.equals(existingMode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stopTimedContainerTask(buttonId);
|
||||||
|
}
|
||||||
|
|
||||||
|
int taskId = getServer().getScheduler().scheduleSyncRepeatingTask(this, () -> {
|
||||||
|
List<String> latestConnected = dataManager.getConnectedBlocks(buttonId);
|
||||||
|
List<String> timedContainers = getTimedContainerLocations(latestConnected);
|
||||||
|
if (timedContainers.isEmpty()) {
|
||||||
|
stopTimedContainerTask(buttonId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TIMED_CONTAINER_MODE_SEQUENTIAL.equals(taskMode)) {
|
||||||
|
int nextIndex = timedContainerNextIndices.getOrDefault(buttonId, 0);
|
||||||
|
if (nextIndex >= timedContainers.size()) nextIndex = 0;
|
||||||
|
|
||||||
|
String locStr = timedContainers.get(nextIndex);
|
||||||
|
Location l = parseLocation(locStr);
|
||||||
|
if (l != null) {
|
||||||
|
Block b = l.getBlock();
|
||||||
|
if (b.getType() == Material.DISPENSER) {
|
||||||
|
triggerContainer(b, "dispense");
|
||||||
|
} else if (b.getType() == Material.DROPPER) {
|
||||||
|
triggerContainer(b, "drop");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timedContainerNextIndices.put(buttonId, (nextIndex + 1) % timedContainers.size());
|
||||||
|
} else {
|
||||||
|
for (String locStr : timedContainers) {
|
||||||
|
Location l = parseLocation(locStr);
|
||||||
|
if (l == null) continue;
|
||||||
|
|
||||||
|
Block b = l.getBlock();
|
||||||
|
if (b.getType() == Material.DISPENSER) {
|
||||||
|
triggerContainer(b, "dispense");
|
||||||
|
} else if (b.getType() == Material.DROPPER) {
|
||||||
|
triggerContainer(b, "drop");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0L, taskPeriod);
|
||||||
|
|
||||||
|
timedContainerTasks.put(buttonId, taskId);
|
||||||
|
timedContainerTaskShotDelays.put(buttonId, taskPeriod);
|
||||||
|
timedContainerTaskModes.put(buttonId, taskMode);
|
||||||
|
timedContainerNextIndices.put(buttonId, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean containsTimedContainer(List<String> connected) {
|
||||||
|
return !getTimedContainerLocations(connected).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getTimedContainerLocations(List<String> connected) {
|
||||||
|
List<String> timedContainers = new ArrayList<>();
|
||||||
|
for (String locStr : connected) {
|
||||||
|
Location l = parseLocation(locStr);
|
||||||
|
if (l == null) continue;
|
||||||
|
Material m = l.getBlock().getType();
|
||||||
|
if (m == Material.DISPENSER || m == Material.DROPPER) {
|
||||||
|
timedContainers.add(locStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return timedContainers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeTimedContainerMode(String mode) {
|
||||||
|
if (mode == null) return null;
|
||||||
|
String normalized = mode.trim().toLowerCase(Locale.ROOT);
|
||||||
|
if (TIMED_CONTAINER_MODE_SEQUENTIAL.equals(normalized)) return TIMED_CONTAINER_MODE_SEQUENTIAL;
|
||||||
|
return TIMED_CONTAINER_MODE_SIMULTANEOUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopTimedContainerTask(String buttonId) {
|
||||||
|
Integer taskId = timedContainerTasks.remove(buttonId);
|
||||||
|
if (taskId != null) getServer().getScheduler().cancelTask(taskId);
|
||||||
|
timedContainerTaskShotDelays.remove(buttonId);
|
||||||
|
timedContainerTaskModes.remove(buttonId);
|
||||||
|
timedContainerNextIndices.remove(buttonId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopAllTimedContainerTasks() {
|
||||||
|
for (Integer taskId : timedContainerTasks.values()) {
|
||||||
|
getServer().getScheduler().cancelTask(taskId);
|
||||||
|
}
|
||||||
|
timedContainerTasks.clear();
|
||||||
|
timedContainerTaskShotDelays.clear();
|
||||||
|
timedContainerTaskModes.clear();
|
||||||
|
timedContainerNextIndices.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean triggerContainer(Block block, String methodName) {
|
||||||
|
try {
|
||||||
|
Object state = block.getState();
|
||||||
|
java.lang.reflect.Method method = state.getClass().getMethod(methodName);
|
||||||
|
Object result = method.invoke(state);
|
||||||
|
return !(result instanceof Boolean) || (Boolean) result;
|
||||||
|
} catch (ReflectiveOperationException ignored) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
@@ -388,18 +583,23 @@ public class ButtonControl extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<String> connected = dataManager.getConnectedBlocks(buttonId);
|
List<String> connected = dataManager.getConnectedBlocks(buttonId);
|
||||||
if (connected == null || connected.isEmpty()) continue;
|
boolean hasConnected = connected != null && !connected.isEmpty();
|
||||||
|
List<String> secretBlocks = dataManager.getSecretBlocks(buttonId);
|
||||||
|
boolean hasSecret = secretBlocks != null && !secretBlocks.isEmpty();
|
||||||
|
if (!hasConnected && !hasSecret) continue;
|
||||||
|
|
||||||
if (detected) {
|
if (detected) {
|
||||||
if (!activeSensors.contains(controllerLoc)) {
|
if (!activeSensors.contains(controllerLoc)) {
|
||||||
setOpenables(connected, true);
|
if (hasConnected) setOpenables(connected, true);
|
||||||
|
triggerSecretWall(buttonId, false);
|
||||||
activeSensors.add(controllerLoc);
|
activeSensors.add(controllerLoc);
|
||||||
}
|
}
|
||||||
lastMotionDetections.put(controllerLoc, now);
|
lastMotionDetections.put(controllerLoc, now);
|
||||||
} else {
|
} else {
|
||||||
Long last = lastMotionDetections.get(controllerLoc);
|
Long last = lastMotionDetections.get(controllerLoc);
|
||||||
if (last != null && now - last >= delay) {
|
if (last != null && now - last >= delay) {
|
||||||
setOpenables(connected, false);
|
if (hasConnected) setOpenables(connected, false);
|
||||||
|
closeSecretWall(buttonId);
|
||||||
lastMotionDetections.remove(controllerLoc);
|
lastMotionDetections.remove(controllerLoc);
|
||||||
activeSensors.remove(controllerLoc);
|
activeSensors.remove(controllerLoc);
|
||||||
}
|
}
|
||||||
@@ -416,6 +616,8 @@ public class ButtonControl extends JavaPlugin {
|
|||||||
org.bukkit.block.data.Openable o = (org.bukkit.block.data.Openable) tb.getBlockData();
|
org.bukkit.block.data.Openable o = (org.bukkit.block.data.Openable) tb.getBlockData();
|
||||||
o.setOpen(open);
|
o.setOpen(open);
|
||||||
tb.setBlockData(o);
|
tb.setBlockData(o);
|
||||||
|
} else if (isGrate(tb.getType()) || (tb.getType() == Material.AIR && openGrates.containsKey(locStr))) {
|
||||||
|
setGrateOpenState(tb, locStr, open);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -429,7 +631,7 @@ public class ButtonControl extends JavaPlugin {
|
|||||||
if (!command.getName().equalsIgnoreCase("bc")) return false;
|
if (!command.getName().equalsIgnoreCase("bc")) return false;
|
||||||
|
|
||||||
if (args.length == 0) {
|
if (args.length == 0) {
|
||||||
sender.sendMessage("§6[BC] §7/bc <info|reload|note|list|rename|schedule|trust|untrust|public|private>");
|
sender.sendMessage("§6[BC] §7/bc <info|reload|note|list|rename|schedule|trust|untrust|public|private|undo|secret>");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,7 +652,10 @@ public class ButtonControl extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
configManager.reloadConfig();
|
configManager.reloadConfig();
|
||||||
dataManager.reloadData();
|
dataManager.reloadData();
|
||||||
|
stopAllTimedContainerTasks();
|
||||||
timedControllerLastState.clear();
|
timedControllerLastState.clear();
|
||||||
|
openGrates.clear();
|
||||||
|
forcedActiveCreakingHearts.clear();
|
||||||
clearAllControllerActionBars();
|
clearAllControllerActionBars();
|
||||||
sender.sendMessage(configManager.getMessage("konfiguration-neugeladen"));
|
sender.sendMessage(configManager.getMessage("konfiguration-neugeladen"));
|
||||||
return true;
|
return true;
|
||||||
@@ -477,9 +682,13 @@ public class ButtonControl extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
Player player = (Player) sender;
|
Player player = (Player) sender;
|
||||||
|
|
||||||
|
if (sub.equals("secret")) {
|
||||||
|
return handleSecretCommand(player, args);
|
||||||
|
}
|
||||||
|
|
||||||
if (sub.equals("list") || sub.equals("rename") || sub.equals("schedule")
|
if (sub.equals("list") || sub.equals("rename") || sub.equals("schedule")
|
||||||
|| sub.equals("trust") || sub.equals("untrust")
|
|| sub.equals("trust") || sub.equals("untrust")
|
||||||
|| sub.equals("public") || sub.equals("private")) {
|
|| sub.equals("public") || sub.equals("private") || sub.equals("undo")) {
|
||||||
|
|
||||||
Block target = player.getTargetBlockExact(5);
|
Block target = player.getTargetBlockExact(5);
|
||||||
if (!isValidController(target)) {
|
if (!isValidController(target)) {
|
||||||
@@ -502,12 +711,18 @@ public class ButtonControl extends JavaPlugin {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "rename":
|
case "rename":
|
||||||
if (!isOwner && !isAdmin) { player.sendMessage(configManager.getMessage("nur-besitzer-abbauen")); return true; }
|
if (!isOwner && !isAdmin) {
|
||||||
|
player.sendMessage("§c✖ Nur der Besitzer oder Admins können das tun.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (args.length < 2) { player.sendMessage("§7/bc rename <Name>"); return true; }
|
if (args.length < 2) { player.sendMessage("§7/bc rename <Name>"); return true; }
|
||||||
|
String oldRename = dataManager.getControllerName(buttonId);
|
||||||
String newName = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
|
String newName = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
|
||||||
if (newName.length() > 32) { player.sendMessage("§cName zu lang (max. 32 Zeichen)."); return true; }
|
if (newName.length() > 32) { player.sendMessage("§cName zu lang (max. 32 Zeichen)."); return true; }
|
||||||
dataManager.setControllerName(buttonId, newName);
|
String coloredName = ChatColor.translateAlternateColorCodes('&', newName);
|
||||||
player.sendMessage(String.format(configManager.getMessage("controller-umbenannt"), newName));
|
dataManager.setControllerName(buttonId, coloredName);
|
||||||
|
saveUndoAction(buttonId, new UndoAction(UndoAction.Type.RENAME, oldRename, coloredName));
|
||||||
|
player.sendMessage(String.format(configManager.getMessage("controller-umbenannt"), coloredName));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "schedule":
|
case "schedule":
|
||||||
@@ -516,30 +731,61 @@ public class ButtonControl extends JavaPlugin {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "trust":
|
case "trust":
|
||||||
if (!isOwner && !isAdmin) { player.sendMessage(configManager.getMessage("nur-besitzer-abbauen")); return true; }
|
if (!isOwner && !isAdmin) {
|
||||||
|
player.sendMessage("§c✖ Nur der Besitzer oder Admins können das tun.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (args.length < 2) { player.sendMessage("§7/bc trust <Spieler>"); return true; }
|
if (args.length < 2) { player.sendMessage("§7/bc trust <Spieler>"); return true; }
|
||||||
org.bukkit.OfflinePlayer tp = Bukkit.getOfflinePlayer(args[1]);
|
org.bukkit.OfflinePlayer tp = Bukkit.getOfflinePlayer(args[1]);
|
||||||
if (!tp.hasPlayedBefore() && !tp.isOnline()) {
|
if (!tp.hasPlayedBefore() && !tp.isOnline()) {
|
||||||
player.sendMessage(configManager.getMessage("spieler-nicht-gefunden")); return true;
|
player.sendMessage(configManager.getMessage("spieler-nicht-gefunden")); return true;
|
||||||
}
|
}
|
||||||
dataManager.addTrustedPlayer(buttonId, tp.getUniqueId());
|
dataManager.addTrustedPlayer(buttonId, tp.getUniqueId());
|
||||||
|
saveUndoAction(buttonId, new UndoAction(UndoAction.Type.TRUST_ADD, tp.getUniqueId().toString(), null));
|
||||||
player.sendMessage(String.format(configManager.getMessage("trust-hinzugefuegt"), args[1]));
|
player.sendMessage(String.format(configManager.getMessage("trust-hinzugefuegt"), args[1]));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "untrust":
|
case "untrust":
|
||||||
if (!isOwner && !isAdmin) { player.sendMessage(configManager.getMessage("nur-besitzer-abbauen")); return true; }
|
if (!isOwner && !isAdmin) {
|
||||||
|
player.sendMessage("§c✖ Nur der Besitzer oder Admins können das tun.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (args.length < 2) { player.sendMessage("§7/bc untrust <Spieler>"); return true; }
|
if (args.length < 2) { player.sendMessage("§7/bc untrust <Spieler>"); return true; }
|
||||||
dataManager.removeTrustedPlayer(buttonId, Bukkit.getOfflinePlayer(args[1]).getUniqueId());
|
org.bukkit.OfflinePlayer untp = Bukkit.getOfflinePlayer(args[1]);
|
||||||
|
java.util.UUID uuid = untp.getUniqueId();
|
||||||
|
dataManager.removeTrustedPlayer(buttonId, uuid);
|
||||||
|
saveUndoAction(buttonId, new UndoAction(UndoAction.Type.TRUST_REMOVE, uuid.toString(), null));
|
||||||
player.sendMessage(String.format(configManager.getMessage("trust-entfernt"), args[1]));
|
player.sendMessage(String.format(configManager.getMessage("trust-entfernt"), args[1]));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: // public / private
|
default: // public / private
|
||||||
if (!isOwner && !isAdmin) { player.sendMessage(configManager.getMessage("nur-besitzer-abbauen")); return true; }
|
if (!isOwner && !isAdmin) {
|
||||||
|
player.sendMessage("§c✖ Nur der Besitzer oder Admins können das tun.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
boolean pub = sub.equals("public");
|
boolean pub = sub.equals("public");
|
||||||
|
boolean oldStatus = dataManager.isPublic(buttonId);
|
||||||
dataManager.setPublic(buttonId, pub);
|
dataManager.setPublic(buttonId, pub);
|
||||||
|
saveUndoAction(buttonId, new UndoAction(pub ? UndoAction.Type.PUBLIC : UndoAction.Type.PRIVATE,
|
||||||
|
String.valueOf(oldStatus), String.valueOf(pub)));
|
||||||
player.sendMessage(String.format(configManager.getMessage("status-geandert"),
|
player.sendMessage(String.format(configManager.getMessage("status-geandert"),
|
||||||
pub ? "§aÖffentlich" : "§cPrivat"));
|
pub ? "§aÖffentlich" : "§cPrivat"));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "undo":
|
||||||
|
if (lastActions.containsKey(buttonId)) {
|
||||||
|
UndoAction action = lastActions.get(buttonId);
|
||||||
|
boolean canUndo = dataManager.isOwner(buttonId, player.getUniqueId()) || player.hasPermission("buttoncontrol.admin");
|
||||||
|
if (!canUndo) {
|
||||||
|
player.sendMessage("§c✖ Du kannst nur Aktionen deiner eigenen Controller rückgängig machen.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
undoAction(buttonId, action, player);
|
||||||
|
} else {
|
||||||
|
player.sendMessage("§c✖ Keine Aktion zum Rückgängigmachen für diesen Controller.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -609,6 +855,113 @@ public class ButtonControl extends JavaPlugin {
|
|||||||
|| m.name().endsWith("_CARPET");
|
|| m.name().endsWith("_CARPET");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isLamp(Material m) {
|
||||||
|
return m == Material.REDSTONE_LAMP
|
||||||
|
|| "COPPER_BULB".equals(m.name())
|
||||||
|
|| m.name().endsWith("_COPPER_BULB");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGrate(Material m) {
|
||||||
|
return m == Material.IRON_BARS || m.name().endsWith("_GRATE");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isManagedOpenGrateLocation(String locStr) {
|
||||||
|
return openGrates.containsKey(locStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean toggleGrate(Block block) {
|
||||||
|
if (block == null) return null;
|
||||||
|
|
||||||
|
String locStr = toLoc(block);
|
||||||
|
Material type = block.getType();
|
||||||
|
|
||||||
|
if (isGrate(type)) {
|
||||||
|
openGrates.put(locStr, type);
|
||||||
|
block.setType(Material.AIR, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == Material.AIR) {
|
||||||
|
Material original = openGrates.get(locStr);
|
||||||
|
if (original != null) {
|
||||||
|
block.setType(original, false);
|
||||||
|
openGrates.remove(locStr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setGrateOpenState(Block block, String locStr, boolean open) {
|
||||||
|
if (open) {
|
||||||
|
if (isGrate(block.getType())) {
|
||||||
|
openGrates.put(locStr, block.getType());
|
||||||
|
block.setType(Material.AIR, false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Material original = openGrates.get(locStr);
|
||||||
|
if (original != null) {
|
||||||
|
block.setType(original, false);
|
||||||
|
openGrates.remove(locStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCreakingHeart(Material m) {
|
||||||
|
return "CREAKING_HEART".equals(m.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean togglePersistentCreakingHeart(Block block) {
|
||||||
|
if (block == null || !isCreakingHeart(block.getType())) return null;
|
||||||
|
|
||||||
|
String loc = toLoc(block);
|
||||||
|
boolean targetActive = !forcedActiveCreakingHearts.contains(loc);
|
||||||
|
if (!applyCreakingHeartActive(block, targetActive)) return null;
|
||||||
|
|
||||||
|
if (targetActive) forcedActiveCreakingHearts.add(loc);
|
||||||
|
else forcedActiveCreakingHearts.remove(loc);
|
||||||
|
|
||||||
|
return targetActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enforceCreakingHeartStates() {
|
||||||
|
if (forcedActiveCreakingHearts.isEmpty()) return;
|
||||||
|
|
||||||
|
java.util.Iterator<String> it = forcedActiveCreakingHearts.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
String loc = it.next();
|
||||||
|
Location l = parseLocation(loc);
|
||||||
|
if (l == null) {
|
||||||
|
it.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Block block = l.getBlock();
|
||||||
|
if (!isCreakingHeart(block.getType())) {
|
||||||
|
it.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!applyCreakingHeartActive(block, true)) {
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean applyCreakingHeartActive(Block block, boolean active) {
|
||||||
|
org.bukkit.block.data.BlockData data = block.getBlockData();
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Method setActive = data.getClass().getMethod("setActive", boolean.class);
|
||||||
|
setActive.invoke(data, active);
|
||||||
|
block.setBlockData(data);
|
||||||
|
return true;
|
||||||
|
} catch (ReflectiveOperationException ignored) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String toLoc(Block b) {
|
public String toLoc(Block b) {
|
||||||
return b.getWorld().getName() + "," + b.getX() + "," + b.getY() + "," + b.getZ();
|
return b.getWorld().getName() + "," + b.getX() + "," + b.getY() + "," + b.getZ();
|
||||||
}
|
}
|
||||||
@@ -650,4 +1003,453 @@ public class ButtonControl extends JavaPlugin {
|
|||||||
|
|
||||||
public ConfigManager getConfigManager() { return configManager; }
|
public ConfigManager getConfigManager() { return configManager; }
|
||||||
public DataManager getDataManager() { return dataManager; }
|
public DataManager getDataManager() { return dataManager; }
|
||||||
|
|
||||||
|
private boolean handleSecretCommand(Player player, String[] args) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage("§7/bc secret <select|info|add|remove|clear|delay|animation>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String mode = args[1].toLowerCase();
|
||||||
|
String buttonId = resolveSecretController(player);
|
||||||
|
|
||||||
|
if (mode.equals("select")) {
|
||||||
|
Block target = player.getTargetBlockExact(5);
|
||||||
|
if (!isValidController(target)) {
|
||||||
|
player.sendMessage(configManager.getMessage("kein-controller-im-blick"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String targetLoc = toLoc(target);
|
||||||
|
String targetButtonId = dataManager.getButtonIdForLocation(targetLoc);
|
||||||
|
if (targetButtonId == null) {
|
||||||
|
player.sendMessage(configManager.getMessage("keine-bloecke-verbunden"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
boolean isAdmin = player.hasPermission("buttoncontrol.admin");
|
||||||
|
boolean isOwner = dataManager.isOwner(targetButtonId, player.getUniqueId());
|
||||||
|
if (!isOwner && !isAdmin) {
|
||||||
|
player.sendMessage("§c✖ Nur der Besitzer oder Admins können Secret-Türen verwalten.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
selectedSecretController.put(player.getUniqueId(), targetButtonId);
|
||||||
|
player.sendMessage("§6[Secret] §7Controller ausgewählt. Jetzt auf Wandblock schauen + /bc secret add");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonId == null) {
|
||||||
|
player.sendMessage("§c✖ Kein Controller ausgewählt. Schau auf den Controller und nutze §7/bc secret select§c.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isAdmin = player.hasPermission("buttoncontrol.admin");
|
||||||
|
boolean isOwner = dataManager.isOwner(buttonId, player.getUniqueId());
|
||||||
|
if (!isOwner && !isAdmin) {
|
||||||
|
player.sendMessage("§c✖ Nur der Besitzer oder Admins können Secret-Türen verwalten.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.equals("info")) {
|
||||||
|
List<String> sb = dataManager.getSecretBlocks(buttonId);
|
||||||
|
long delayMs = dataManager.getSecretRestoreDelayMs(buttonId);
|
||||||
|
String animation = normalizeSecretAnimation(dataManager.getSecretAnimation(buttonId));
|
||||||
|
player.sendMessage("§6[Secret] §7Blöcke: §f" + sb.size() + " §8| §7Delay: §f" + (delayMs / 1000.0) + "s");
|
||||||
|
player.sendMessage("§6[Secret] §7Animation: §f" + animation);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.equals("clear")) {
|
||||||
|
dataManager.clearSecret(buttonId);
|
||||||
|
closeSecretWall(buttonId);
|
||||||
|
player.sendMessage("§6[Secret] §7Alle Secret-Blöcke entfernt.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.equals("delay")) {
|
||||||
|
if (args.length < 3) {
|
||||||
|
player.sendMessage("§7/bc secret delay <sekunden>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int sec = Integer.parseInt(args[2]);
|
||||||
|
if (sec < 1 || sec > 300) {
|
||||||
|
player.sendMessage("§c✖ Delay muss zwischen 1 und 300 Sekunden liegen.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
dataManager.setSecretRestoreDelayMs(buttonId, sec * 1000L);
|
||||||
|
player.sendMessage("§6[Secret] §7Wiederherstellung: §f" + sec + "s");
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
player.sendMessage("§c✖ Ungültige Zahl.");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.equals("animation")) {
|
||||||
|
if (args.length < 3) {
|
||||||
|
player.sendMessage("§7/bc secret animation <instant|wave|reverse|center>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String animation = normalizeSecretAnimation(args[2]);
|
||||||
|
if (!isValidSecretAnimation(animation)) {
|
||||||
|
player.sendMessage("§c✖ Nutze: instant, wave, reverse oder center");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
dataManager.setSecretAnimation(buttonId, animation);
|
||||||
|
player.sendMessage("§6[Secret] §7Animation gesetzt: §f" + animation);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Block wallTarget = player.getTargetBlockExact(6);
|
||||||
|
if (wallTarget == null || wallTarget.getType() == Material.AIR) {
|
||||||
|
player.sendMessage("§c✖ Schau auf einen Block innerhalb von 6 Blöcken.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String wallLoc = toLoc(wallTarget);
|
||||||
|
List<String> secretBlocks = new ArrayList<>(dataManager.getSecretBlocks(buttonId));
|
||||||
|
|
||||||
|
if (mode.equals("add")) {
|
||||||
|
if (isUnsafeSecretBlock(wallTarget)) {
|
||||||
|
player.sendMessage("§c✖ Dieser Block trägt/enthält einen Controller oder Schalter. Nicht als Secret-Block erlaubt.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (secretBlocks.contains(wallLoc)) {
|
||||||
|
player.sendMessage("§c✖ Dieser Block ist bereits als Secret-Block gesetzt.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
secretBlocks.add(wallLoc);
|
||||||
|
dataManager.setSecretBlocks(buttonId, secretBlocks);
|
||||||
|
player.sendMessage("§6[Secret] §7Block hinzugefügt. §8(" + secretBlocks.size() + ")");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.equals("remove")) {
|
||||||
|
if (!secretBlocks.remove(wallLoc)) {
|
||||||
|
player.sendMessage("§c✖ Dieser Block ist kein Secret-Block.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
dataManager.setSecretBlocks(buttonId, secretBlocks);
|
||||||
|
player.sendMessage("§6[Secret] §7Block entfernt. §8(" + secretBlocks.size() + ")");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.sendMessage("§7/bc secret <select|info|add|remove|clear|delay|animation>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveSecretController(Player player) {
|
||||||
|
Block target = player.getTargetBlockExact(5);
|
||||||
|
if (isValidController(target)) {
|
||||||
|
String id = dataManager.getButtonIdForLocation(toLoc(target));
|
||||||
|
if (id != null) {
|
||||||
|
selectedSecretController.put(player.getUniqueId(), id);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selectedSecretController.get(player.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isUnsafeSecretBlock(Block wallBlock) {
|
||||||
|
String wallLoc = toLoc(wallBlock);
|
||||||
|
for (String controllerLoc : dataManager.getAllPlacedControllers()) {
|
||||||
|
Location l = parseLocation(controllerLoc);
|
||||||
|
if (l == null) continue;
|
||||||
|
Block controller = l.getBlock();
|
||||||
|
|
||||||
|
// Controller selbst nie entfernen
|
||||||
|
if (controllerLoc.equals(wallLoc)) return true;
|
||||||
|
|
||||||
|
// Trägerblock des Controllers nie entfernen
|
||||||
|
Block support = getSupportBlock(controller);
|
||||||
|
if (support != null && toLoc(support).equals(wallLoc)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Block getSupportBlock(Block controller) {
|
||||||
|
Material m = controller.getType();
|
||||||
|
|
||||||
|
// Aufliegende Controller: Teppich / Tageslichtsensor / Tripwire Hook
|
||||||
|
if (m.name().endsWith("_CARPET") || m == Material.DAYLIGHT_DETECTOR || m == Material.TRIPWIRE_HOOK) {
|
||||||
|
return controller.getRelative(BlockFace.DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buttons & wall-attached Blöcke
|
||||||
|
if (controller.getBlockData() instanceof org.bukkit.block.data.FaceAttachable) {
|
||||||
|
org.bukkit.block.data.FaceAttachable fa = (org.bukkit.block.data.FaceAttachable) controller.getBlockData();
|
||||||
|
switch (fa.getAttachedFace()) {
|
||||||
|
case FLOOR:
|
||||||
|
return controller.getRelative(BlockFace.DOWN);
|
||||||
|
case CEILING:
|
||||||
|
return controller.getRelative(BlockFace.UP);
|
||||||
|
case WALL:
|
||||||
|
if (controller.getBlockData() instanceof org.bukkit.block.data.Directional) {
|
||||||
|
org.bukkit.block.data.Directional d = (org.bukkit.block.data.Directional) controller.getBlockData();
|
||||||
|
return controller.getRelative(d.getFacing().getOppositeFace());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schilder
|
||||||
|
if (m.name().endsWith("_SIGN")) {
|
||||||
|
if (m.name().contains("WALL") && controller.getBlockData() instanceof org.bukkit.block.data.Directional) {
|
||||||
|
org.bukkit.block.data.Directional d = (org.bukkit.block.data.Directional) controller.getBlockData();
|
||||||
|
return controller.getRelative(d.getFacing().getOppositeFace());
|
||||||
|
}
|
||||||
|
return controller.getRelative(BlockFace.DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Löst eine Secret Wall aus. autoClose=false: kein automatisches Schließen (für Sensoren). */
|
||||||
|
public boolean triggerSecretWall(String buttonId) {
|
||||||
|
return triggerSecretWall(buttonId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean triggerSecretWall(String buttonId, boolean autoClose) {
|
||||||
|
if (buttonId == null) return false;
|
||||||
|
List<String> secretBlocks = dataManager.getSecretBlocks(buttonId);
|
||||||
|
if (secretBlocks == null || secretBlocks.isEmpty()) return false;
|
||||||
|
if (openSecretWalls.containsKey(buttonId)) return false;
|
||||||
|
|
||||||
|
List<SecretBlockSnapshot> snapshots = new ArrayList<>();
|
||||||
|
for (String locStr : secretBlocks) {
|
||||||
|
Location l = parseLocation(locStr);
|
||||||
|
if (l == null) continue;
|
||||||
|
Block b = l.getBlock();
|
||||||
|
if (b.getType() == Material.AIR) continue;
|
||||||
|
snapshots.add(new SecretBlockSnapshot(locStr, b.getType().name(), b.getBlockData().getAsString(), l.getBlockX(), l.getBlockY(), l.getBlockZ()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshots.isEmpty()) return false;
|
||||||
|
openSecretWalls.put(buttonId, snapshots);
|
||||||
|
|
||||||
|
String animation = normalizeSecretAnimation(dataManager.getSecretAnimation(buttonId));
|
||||||
|
List<SecretBlockSnapshot> openOrder = getOrderedSecretSnapshots(snapshots, animation, true);
|
||||||
|
long openStepTicks = getSecretStepTicks(animation);
|
||||||
|
long[] openDelays = computeDelays(openOrder, animation, openStepTicks);
|
||||||
|
scheduleSecretOpen(openOrder, openDelays);
|
||||||
|
|
||||||
|
if (autoClose) {
|
||||||
|
long delayMs = Math.max(1000L, dataManager.getSecretRestoreDelayMs(buttonId));
|
||||||
|
long delayTicks = Math.max(1L, delayMs / 50L);
|
||||||
|
long openDurationTicks = openDelays.length == 0 ? 0L : openDelays[openDelays.length - 1];
|
||||||
|
getServer().getScheduler().runTaskLater(this,
|
||||||
|
() -> closeSecretWall(buttonId), openDurationTicks + delayTicks);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeSecretWall(String buttonId) {
|
||||||
|
List<SecretBlockSnapshot> snapshots = openSecretWalls.remove(buttonId);
|
||||||
|
if (snapshots == null || snapshots.isEmpty()) return;
|
||||||
|
|
||||||
|
String animation = normalizeSecretAnimation(dataManager.getSecretAnimation(buttonId));
|
||||||
|
List<SecretBlockSnapshot> closeOrder = getOrderedSecretSnapshots(snapshots, animation, false);
|
||||||
|
long closeStepTicks = getSecretStepTicks(animation);
|
||||||
|
long[] closeDelays = computeDelays(closeOrder, animation, closeStepTicks);
|
||||||
|
scheduleSecretClose(closeOrder, closeDelays);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleSecretOpen(List<SecretBlockSnapshot> snapshots, long[] delays) {
|
||||||
|
for (int i = 0; i < snapshots.size(); i++) {
|
||||||
|
SecretBlockSnapshot s = snapshots.get(i);
|
||||||
|
long when = delays[i];
|
||||||
|
getServer().getScheduler().runTaskLater(this, () -> {
|
||||||
|
Location l = parseLocation(s.loc);
|
||||||
|
if (l == null) return;
|
||||||
|
Block b = l.getBlock();
|
||||||
|
if (b.getType() != Material.AIR) {
|
||||||
|
b.setType(Material.AIR, false);
|
||||||
|
}
|
||||||
|
}, when);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleSecretClose(List<SecretBlockSnapshot> snapshots, long[] delays) {
|
||||||
|
for (int i = 0; i < snapshots.size(); i++) {
|
||||||
|
SecretBlockSnapshot s = snapshots.get(i);
|
||||||
|
long when = delays[i];
|
||||||
|
getServer().getScheduler().runTaskLater(this, () -> {
|
||||||
|
Location l = parseLocation(s.loc);
|
||||||
|
if (l == null) return;
|
||||||
|
Block b = l.getBlock();
|
||||||
|
Material m = Material.matchMaterial(s.materialName);
|
||||||
|
if (m == null || m == Material.AIR) return;
|
||||||
|
b.setType(m, false);
|
||||||
|
try {
|
||||||
|
b.setBlockData(Bukkit.createBlockData(s.blockData), false);
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
// Fallback: Material wurde bereits gesetzt
|
||||||
|
}
|
||||||
|
}, when);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SecretBlockSnapshot> getOrderedSecretSnapshots(List<SecretBlockSnapshot> source, String animation, boolean opening) {
|
||||||
|
List<SecretBlockSnapshot> ordered = new ArrayList<>(source);
|
||||||
|
switch (animation) {
|
||||||
|
case "instant":
|
||||||
|
case "wave":
|
||||||
|
if (!opening) {
|
||||||
|
java.util.Collections.reverse(ordered);
|
||||||
|
}
|
||||||
|
return ordered;
|
||||||
|
case "reverse":
|
||||||
|
if (opening) {
|
||||||
|
java.util.Collections.reverse(ordered);
|
||||||
|
}
|
||||||
|
return ordered;
|
||||||
|
case "center":
|
||||||
|
double centerX = 0;
|
||||||
|
double centerY = 0;
|
||||||
|
double centerZ = 0;
|
||||||
|
for (SecretBlockSnapshot s : ordered) {
|
||||||
|
centerX += s.x;
|
||||||
|
centerY += s.y;
|
||||||
|
centerZ += s.z;
|
||||||
|
}
|
||||||
|
centerX /= ordered.size();
|
||||||
|
centerY /= ordered.size();
|
||||||
|
centerZ /= ordered.size();
|
||||||
|
final double cx = centerX;
|
||||||
|
final double cy = centerY;
|
||||||
|
final double cz = centerZ;
|
||||||
|
ordered.sort((a, b) -> Double.compare(distanceSquared(a, cx, cy, cz), distanceSquared(b, cx, cy, cz)));
|
||||||
|
if (!opening) {
|
||||||
|
java.util.Collections.reverse(ordered);
|
||||||
|
}
|
||||||
|
return ordered;
|
||||||
|
default:
|
||||||
|
return ordered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long[] computeDelays(List<SecretBlockSnapshot> ordered, String animation, long stepTicks) {
|
||||||
|
long[] delays = new long[ordered.size()];
|
||||||
|
if (!animation.equals("center")) {
|
||||||
|
for (int i = 0; i < ordered.size(); i++) {
|
||||||
|
delays[i] = i * stepTicks;
|
||||||
|
}
|
||||||
|
return delays;
|
||||||
|
}
|
||||||
|
// Für center: Blöcke im gleichen Abstandsring bekommen denselben Tick
|
||||||
|
double cx = 0, cy = 0, cz = 0;
|
||||||
|
for (SecretBlockSnapshot s : ordered) { cx += s.x; cy += s.y; cz += s.z; }
|
||||||
|
cx /= ordered.size(); cy /= ordered.size(); cz /= ordered.size();
|
||||||
|
final double fcx = cx, fcy = cy, fcz = cz;
|
||||||
|
double prev = -1;
|
||||||
|
int rank = -1;
|
||||||
|
for (int i = 0; i < ordered.size(); i++) {
|
||||||
|
double d = distanceSquared(ordered.get(i), fcx, fcy, fcz);
|
||||||
|
if (i == 0 || Math.abs(d - prev) > 0.01) {
|
||||||
|
rank++;
|
||||||
|
prev = d;
|
||||||
|
}
|
||||||
|
delays[i] = rank * stepTicks;
|
||||||
|
}
|
||||||
|
return delays;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double distanceSquared(SecretBlockSnapshot s, double centerX, double centerY, double centerZ) {
|
||||||
|
double dx = s.x - centerX;
|
||||||
|
double dy = s.y - centerY;
|
||||||
|
double dz = s.z - centerZ;
|
||||||
|
return dx * dx + dy * dy + dz * dz;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getSecretStepTicks(String animation) {
|
||||||
|
return animation.equals("instant") ? 0L : 2L;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidSecretAnimation(String animation) {
|
||||||
|
return animation.equals("instant") || animation.equals("wave")
|
||||||
|
|| animation.equals("reverse") || animation.equals("center");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeSecretAnimation(String animation) {
|
||||||
|
if (animation == null) return "wave";
|
||||||
|
return animation.trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// Undo-System
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void saveUndoAction(String buttonId, UndoAction action) {
|
||||||
|
lastActions.put(buttonId, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void undoAction(String buttonId, UndoAction action, Player player) {
|
||||||
|
switch (action.type) {
|
||||||
|
case RENAME:
|
||||||
|
dataManager.setControllerName(buttonId, (String) action.oldValue);
|
||||||
|
player.sendMessage("§a✔ Rename rückgängig gemacht.");
|
||||||
|
break;
|
||||||
|
case TRUST_ADD:
|
||||||
|
dataManager.removeTrustedPlayer(buttonId, java.util.UUID.fromString((String) action.oldValue));
|
||||||
|
player.sendMessage("§a✔ Trust-Hinzufügung rückgängig gemacht.");
|
||||||
|
break;
|
||||||
|
case TRUST_REMOVE:
|
||||||
|
dataManager.addTrustedPlayer(buttonId, java.util.UUID.fromString((String) action.oldValue));
|
||||||
|
player.sendMessage("§a✔ Trust-Entfernung rückgängig gemacht.");
|
||||||
|
break;
|
||||||
|
case PUBLIC:
|
||||||
|
case PRIVATE:
|
||||||
|
boolean wasPublic = Boolean.parseBoolean((String) action.oldValue);
|
||||||
|
dataManager.setPublic(buttonId, wasPublic);
|
||||||
|
player.sendMessage("§a✔ Status rückgängig gemacht: " + (wasPublic ? "§aÖffentlich" : "§cPrivat"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lastActions.remove(buttonId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanupOldUndoActions() {
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
long timeout = 5 * 60 * 1000; // 5 Minuten
|
||||||
|
lastActions.entrySet().removeIf(entry ->
|
||||||
|
currentTime - entry.getValue().timestamp > timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// Undo Action Klasse
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
public static class UndoAction {
|
||||||
|
public enum Type { RENAME, TRUST_ADD, TRUST_REMOVE, PUBLIC, PRIVATE }
|
||||||
|
|
||||||
|
public Type type;
|
||||||
|
public Object oldValue;
|
||||||
|
public Object newValue;
|
||||||
|
public long timestamp;
|
||||||
|
|
||||||
|
public UndoAction(Type type, Object oldValue, Object newValue) {
|
||||||
|
this.type = type;
|
||||||
|
this.oldValue = oldValue;
|
||||||
|
this.newValue = newValue;
|
||||||
|
this.timestamp = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SecretBlockSnapshot {
|
||||||
|
public final String loc;
|
||||||
|
public final String materialName;
|
||||||
|
public final String blockData;
|
||||||
|
public final int x;
|
||||||
|
public final int y;
|
||||||
|
public final int z;
|
||||||
|
|
||||||
|
public SecretBlockSnapshot(String loc, String materialName, String blockData, int x, int y, int z) {
|
||||||
|
this.loc = loc;
|
||||||
|
this.materialName = materialName;
|
||||||
|
this.blockData = blockData;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -83,9 +83,14 @@ public class ButtonListener implements Listener {
|
|||||||
|
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
List<String> connectedBlocks = dataManager.getConnectedBlocks(buttonId);
|
List<String> connectedBlocks = dataManager.getConnectedBlocks(buttonId);
|
||||||
if (connectedBlocks != null && !connectedBlocks.isEmpty()) {
|
boolean hasConnectedBlocks = connectedBlocks != null && !connectedBlocks.isEmpty();
|
||||||
|
boolean secretTriggered = plugin.triggerSecretWall(buttonId);
|
||||||
|
|
||||||
|
if (hasConnectedBlocks) {
|
||||||
toggleConnectedBlocks(player, playerUUID, connectedBlocks);
|
toggleConnectedBlocks(player, playerUUID, connectedBlocks);
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (!hasConnectedBlocks && !secretTriggered) {
|
||||||
player.sendMessage(configManager.getMessage("keine-bloecke-verbunden"));
|
player.sendMessage(configManager.getMessage("keine-bloecke-verbunden"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,8 +207,12 @@ public class ButtonListener implements Listener {
|
|||||||
boolean anyIronDoorOpened = false, anyIronDoorClosed = false;
|
boolean anyIronDoorOpened = false, anyIronDoorClosed = false;
|
||||||
boolean anyIronTrapOpened = false, anyIronTrapClosed = false;
|
boolean anyIronTrapOpened = false, anyIronTrapClosed = false;
|
||||||
boolean anyLampOn = false, anyLampOff = false;
|
boolean anyLampOn = false, anyLampOff = false;
|
||||||
|
boolean anyGrateOpened = false, anyGrateClosed = false;
|
||||||
|
boolean anyCreakingHeartOn = false, anyCreakingHeartOff = false;
|
||||||
boolean anyNoteBlockPlayed = false;
|
boolean anyNoteBlockPlayed = false;
|
||||||
boolean anyBellPlayed = false;
|
boolean anyBellPlayed = false;
|
||||||
|
boolean anyDispenserTriggered = false;
|
||||||
|
boolean anyDropperTriggered = false;
|
||||||
|
|
||||||
boolean soundsEnabled = configManager.getConfig().getBoolean("sounds.enabled", true);
|
boolean soundsEnabled = configManager.getConfig().getBoolean("sounds.enabled", true);
|
||||||
|
|
||||||
@@ -268,18 +277,28 @@ public class ButtonListener implements Listener {
|
|||||||
else { if (!wasOpen) anyTrapOpened = true; else anyTrapClosed = true; }
|
else { if (!wasOpen) anyTrapOpened = true; else anyTrapClosed = true; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ── Redstone-Lampe ────────────────────────────────────────────
|
// ── Lampen (Redstone + Kupferlampen) ─────────────────────────
|
||||||
else if (mat == Material.REDSTONE_LAMP) {
|
else if (isLamp(mat)) {
|
||||||
Lightable lamp = (Lightable) targetBlock.getBlockData();
|
if (targetBlock.getBlockData() instanceof Lightable) {
|
||||||
boolean wasLit = lamp.isLit();
|
Lightable lamp = (Lightable) targetBlock.getBlockData();
|
||||||
lamp.setLit(!wasLit);
|
boolean wasLit = lamp.isLit();
|
||||||
targetBlock.setBlockData(lamp);
|
lamp.setLit(!wasLit);
|
||||||
if (soundsEnabled) {
|
targetBlock.setBlockData(lamp);
|
||||||
playConfigSound(location,
|
if (soundsEnabled) {
|
||||||
wasLit ? "sounds.lamp-off" : "sounds.lamp-on",
|
playConfigSound(location,
|
||||||
"BLOCK_LEVER_CLICK");
|
wasLit ? "sounds.lamp-off" : "sounds.lamp-on",
|
||||||
|
"BLOCK_LEVER_CLICK");
|
||||||
|
}
|
||||||
|
if (!wasLit) anyLampOn = true; else anyLampOff = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ── Gitter (alle *_GRATE + Eisenstangen) ─────────────────────
|
||||||
|
else if (plugin.isGrate(mat) || (mat == Material.AIR && plugin.isManagedOpenGrateLocation(locStr))) {
|
||||||
|
Boolean nowOpen = plugin.toggleGrate(targetBlock);
|
||||||
|
if (nowOpen != null) {
|
||||||
|
if (nowOpen) anyGrateOpened = true;
|
||||||
|
else anyGrateClosed = true;
|
||||||
}
|
}
|
||||||
if (!wasLit) anyLampOn = true; else anyLampOff = true;
|
|
||||||
}
|
}
|
||||||
// ── Notenblock ────────────────────────────────────────────────
|
// ── Notenblock ────────────────────────────────────────────────
|
||||||
else if (mat == Material.NOTE_BLOCK) {
|
else if (mat == Material.NOTE_BLOCK) {
|
||||||
@@ -294,6 +313,27 @@ public class ButtonListener implements Listener {
|
|||||||
targetBlock.getWorld().playSound(location, Sound.BLOCK_BELL_USE, 3.0f, 1.0f);
|
targetBlock.getWorld().playSound(location, Sound.BLOCK_BELL_USE, 3.0f, 1.0f);
|
||||||
anyBellPlayed = true;
|
anyBellPlayed = true;
|
||||||
}
|
}
|
||||||
|
// ── Spender / Werfer ──────────────────────────────────────────
|
||||||
|
else if (mat == Material.DISPENSER) {
|
||||||
|
if (triggerContainer(targetBlock, "dispense")) {
|
||||||
|
targetBlock.getWorld().playSound(location, Sound.BLOCK_DISPENSER_DISPENSE, 1.0f, 1.0f);
|
||||||
|
anyDispenserTriggered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mat == Material.DROPPER) {
|
||||||
|
if (triggerContainer(targetBlock, "drop")) {
|
||||||
|
targetBlock.getWorld().playSound(location, Sound.BLOCK_DISPENSER_DISPENSE, 1.0f, 1.0f);
|
||||||
|
anyDropperTriggered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ── Creaking Heart ────────────────────────────────────────────
|
||||||
|
else if (isCreakingHeart(mat)) {
|
||||||
|
Boolean nowActive = plugin.togglePersistentCreakingHeart(targetBlock);
|
||||||
|
if (nowActive != null) {
|
||||||
|
if (nowActive) anyCreakingHeartOn = true;
|
||||||
|
else anyCreakingHeartOff = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Feedback-Nachrichten
|
// Feedback-Nachrichten
|
||||||
@@ -309,8 +349,14 @@ public class ButtonListener implements Listener {
|
|||||||
if (anyTrapClosed) player.sendMessage(configManager.getMessage("fallturen-geschlossen"));
|
if (anyTrapClosed) player.sendMessage(configManager.getMessage("fallturen-geschlossen"));
|
||||||
if (anyLampOn) player.sendMessage(configManager.getMessage("lampen-eingeschaltet"));
|
if (anyLampOn) player.sendMessage(configManager.getMessage("lampen-eingeschaltet"));
|
||||||
if (anyLampOff) player.sendMessage(configManager.getMessage("lampen-ausgeschaltet"));
|
if (anyLampOff) player.sendMessage(configManager.getMessage("lampen-ausgeschaltet"));
|
||||||
|
if (anyGrateOpened) player.sendMessage(configManager.getMessage("gitter-geoeffnet"));
|
||||||
|
if (anyGrateClosed) player.sendMessage(configManager.getMessage("gitter-geschlossen"));
|
||||||
|
if (anyCreakingHeartOn) player.sendMessage(configManager.getMessage("creaking-heart-aktiviert"));
|
||||||
|
if (anyCreakingHeartOff) player.sendMessage(configManager.getMessage("creaking-heart-deaktiviert"));
|
||||||
if (anyNoteBlockPlayed) player.sendMessage(configManager.getMessage("notenblock-ausgeloest"));
|
if (anyNoteBlockPlayed) player.sendMessage(configManager.getMessage("notenblock-ausgeloest"));
|
||||||
if (anyBellPlayed) player.sendMessage(configManager.getMessage("glocke-gelaeutet"));
|
if (anyBellPlayed) player.sendMessage(configManager.getMessage("glocke-gelaeutet"));
|
||||||
|
if (anyDispenserTriggered) player.sendMessage(configManager.getMessage("spender-ausgeloest"));
|
||||||
|
if (anyDropperTriggered) player.sendMessage(configManager.getMessage("werfer-ausgeloest"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -330,6 +376,17 @@ public class ButtonListener implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean triggerContainer(Block block, String methodName) {
|
||||||
|
try {
|
||||||
|
Object state = block.getState();
|
||||||
|
java.lang.reflect.Method method = state.getClass().getMethod(methodName);
|
||||||
|
Object result = method.invoke(state);
|
||||||
|
return !(result instanceof Boolean) || (Boolean) result;
|
||||||
|
} catch (ReflectiveOperationException ignored) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Limits
|
// Limits
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
@@ -360,8 +417,8 @@ public class ButtonListener implements Listener {
|
|||||||
>= configManager.getMaxTrapdoors()) {
|
>= configManager.getMaxTrapdoors()) {
|
||||||
player.sendMessage(configManager.getMessage("max-fallturen-erreicht")); return false;
|
player.sendMessage(configManager.getMessage("max-fallturen-erreicht")); return false;
|
||||||
}
|
}
|
||||||
} else if (type == Material.REDSTONE_LAMP) {
|
} else if (isLamp(type)) {
|
||||||
if (connected.stream().filter(l -> getMaterialAt(l) == Material.REDSTONE_LAMP).count()
|
if (connected.stream().filter(l -> isLamp(getMaterialAt(l))).count()
|
||||||
>= configManager.getMaxLamps()) {
|
>= configManager.getMaxLamps()) {
|
||||||
player.sendMessage(configManager.getMessage("max-lampen-erreicht")); return false;
|
player.sendMessage(configManager.getMessage("max-lampen-erreicht")); return false;
|
||||||
}
|
}
|
||||||
@@ -375,6 +432,16 @@ public class ButtonListener implements Listener {
|
|||||||
>= configManager.getMaxBells()) {
|
>= configManager.getMaxBells()) {
|
||||||
player.sendMessage(configManager.getMessage("max-glocken-erreicht")); return false;
|
player.sendMessage(configManager.getMessage("max-glocken-erreicht")); return false;
|
||||||
}
|
}
|
||||||
|
} else if (type == Material.DISPENSER) {
|
||||||
|
if (connected.stream().filter(l -> getMaterialAt(l) == Material.DISPENSER).count()
|
||||||
|
>= configManager.getMaxDispensers()) {
|
||||||
|
player.sendMessage(configManager.getMessage("max-spender-erreicht")); return false;
|
||||||
|
}
|
||||||
|
} else if (type == Material.DROPPER) {
|
||||||
|
if (connected.stream().filter(l -> getMaterialAt(l) == Material.DROPPER).count()
|
||||||
|
>= configManager.getMaxDroppers()) {
|
||||||
|
player.sendMessage(configManager.getMessage("max-werfer-erreicht")); return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -415,11 +482,20 @@ public class ButtonListener implements Listener {
|
|||||||
private boolean isDoor(Material m) { return m.name().endsWith("_DOOR") && m != Material.IRON_DOOR; }
|
private boolean isDoor(Material m) { return m.name().endsWith("_DOOR") && m != Material.IRON_DOOR; }
|
||||||
private boolean isGate(Material m) { return m.name().endsWith("_FENCE_GATE"); }
|
private boolean isGate(Material m) { return m.name().endsWith("_FENCE_GATE"); }
|
||||||
private boolean isTrapdoor(Material m) { return m.name().endsWith("_TRAPDOOR") && m != Material.IRON_TRAPDOOR; }
|
private boolean isTrapdoor(Material m) { return m.name().endsWith("_TRAPDOOR") && m != Material.IRON_TRAPDOOR; }
|
||||||
|
private boolean isLamp(Material m) {
|
||||||
|
return m == Material.REDSTONE_LAMP
|
||||||
|
|| "COPPER_BULB".equals(m.name())
|
||||||
|
|| m.name().endsWith("_COPPER_BULB");
|
||||||
|
}
|
||||||
|
private boolean isCreakingHeart(Material m) { return "CREAKING_HEART".equals(m.name()); }
|
||||||
|
|
||||||
private boolean isInteractableTarget(Material m) {
|
private boolean isInteractableTarget(Material m) {
|
||||||
return isDoor(m) || isGate(m) || isTrapdoor(m)
|
return isDoor(m) || isGate(m) || isTrapdoor(m)
|
||||||
|| m == Material.IRON_DOOR || m == Material.IRON_TRAPDOOR
|
|| m == Material.IRON_DOOR || m == Material.IRON_TRAPDOOR
|
||||||
|| m == Material.REDSTONE_LAMP || m == Material.NOTE_BLOCK || m == Material.BELL;
|
|| isLamp(m) || plugin.isGrate(m)
|
||||||
|
|| m == Material.NOTE_BLOCK || m == Material.BELL
|
||||||
|
|| m == Material.DISPENSER || m == Material.DROPPER
|
||||||
|
|| isCreakingHeart(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Material getMaterialAt(String locString) {
|
private Material getMaterialAt(String locString) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class ButtonTabCompleter implements TabCompleter {
|
|||||||
|
|
||||||
private final List<String> commands = Arrays.asList(
|
private final List<String> commands = Arrays.asList(
|
||||||
"info", "reload", "note", "list", "rename", "schedule",
|
"info", "reload", "note", "list", "rename", "schedule",
|
||||||
"trust", "untrust", "public", "private"
|
"trust", "untrust", "public", "private", "undo", "secret"
|
||||||
);
|
);
|
||||||
|
|
||||||
private final List<String> instruments = Arrays.asList(
|
private final List<String> instruments = Arrays.asList(
|
||||||
@@ -44,6 +44,26 @@ public class ButtonTabCompleter implements TabCompleter {
|
|||||||
case "rename":
|
case "rename":
|
||||||
completions.add("<Name>");
|
completions.add("<Name>");
|
||||||
break;
|
break;
|
||||||
|
case "secret":
|
||||||
|
StringUtil.copyPartialMatches(args[1], Arrays.asList("select", "info", "add", "remove", "clear", "delay", "animation"), completions);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (args.length == 3 && args[0].equalsIgnoreCase("secret") && args[1].equalsIgnoreCase("delay")) {
|
||||||
|
completions.add("3");
|
||||||
|
completions.add("5");
|
||||||
|
completions.add("10");
|
||||||
|
completions.add("30");
|
||||||
|
completions.add("60");
|
||||||
|
} else if (args.length == 3 && args[0].equalsIgnoreCase("secret") && args[1].equalsIgnoreCase("animation")) {
|
||||||
|
completions.add("instant");
|
||||||
|
completions.add("wave");
|
||||||
|
completions.add("reverse");
|
||||||
|
completions.add("center");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 2 && args[0].equalsIgnoreCase("secret")) {
|
||||||
|
if (completions.isEmpty()) {
|
||||||
|
StringUtil.copyPartialMatches(args[1], Arrays.asList("select", "info", "add", "remove", "clear", "delay", "animation"), completions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,12 +68,15 @@ public class ConfigManager {
|
|||||||
def(config, "max-gates", 20);
|
def(config, "max-gates", 20);
|
||||||
def(config, "max-trapdoors", 20);
|
def(config, "max-trapdoors", 20);
|
||||||
def(config, "max-bells", 5);
|
def(config, "max-bells", 5);
|
||||||
|
def(config, "max-dispensers", 20);
|
||||||
|
def(config, "max-droppers", 20);
|
||||||
def(config, "default-note", "PIANO");
|
def(config, "default-note", "PIANO");
|
||||||
def(config, "double-note-enabled", true);
|
def(config, "double-note-enabled", true);
|
||||||
def(config, "double-note-delay-ms", 1000);
|
def(config, "double-note-delay-ms", 1000);
|
||||||
def(config, "motion-detection-radius", 5.0);
|
def(config, "motion-detection-radius", 5.0);
|
||||||
def(config, "motion-close-delay-ms", 5000);
|
def(config, "motion-close-delay-ms", 5000);
|
||||||
def(config, "motion-trigger-cooldown-ms", 2000);
|
def(config, "motion-trigger-cooldown-ms", 2000);
|
||||||
|
def(config, "timed-container-interval-ticks", 40);
|
||||||
|
|
||||||
// Optionales MySQL-Backend
|
// Optionales MySQL-Backend
|
||||||
def(config, "mysql.enabled", false);
|
def(config, "mysql.enabled", false);
|
||||||
@@ -122,9 +125,20 @@ public class ConfigManager {
|
|||||||
def(lang, "lampen-eingeschaltet", "§aLampen wurden eingeschaltet.");
|
def(lang, "lampen-eingeschaltet", "§aLampen wurden eingeschaltet.");
|
||||||
def(lang, "lampen-ausgeschaltet", "§cLampen wurden ausgeschaltet.");
|
def(lang, "lampen-ausgeschaltet", "§cLampen wurden ausgeschaltet.");
|
||||||
def(lang, "max-lampen-erreicht", "§cMaximale Anzahl an Lampen erreicht.");
|
def(lang, "max-lampen-erreicht", "§cMaximale Anzahl an Lampen erreicht.");
|
||||||
|
// Creaking Heart
|
||||||
|
def(lang, "creaking-heart-aktiviert", "§aKnarrherz wurde aktiviert.");
|
||||||
|
def(lang, "creaking-heart-deaktiviert", "§cKnarrherz wurde deaktiviert.");
|
||||||
|
// Gitter
|
||||||
|
def(lang, "gitter-geoeffnet", "§aGitter wurden geöffnet.");
|
||||||
|
def(lang, "gitter-geschlossen", "§cGitter wurden geschlossen.");
|
||||||
// Glocken
|
// Glocken
|
||||||
def(lang, "glocke-gelaeutet", "§aGlocke wurde geläutet.");
|
def(lang, "glocke-gelaeutet", "§aGlocke wurde geläutet.");
|
||||||
def(lang, "max-glocken-erreicht", "§cMaximale Anzahl an Glocken erreicht.");
|
def(lang, "max-glocken-erreicht", "§cMaximale Anzahl an Glocken erreicht.");
|
||||||
|
// Spender / Werfer
|
||||||
|
def(lang, "spender-ausgeloest", "§aSpender wurden ausgelöst.");
|
||||||
|
def(lang, "werfer-ausgeloest", "§aWerfer wurden ausgelöst.");
|
||||||
|
def(lang, "max-spender-erreicht", "§cMaximale Anzahl an Spendern erreicht.");
|
||||||
|
def(lang, "max-werfer-erreicht", "§cMaximale Anzahl an Werfern erreicht.");
|
||||||
// Notenblöcke
|
// Notenblöcke
|
||||||
def(lang, "notenblock-ausgeloest", "§aNotenblock-Klingel wurde ausgelöst.");
|
def(lang, "notenblock-ausgeloest", "§aNotenblock-Klingel wurde ausgelöst.");
|
||||||
def(lang, "instrument-gesetzt", "§aDein Instrument wurde auf %s gesetzt.");
|
def(lang, "instrument-gesetzt", "§aDein Instrument wurde auf %s gesetzt.");
|
||||||
@@ -179,6 +193,8 @@ public class ConfigManager {
|
|||||||
public int getMaxGates() { return config.getInt("max-gates", 20); }
|
public int getMaxGates() { return config.getInt("max-gates", 20); }
|
||||||
public int getMaxTrapdoors() { return config.getInt("max-trapdoors", 20); }
|
public int getMaxTrapdoors() { return config.getInt("max-trapdoors", 20); }
|
||||||
public int getMaxBells() { return config.getInt("max-bells", 5); }
|
public int getMaxBells() { return config.getInt("max-bells", 5); }
|
||||||
|
public int getMaxDispensers() { return config.getInt("max-dispensers", 20); }
|
||||||
|
public int getMaxDroppers() { return config.getInt("max-droppers", 20); }
|
||||||
|
|
||||||
public String getMessage(String key) {
|
public String getMessage(String key) {
|
||||||
return lang.getString(key, "§cNachricht fehlt: " + key);
|
return lang.getString(key, "§cNachricht fehlt: " + key);
|
||||||
|
|||||||
@@ -96,24 +96,29 @@ public class DataManager {
|
|||||||
}
|
}
|
||||||
// buttonId vor dem Löschen des Location-Eintrags ermitteln
|
// buttonId vor dem Löschen des Location-Eintrags ermitteln
|
||||||
String buttonId = getButtonIdForPlacedController(location);
|
String buttonId = getButtonIdForPlacedController(location);
|
||||||
String ownerUUID = null;
|
|
||||||
if (data.getConfigurationSection("players") != null) {
|
if (data.getConfigurationSection("players") != null) {
|
||||||
for (String uuid : data.getConfigurationSection("players").getKeys(false)) {
|
for (String uuid : data.getConfigurationSection("players").getKeys(false)) {
|
||||||
String path = "players." + uuid + ".placed-controllers." + location;
|
String path = "players." + uuid + ".placed-controllers." + location;
|
||||||
if (data.contains(path)) {
|
if (data.contains(path)) {
|
||||||
data.set(path, null);
|
data.set(path, null);
|
||||||
ownerUUID = uuid;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Alle zugehörigen Daten (Name, Status, Trust, Zeitplan, Verbindungen) bereinigen
|
// Alle zugehörigen Daten (Name, Status, Trust, Zeitplan, Verbindungen, Secret) bereinigen
|
||||||
if (buttonId != null) {
|
if (buttonId != null) {
|
||||||
data.set("names." + buttonId, null);
|
data.set("names." + buttonId, null);
|
||||||
data.set("public-status." + buttonId, null);
|
data.set("public-status." + buttonId, null);
|
||||||
data.set("trust." + buttonId, null);
|
data.set("trust." + buttonId, null);
|
||||||
data.set("schedules." + buttonId, null);
|
data.set("schedules." + buttonId, null);
|
||||||
if (ownerUUID != null) {
|
|
||||||
data.set("players." + ownerUUID + ".buttons." + buttonId, null);
|
// Secret-Wall-Daten ebenfalls entfernen
|
||||||
|
data.set("secret-walls." + buttonId, null);
|
||||||
|
|
||||||
|
// Sicherheitshalber bei ALLEN Spielern den Button-Eintrag löschen
|
||||||
|
if (data.getConfigurationSection("players") != null) {
|
||||||
|
for (String uuid : data.getConfigurationSection("players").getKeys(false)) {
|
||||||
|
data.set("players." + uuid + ".buttons." + buttonId, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeMotionSensorSettings(location);
|
removeMotionSensorSettings(location);
|
||||||
@@ -238,6 +243,34 @@ public class DataManager {
|
|||||||
return data.getLong("schedules." + buttonId + ".close-time", -1);
|
return data.getLong("schedules." + buttonId + ".close-time", -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setScheduleShotDelayTicks(String buttonId, int ticks) {
|
||||||
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.setScheduleShotDelayTicks(buttonId, ticks);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.set("schedules." + buttonId + ".shot-delay-ticks", ticks);
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getScheduleShotDelayTicks(String buttonId) {
|
||||||
|
if (mySQLStorage != null) return mySQLStorage.getScheduleShotDelayTicks(buttonId);
|
||||||
|
return data.getInt("schedules." + buttonId + ".shot-delay-ticks", -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScheduleTriggerMode(String buttonId, String mode) {
|
||||||
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.setScheduleTriggerMode(buttonId, mode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.set("schedules." + buttonId + ".trigger-mode", mode);
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScheduleTriggerMode(String buttonId) {
|
||||||
|
if (mySQLStorage != null) return mySQLStorage.getScheduleTriggerMode(buttonId);
|
||||||
|
return data.getString("schedules." + buttonId + ".trigger-mode");
|
||||||
|
}
|
||||||
|
|
||||||
/** Entfernt den kompletten Zeitplan für einen Controller. */
|
/** Entfernt den kompletten Zeitplan für einen Controller. */
|
||||||
public void clearSchedule(String buttonId) {
|
public void clearSchedule(String buttonId) {
|
||||||
if (mySQLStorage != null) {
|
if (mySQLStorage != null) {
|
||||||
@@ -349,6 +382,61 @@ public class DataManager {
|
|||||||
saveData();
|
saveData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Secret-Wall (Geheimwand)
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
public void setSecretBlocks(String buttonId, List<String> blocks) {
|
||||||
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.setSecretBlocks(buttonId, blocks);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.set("secret-walls." + buttonId + ".blocks", blocks);
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getSecretBlocks(String buttonId) {
|
||||||
|
if (mySQLStorage != null) return mySQLStorage.getSecretBlocks(buttonId);
|
||||||
|
return data.getStringList("secret-walls." + buttonId + ".blocks");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecretRestoreDelayMs(String buttonId, long delayMs) {
|
||||||
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.setSecretRestoreDelayMs(buttonId, delayMs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.set("secret-walls." + buttonId + ".delay-ms", delayMs);
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSecretRestoreDelayMs(String buttonId) {
|
||||||
|
if (mySQLStorage != null) return mySQLStorage.getSecretRestoreDelayMs(buttonId);
|
||||||
|
return data.getLong("secret-walls." + buttonId + ".delay-ms", 5000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecretAnimation(String buttonId, String animation) {
|
||||||
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.setSecretAnimation(buttonId, animation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.set("secret-walls." + buttonId + ".animation", animation);
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSecretAnimation(String buttonId) {
|
||||||
|
if (mySQLStorage != null) return mySQLStorage.getSecretAnimation(buttonId);
|
||||||
|
return data.getString("secret-walls." + buttonId + ".animation", "wave");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearSecret(String buttonId) {
|
||||||
|
if (mySQLStorage != null) {
|
||||||
|
mySQLStorage.clearSecret(buttonId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.set("secret-walls." + buttonId, null);
|
||||||
|
saveData();
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Speichern – asynchron
|
// Speichern – asynchron
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ public class MySQLStorage {
|
|||||||
plugin.getLogger().info("MySQL aktiviert.");
|
plugin.getLogger().info("MySQL aktiviert.");
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
plugin.getLogger().warning("MySQL konnte nicht initialisiert werden, verwende data.yml: " + e.getMessage());
|
plugin.getLogger().warning("MySQL Verbindung fehlgeschlagen - verwende data.yml für Datenspeicherung.");
|
||||||
|
plugin.getLogger().fine("Fehlerdetails: " + e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +94,9 @@ public class MySQLStorage {
|
|||||||
st.executeUpdate("CREATE TABLE IF NOT EXISTS bc_schedules ("
|
st.executeUpdate("CREATE TABLE IF NOT EXISTS bc_schedules ("
|
||||||
+ "button_id VARCHAR(64) PRIMARY KEY,"
|
+ "button_id VARCHAR(64) PRIMARY KEY,"
|
||||||
+ "open_time BIGINT,"
|
+ "open_time BIGINT,"
|
||||||
+ "close_time BIGINT"
|
+ "close_time BIGINT,"
|
||||||
|
+ "shot_delay_ticks INT,"
|
||||||
|
+ "trigger_mode VARCHAR(16)"
|
||||||
+ ")");
|
+ ")");
|
||||||
|
|
||||||
st.executeUpdate("CREATE TABLE IF NOT EXISTS bc_trust ("
|
st.executeUpdate("CREATE TABLE IF NOT EXISTS bc_trust ("
|
||||||
@@ -117,6 +120,27 @@ public class MySQLStorage {
|
|||||||
+ "radius DOUBLE,"
|
+ "radius DOUBLE,"
|
||||||
+ "delay_ms BIGINT"
|
+ "delay_ms BIGINT"
|
||||||
+ ")");
|
+ ")");
|
||||||
|
|
||||||
|
st.executeUpdate("CREATE TABLE IF NOT EXISTS bc_secret_walls ("
|
||||||
|
+ "button_id VARCHAR(64) NOT NULL,"
|
||||||
|
+ "block_location VARCHAR(128) NOT NULL,"
|
||||||
|
+ "PRIMARY KEY (button_id, block_location)"
|
||||||
|
+ ")");
|
||||||
|
|
||||||
|
st.executeUpdate("CREATE TABLE IF NOT EXISTS bc_secret_settings ("
|
||||||
|
+ "button_id VARCHAR(64) PRIMARY KEY,"
|
||||||
|
+ "delay_ms BIGINT NOT NULL,"
|
||||||
|
+ "animation VARCHAR(16) NOT NULL DEFAULT 'wave'"
|
||||||
|
+ ")");
|
||||||
|
|
||||||
|
st.executeUpdate("ALTER TABLE bc_schedules "
|
||||||
|
+ "ADD COLUMN IF NOT EXISTS shot_delay_ticks INT");
|
||||||
|
|
||||||
|
st.executeUpdate("ALTER TABLE bc_schedules "
|
||||||
|
+ "ADD COLUMN IF NOT EXISTS trigger_mode VARCHAR(16)");
|
||||||
|
|
||||||
|
st.executeUpdate("ALTER TABLE bc_secret_settings "
|
||||||
|
+ "ADD COLUMN IF NOT EXISTS animation VARCHAR(16) NOT NULL DEFAULT 'wave'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +219,9 @@ public class MySQLStorage {
|
|||||||
"DELETE FROM bc_public_status WHERE button_id = ?",
|
"DELETE FROM bc_public_status WHERE button_id = ?",
|
||||||
"DELETE FROM bc_trust WHERE button_id = ?",
|
"DELETE FROM bc_trust WHERE button_id = ?",
|
||||||
"DELETE FROM bc_schedules WHERE button_id = ?",
|
"DELETE FROM bc_schedules WHERE button_id = ?",
|
||||||
"DELETE FROM bc_button_connections WHERE button_id = ?"
|
"DELETE FROM bc_button_connections WHERE button_id = ?",
|
||||||
|
"DELETE FROM bc_secret_walls WHERE button_id = ?",
|
||||||
|
"DELETE FROM bc_secret_settings WHERE button_id = ?"
|
||||||
};
|
};
|
||||||
for (String q : queries) {
|
for (String q : queries) {
|
||||||
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
@@ -295,12 +321,11 @@ public class MySQLStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setScheduleOpenTime(String buttonId, long ticks) {
|
public void setScheduleOpenTime(String buttonId, long ticks) {
|
||||||
String q = "INSERT INTO bc_schedules (button_id, open_time, close_time) VALUES (?, ?, COALESCE((SELECT close_time FROM bc_schedules WHERE button_id = ?), -1))"
|
String q = "INSERT INTO bc_schedules (button_id, open_time, close_time, shot_delay_ticks, trigger_mode) VALUES (?, ?, -1, -1, 'simultaneous')"
|
||||||
+ " ON DUPLICATE KEY UPDATE open_time = VALUES(open_time)";
|
+ " ON DUPLICATE KEY UPDATE open_time = VALUES(open_time)";
|
||||||
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
ps.setString(1, buttonId);
|
ps.setString(1, buttonId);
|
||||||
ps.setLong(2, ticks);
|
ps.setLong(2, ticks);
|
||||||
ps.setString(3, buttonId);
|
|
||||||
ps.executeUpdate();
|
ps.executeUpdate();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
plugin.getLogger().warning("MySQL setScheduleOpenTime Fehler: " + e.getMessage());
|
plugin.getLogger().warning("MySQL setScheduleOpenTime Fehler: " + e.getMessage());
|
||||||
@@ -321,12 +346,11 @@ public class MySQLStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setScheduleCloseTime(String buttonId, long ticks) {
|
public void setScheduleCloseTime(String buttonId, long ticks) {
|
||||||
String q = "INSERT INTO bc_schedules (button_id, open_time, close_time) VALUES (?, COALESCE((SELECT open_time FROM bc_schedules WHERE button_id = ?), -1), ?)"
|
String q = "INSERT INTO bc_schedules (button_id, open_time, close_time, shot_delay_ticks, trigger_mode) VALUES (?, -1, ?, -1, 'simultaneous')"
|
||||||
+ " ON DUPLICATE KEY UPDATE close_time = VALUES(close_time)";
|
+ " ON DUPLICATE KEY UPDATE close_time = VALUES(close_time)";
|
||||||
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
ps.setString(1, buttonId);
|
ps.setString(1, buttonId);
|
||||||
ps.setString(2, buttonId);
|
ps.setLong(2, ticks);
|
||||||
ps.setLong(3, ticks);
|
|
||||||
ps.executeUpdate();
|
ps.executeUpdate();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
plugin.getLogger().warning("MySQL setScheduleCloseTime Fehler: " + e.getMessage());
|
plugin.getLogger().warning("MySQL setScheduleCloseTime Fehler: " + e.getMessage());
|
||||||
@@ -346,6 +370,58 @@ public class MySQLStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setScheduleShotDelayTicks(String buttonId, int ticks) {
|
||||||
|
String q = "INSERT INTO bc_schedules (button_id, open_time, close_time, shot_delay_ticks, trigger_mode) VALUES (?, -1, -1, ?, 'simultaneous')"
|
||||||
|
+ " ON DUPLICATE KEY UPDATE shot_delay_ticks = VALUES(shot_delay_ticks)";
|
||||||
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
ps.setString(1, buttonId);
|
||||||
|
ps.setInt(2, ticks);
|
||||||
|
ps.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL setScheduleShotDelayTicks Fehler: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getScheduleShotDelayTicks(String buttonId) {
|
||||||
|
String q = "SELECT shot_delay_ticks FROM bc_schedules WHERE button_id = ? LIMIT 1";
|
||||||
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
ps.setString(1, buttonId);
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
if (!rs.next()) return -1;
|
||||||
|
int delay = rs.getInt(1);
|
||||||
|
return rs.wasNull() ? -1 : delay;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL getScheduleShotDelayTicks Fehler: " + e.getMessage());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScheduleTriggerMode(String buttonId, String mode) {
|
||||||
|
String q = "INSERT INTO bc_schedules (button_id, open_time, close_time, shot_delay_ticks, trigger_mode) VALUES (?, -1, -1, -1, ?)"
|
||||||
|
+ " ON DUPLICATE KEY UPDATE trigger_mode = VALUES(trigger_mode)";
|
||||||
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
ps.setString(1, buttonId);
|
||||||
|
ps.setString(2, mode);
|
||||||
|
ps.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL setScheduleTriggerMode Fehler: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScheduleTriggerMode(String buttonId) {
|
||||||
|
String q = "SELECT trigger_mode FROM bc_schedules WHERE button_id = ? LIMIT 1";
|
||||||
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
ps.setString(1, buttonId);
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
return rs.next() ? rs.getString(1) : null;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL getScheduleTriggerMode Fehler: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void clearSchedule(String buttonId) {
|
public void clearSchedule(String buttonId) {
|
||||||
String q = "DELETE FROM bc_schedules WHERE button_id = ?";
|
String q = "DELETE FROM bc_schedules WHERE button_id = ?";
|
||||||
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
@@ -490,6 +566,104 @@ public class MySQLStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSecretBlocks(String buttonId, List<String> blocks) {
|
||||||
|
String del = "DELETE FROM bc_secret_walls WHERE button_id = ?";
|
||||||
|
String ins = "INSERT INTO bc_secret_walls (button_id, block_location) VALUES (?, ?)";
|
||||||
|
try (PreparedStatement psDel = getConnection().prepareStatement(del);
|
||||||
|
PreparedStatement psIns = getConnection().prepareStatement(ins)) {
|
||||||
|
psDel.setString(1, buttonId);
|
||||||
|
psDel.executeUpdate();
|
||||||
|
|
||||||
|
for (String block : blocks) {
|
||||||
|
psIns.setString(1, buttonId);
|
||||||
|
psIns.setString(2, block);
|
||||||
|
psIns.addBatch();
|
||||||
|
}
|
||||||
|
psIns.executeBatch();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL setSecretBlocks Fehler: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getSecretBlocks(String buttonId) {
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
String q = "SELECT block_location FROM bc_secret_walls WHERE button_id = ?";
|
||||||
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
ps.setString(1, buttonId);
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
while (rs.next()) result.add(rs.getString(1));
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL getSecretBlocks Fehler: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecretRestoreDelayMs(String buttonId, long delayMs) {
|
||||||
|
String q = "INSERT INTO bc_secret_settings (button_id, delay_ms) VALUES (?, ?)"
|
||||||
|
+ " ON DUPLICATE KEY UPDATE delay_ms = VALUES(delay_ms)";
|
||||||
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
ps.setString(1, buttonId);
|
||||||
|
ps.setLong(2, delayMs);
|
||||||
|
ps.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL setSecretRestoreDelayMs Fehler: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSecretRestoreDelayMs(String buttonId) {
|
||||||
|
String q = "SELECT delay_ms FROM bc_secret_settings WHERE button_id = ? LIMIT 1";
|
||||||
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
ps.setString(1, buttonId);
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
return rs.next() ? rs.getLong(1) : 5000L;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL getSecretRestoreDelayMs Fehler: " + e.getMessage());
|
||||||
|
return 5000L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecretAnimation(String buttonId, String animation) {
|
||||||
|
String q = "INSERT INTO bc_secret_settings (button_id, delay_ms, animation) VALUES (?, COALESCE((SELECT delay_ms FROM bc_secret_settings WHERE button_id = ?), 5000), ?)"
|
||||||
|
+ " ON DUPLICATE KEY UPDATE animation = VALUES(animation)";
|
||||||
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
ps.setString(1, buttonId);
|
||||||
|
ps.setString(2, buttonId);
|
||||||
|
ps.setString(3, animation);
|
||||||
|
ps.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL setSecretAnimation Fehler: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSecretAnimation(String buttonId) {
|
||||||
|
String q = "SELECT animation FROM bc_secret_settings WHERE button_id = ? LIMIT 1";
|
||||||
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
ps.setString(1, buttonId);
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
return rs.next() ? rs.getString(1) : "wave";
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL getSecretAnimation Fehler: " + e.getMessage());
|
||||||
|
return "wave";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearSecret(String buttonId) {
|
||||||
|
String q1 = "DELETE FROM bc_secret_walls WHERE button_id = ?";
|
||||||
|
String q2 = "DELETE FROM bc_secret_settings WHERE button_id = ?";
|
||||||
|
try (PreparedStatement ps1 = getConnection().prepareStatement(q1);
|
||||||
|
PreparedStatement ps2 = getConnection().prepareStatement(q2)) {
|
||||||
|
ps1.setString(1, buttonId);
|
||||||
|
ps1.executeUpdate();
|
||||||
|
ps2.setString(1, buttonId);
|
||||||
|
ps2.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().warning("MySQL clearSecret Fehler: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isTrusted(String buttonId, UUID playerUUID) {
|
private boolean isTrusted(String buttonId, UUID playerUUID) {
|
||||||
String q = "SELECT 1 FROM bc_trust WHERE button_id = ? AND target_uuid = ? LIMIT 1";
|
String q = "SELECT 1 FROM bc_trust WHERE button_id = ? AND target_uuid = ? LIMIT 1";
|
||||||
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
try (PreparedStatement ps = getConnection().prepareStatement(q)) {
|
||||||
|
|||||||
@@ -16,11 +16,14 @@ import org.bukkit.inventory.meta.ItemMeta;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GUI zur Konfiguration der zeitgesteuerten Automatisierung eines Controllers.
|
* GUI zur Konfiguration der zeitgesteuerten Automatisierung eines Controllers.
|
||||||
*
|
*
|
||||||
* Layout (9×3 = 27 Slots):
|
* Layout (9×3 = 27 Slots):
|
||||||
|
* Slot 4 – Abschuss-Verzögerung Werfer/Spender (REPEATER) ← Links/Rechts: ±1 Tick | Shift: ±5
|
||||||
|
* Slot 6 – Schuss-Modus (COMPARATOR) ← Klick: gleichzeitig / nacheinander
|
||||||
* Slot 10 – Öffnungszeit (LIME_DYE / Sonne) ← Links/Rechts: ±1h | Shift: ±15min
|
* Slot 10 – Öffnungszeit (LIME_DYE / Sonne) ← Links/Rechts: ±1h | Shift: ±15min
|
||||||
* Slot 13 – Aktivierung an/aus (LEVER)
|
* Slot 13 – Aktivierung an/aus (LEVER)
|
||||||
* Slot 16 – Schließzeit (RED_DYE / Mond) ← Links/Rechts: ±1h | Shift: ±15min
|
* Slot 16 – Schließzeit (RED_DYE / Mond) ← Links/Rechts: ±1h | Shift: ±15min
|
||||||
@@ -39,6 +42,8 @@ public class ScheduleGUI implements Listener {
|
|||||||
// Aktuelle Werte während die GUI offen ist
|
// Aktuelle Werte während die GUI offen ist
|
||||||
private long openTime;
|
private long openTime;
|
||||||
private long closeTime;
|
private long closeTime;
|
||||||
|
private int shotDelayTicks;
|
||||||
|
private String triggerMode;
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
|
|
||||||
public ScheduleGUI(ButtonControl plugin, Player player, String buttonId) {
|
public ScheduleGUI(ButtonControl plugin, Player player, String buttonId) {
|
||||||
@@ -51,8 +56,16 @@ public class ScheduleGUI implements Listener {
|
|||||||
// Gespeicherte Werte laden (oder Standardwerte)
|
// Gespeicherte Werte laden (oder Standardwerte)
|
||||||
long savedOpen = dataManager.getScheduleOpenTime(buttonId);
|
long savedOpen = dataManager.getScheduleOpenTime(buttonId);
|
||||||
long savedClose = dataManager.getScheduleCloseTime(buttonId);
|
long savedClose = dataManager.getScheduleCloseTime(buttonId);
|
||||||
|
int savedShotDelay = dataManager.getScheduleShotDelayTicks(buttonId);
|
||||||
|
String savedTriggerMode = dataManager.getScheduleTriggerMode(buttonId);
|
||||||
this.openTime = savedOpen >= 0 ? savedOpen : plugin.timeToTicks(7, 0); // 07:00
|
this.openTime = savedOpen >= 0 ? savedOpen : plugin.timeToTicks(7, 0); // 07:00
|
||||||
this.closeTime = savedClose >= 0 ? savedClose : plugin.timeToTicks(19, 0); // 19:00
|
this.closeTime = savedClose >= 0 ? savedClose : plugin.timeToTicks(19, 0); // 19:00
|
||||||
|
this.shotDelayTicks = savedShotDelay >= 0
|
||||||
|
? savedShotDelay
|
||||||
|
: Math.max(1, plugin.getConfigManager().getConfig().getInt("timed-container-shot-delay-ticks", 2));
|
||||||
|
this.triggerMode = normalizeTriggerMode(savedTriggerMode != null
|
||||||
|
? savedTriggerMode
|
||||||
|
: plugin.getConfigManager().getConfig().getString("timed-container-trigger-mode", "simultaneous"));
|
||||||
this.enabled = savedOpen >= 0;
|
this.enabled = savedOpen >= 0;
|
||||||
|
|
||||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
@@ -72,6 +85,12 @@ public class ScheduleGUI implements Listener {
|
|||||||
ItemStack filler = makeItem(Material.GRAY_STAINED_GLASS_PANE, ChatColor.RESET + "");
|
ItemStack filler = makeItem(Material.GRAY_STAINED_GLASS_PANE, ChatColor.RESET + "");
|
||||||
for (int i = 0; i < 27; i++) inv.setItem(i, filler);
|
for (int i = 0; i < 27; i++) inv.setItem(i, filler);
|
||||||
|
|
||||||
|
// Slot 4 – Abschuss-Verzögerung
|
||||||
|
inv.setItem(4, makeDelayItem());
|
||||||
|
|
||||||
|
// Slot 6 – Modus
|
||||||
|
inv.setItem(6, makeModeItem());
|
||||||
|
|
||||||
// Slot 10 – Öffnungszeit
|
// Slot 10 – Öffnungszeit
|
||||||
inv.setItem(10, makeTimeItem(
|
inv.setItem(10, makeTimeItem(
|
||||||
Material.LIME_DYE,
|
Material.LIME_DYE,
|
||||||
@@ -106,6 +125,65 @@ public class ScheduleGUI implements Listener {
|
|||||||
"§7Speichert den aktuellen Zeitplan."));
|
"§7Speichert den aktuellen Zeitplan."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ItemStack makeDelayItem() {
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
lore.add("§e§l" + shotDelayTicks + " Ticks §7(" + formatShotDelaySeconds() + "s§7)");
|
||||||
|
if (isSequentialMode()) {
|
||||||
|
lore.add("§7Aktuell: §f" + shotDelayTicks + " Ticks zwischen einzelnen Geräten");
|
||||||
|
} else if (shotDelayTicks <= 1) {
|
||||||
|
lore.add("§7Aktuell: §falle verbundenen Werfer schießen jeden Tick");
|
||||||
|
} else {
|
||||||
|
lore.add("§7Aktuell: §f" + shotDelayTicks + " Ticks zwischen gemeinsamen Schüssen");
|
||||||
|
}
|
||||||
|
lore.add("");
|
||||||
|
lore.add("§7Linksklick: §f+1 Tick");
|
||||||
|
lore.add("§7Rechtsklick: §f−1 Tick");
|
||||||
|
lore.add("§7Shift+Links: §f+5 Ticks");
|
||||||
|
lore.add("§7Shift+Rechts: §f−5 Ticks");
|
||||||
|
lore.add("§8(1 Tick = schnellstmöglich)");
|
||||||
|
|
||||||
|
ItemStack item = new ItemStack(Material.REPEATER);
|
||||||
|
ItemMeta meta = item.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName("§b§lAbschuss-Verzögerung");
|
||||||
|
meta.setLore(lore);
|
||||||
|
item.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack makeModeItem() {
|
||||||
|
List<String> lore = new ArrayList<>();
|
||||||
|
lore.add(isSequentialMode()
|
||||||
|
? "§e§lNacheinander"
|
||||||
|
: "§e§lGleichzeitig");
|
||||||
|
lore.add("");
|
||||||
|
lore.add("§7Klick: §fModus wechseln");
|
||||||
|
lore.add("§8Gleichzeitig = alle Werfer zusammen");
|
||||||
|
lore.add("§8Nacheinander = Geräte rotieren der Reihe nach");
|
||||||
|
|
||||||
|
ItemStack item = new ItemStack(Material.COMPARATOR);
|
||||||
|
ItemMeta meta = item.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName("§d§lSchuss-Modus");
|
||||||
|
meta.setLore(lore);
|
||||||
|
item.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatShotDelaySeconds() {
|
||||||
|
return String.format(Locale.US, "%.2f", shotDelayTicks / 20.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSequentialMode() {
|
||||||
|
return "sequential".equals(triggerMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeTriggerMode(String mode) {
|
||||||
|
return "sequential".equalsIgnoreCase(mode) ? "sequential" : "simultaneous";
|
||||||
|
}
|
||||||
|
|
||||||
private ItemStack makeTimeItem(Material mat, String name, long ticks, String... loreLines) {
|
private ItemStack makeTimeItem(Material mat, String name, long ticks, String... loreLines) {
|
||||||
String timeStr = plugin.ticksToTime(ticks);
|
String timeStr = plugin.ticksToTime(ticks);
|
||||||
List<String> lore = new ArrayList<>();
|
List<String> lore = new ArrayList<>();
|
||||||
@@ -153,7 +231,20 @@ public class ScheduleGUI implements Listener {
|
|||||||
// Schrittgröße: Shift = 15 Min (250 Ticks), sonst 1 Std (1000 Ticks)
|
// Schrittgröße: Shift = 15 Min (250 Ticks), sonst 1 Std (1000 Ticks)
|
||||||
long step = event.isShiftClick() ? 250L : 1000L;
|
long step = event.isShiftClick() ? 250L : 1000L;
|
||||||
|
|
||||||
if (slot == 10) {
|
if (slot == 4) {
|
||||||
|
int delayStep = event.isShiftClick() ? 5 : 1;
|
||||||
|
if (event.isLeftClick()) shotDelayTicks += delayStep;
|
||||||
|
if (event.isRightClick()) shotDelayTicks -= delayStep;
|
||||||
|
if (shotDelayTicks < 1) shotDelayTicks = 1;
|
||||||
|
if (shotDelayTicks > 200) shotDelayTicks = 200;
|
||||||
|
inv.setItem(4, makeDelayItem());
|
||||||
|
|
||||||
|
} else if (slot == 6) {
|
||||||
|
triggerMode = isSequentialMode() ? "simultaneous" : "sequential";
|
||||||
|
inv.setItem(4, makeDelayItem());
|
||||||
|
inv.setItem(6, makeModeItem());
|
||||||
|
|
||||||
|
} else if (slot == 10) {
|
||||||
// Öffnungszeit anpassen
|
// Öffnungszeit anpassen
|
||||||
if (event.isLeftClick()) openTime = (openTime + step + 24000) % 24000;
|
if (event.isLeftClick()) openTime = (openTime + step + 24000) % 24000;
|
||||||
if (event.isRightClick()) openTime = (openTime - step + 24000) % 24000;
|
if (event.isRightClick()) openTime = (openTime - step + 24000) % 24000;
|
||||||
@@ -203,10 +294,18 @@ public class ScheduleGUI implements Listener {
|
|||||||
if (enabled) {
|
if (enabled) {
|
||||||
dataManager.setScheduleOpenTime(buttonId, openTime);
|
dataManager.setScheduleOpenTime(buttonId, openTime);
|
||||||
dataManager.setScheduleCloseTime(buttonId, closeTime);
|
dataManager.setScheduleCloseTime(buttonId, closeTime);
|
||||||
|
dataManager.setScheduleShotDelayTicks(buttonId, shotDelayTicks);
|
||||||
|
dataManager.setScheduleTriggerMode(buttonId, triggerMode);
|
||||||
player.sendMessage("§a[BC] §7Zeitplan gespeichert: §aÖffnet §7um §e"
|
player.sendMessage("§a[BC] §7Zeitplan gespeichert: §aÖffnet §7um §e"
|
||||||
+ plugin.ticksToTime(openTime)
|
+ plugin.ticksToTime(openTime)
|
||||||
+ " §7· §cSchließt §7um §e"
|
+ " §7· §cSchließt §7um §e"
|
||||||
+ plugin.ticksToTime(closeTime));
|
+ plugin.ticksToTime(closeTime)
|
||||||
|
+ " §7· §bDelay §e"
|
||||||
|
+ shotDelayTicks
|
||||||
|
+ "§7 Ticks §8("
|
||||||
|
+ formatShotDelaySeconds()
|
||||||
|
+ "s§8) §7· §dModus §e"
|
||||||
|
+ (isSequentialMode() ? "nacheinander" : "gleichzeitig"));
|
||||||
} else {
|
} else {
|
||||||
dataManager.clearSchedule(buttonId);
|
dataManager.clearSchedule(buttonId);
|
||||||
player.sendMessage("§7[BC] Zeitplan deaktiviert.");
|
player.sendMessage("§7[BC] Zeitplan deaktiviert.");
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ max-noteblocks: 10
|
|||||||
max-gates: 20
|
max-gates: 20
|
||||||
max-trapdoors: 20
|
max-trapdoors: 20
|
||||||
max-bells: 5
|
max-bells: 5
|
||||||
|
max-dispensers: 20
|
||||||
|
max-droppers: 20
|
||||||
|
|
||||||
# ── Notenblöcke ─────────────────────────────────────────────────────────────
|
# ── Notenblöcke ─────────────────────────────────────────────────────────────
|
||||||
default-note: "PIANO"
|
default-note: "PIANO"
|
||||||
@@ -19,6 +21,14 @@ motion-detection-radius: 5.0
|
|||||||
motion-close-delay-ms: 5000
|
motion-close-delay-ms: 5000
|
||||||
# Cooldown: wie lange nach dem Schließen der Sensor nicht erneut auslöst
|
# Cooldown: wie lange nach dem Schließen der Sensor nicht erneut auslöst
|
||||||
motion-trigger-cooldown-ms: 2000
|
motion-trigger-cooldown-ms: 2000
|
||||||
|
# Legacy-Fallback für ältere Zeitpläne ohne eigenen GUI-Delay-Wert
|
||||||
|
# (20 Ticks = 1 Sekunde)
|
||||||
|
timed-container-interval-ticks: 40
|
||||||
|
# Standardwert für die Zeit zwischen einzelnen Abschüssen im Zeitplan
|
||||||
|
# Wird verwendet, bis ein Controller in der GUI einen eigenen Wert speichert
|
||||||
|
timed-container-shot-delay-ticks: 2
|
||||||
|
# Standardmodus für Zeitplan-Werfer/Spender: simultaneous oder sequential
|
||||||
|
timed-container-trigger-mode: simultaneous
|
||||||
|
|
||||||
# ── Optionales MySQL-Backend ────────────────────────────────────────────────
|
# ── Optionales MySQL-Backend ────────────────────────────────────────────────
|
||||||
mysql:
|
mysql:
|
||||||
|
|||||||
@@ -25,8 +25,16 @@ max-fallturen-erreicht: "§cMaximale Anzahl an Falltüren erreicht."
|
|||||||
lampen-eingeschaltet: "§aLampen wurden eingeschaltet."
|
lampen-eingeschaltet: "§aLampen wurden eingeschaltet."
|
||||||
lampen-ausgeschaltet: "§cLampen wurden ausgeschaltet."
|
lampen-ausgeschaltet: "§cLampen wurden ausgeschaltet."
|
||||||
max-lampen-erreicht: "§cMaximale Anzahl an Lampen erreicht."
|
max-lampen-erreicht: "§cMaximale Anzahl an Lampen erreicht."
|
||||||
|
creaking-heart-aktiviert: "§aKnarrherz wurde aktiviert."
|
||||||
|
creaking-heart-deaktiviert: "§cKnarrherz wurde deaktiviert."
|
||||||
|
gitter-geoeffnet: "§aGitter wurden geöffnet."
|
||||||
|
gitter-geschlossen: "§cGitter wurden geschlossen."
|
||||||
glocke-gelaeutet: "§aGlocke wurde geläutet."
|
glocke-gelaeutet: "§aGlocke wurde geläutet."
|
||||||
max-glocken-erreicht: "§cMaximale Anzahl an Glocken erreicht."
|
max-glocken-erreicht: "§cMaximale Anzahl an Glocken erreicht."
|
||||||
|
spender-ausgeloest: "§aSpender wurden ausgelöst."
|
||||||
|
werfer-ausgeloest: "§aWerfer wurden ausgelöst."
|
||||||
|
max-spender-erreicht: "§cMaximale Anzahl an Spendern erreicht."
|
||||||
|
max-werfer-erreicht: "§cMaximale Anzahl an Werfern erreicht."
|
||||||
|
|
||||||
# ── Notenblöcke ──────────────────────────────────────────────────────────────
|
# ── Notenblöcke ──────────────────────────────────────────────────────────────
|
||||||
notenblock-ausgeloest: "§aNotenblock-Klingel wurde ausgelöst."
|
notenblock-ausgeloest: "§aNotenblock-Klingel wurde ausgelöst."
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: ButtonControl
|
name: ButtonControl
|
||||||
version: 1.7
|
version: 1.8
|
||||||
main: viper.ButtonControl
|
main: viper.ButtonControl
|
||||||
api-version: 1.21
|
api-version: 1.21
|
||||||
author: M_Viper
|
author: M_Viper
|
||||||
@@ -11,7 +11,7 @@ description: >
|
|||||||
commands:
|
commands:
|
||||||
bc:
|
bc:
|
||||||
description: Hauptbefehl für ButtonControl
|
description: Hauptbefehl für ButtonControl
|
||||||
usage: /bc <info|reload|note|list|rename|schedule|trust|untrust|public|private>
|
usage: "/bc <info|reload|note|list|rename|schedule|trust|untrust|public|private|undo|secret> (Secret: select|info|add|remove|clear|delay|animation)"
|
||||||
aliases: [buttoncontrol]
|
aliases: [buttoncontrol]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|||||||
Reference in New Issue
Block a user