From 0dddc0dfb55f94c64df8949836b933265fe69c20 Mon Sep 17 00:00:00 2001 From: M_Viper Date: Sat, 28 Feb 2026 13:37:23 +0100 Subject: [PATCH] Update from Git Manager GUI --- .../commands/LobbyTabCompleter.java | 55 +-- .../commands/NexusLobbyCommand.java | 193 +++++++---- .../nexuslobby/modules/ScoreboardModule.java | 95 +++--- .../ArmorStandCmdExecutor.java | 13 + .../nexuslobby/modules/gadgets/Balloon.java | 2 + .../nexuslobby/modules/gadgets/FreezeRay.java | 49 +-- .../modules/gadgets/GadgetModule.java | 304 +++++++++++------ .../modules/gadgets/GadgetShield.java | 59 ++++ .../modules/gadgets/MeteorStrike.java | 16 +- .../modules/gadgets/ShieldTask.java | 39 ++- .../modules/parkour/ParkourListener.java | 62 ++-- .../modules/parkour/ParkourManager.java | 315 +++++++++++++----- src/main/resources/plugin.yml | 2 +- 13 files changed, 813 insertions(+), 391 deletions(-) create mode 100644 src/main/java/de/nexuslobby/modules/gadgets/GadgetShield.java diff --git a/src/main/java/de/nexuslobby/commands/LobbyTabCompleter.java b/src/main/java/de/nexuslobby/commands/LobbyTabCompleter.java index 675d1fd..c175a61 100644 --- a/src/main/java/de/nexuslobby/commands/LobbyTabCompleter.java +++ b/src/main/java/de/nexuslobby/commands/LobbyTabCompleter.java @@ -19,7 +19,7 @@ public class LobbyTabCompleter implements TabCompleter { private final HologramModule hologramModule; public LobbyTabCompleter(PortalManager portalManager, HologramModule hologramModule) { - this.portalManager = portalManager; + this.portalManager = portalManager; this.hologramModule = hologramModule; } @@ -28,14 +28,14 @@ public class LobbyTabCompleter implements TabCompleter { List suggestions = new ArrayList<>(); String cmdName = command.getName().toLowerCase(); - // ── NexusLobby Hauptbefehl (/nexus oder /nexuslobby) ───────────────── + // ── NexusLobby Hauptbefehl (/nexus oder /nexuslobby) ────────────────── if (cmdName.equals("nexuslobby") || cmdName.equals("nexus")) { // /nexuslobby if (args.length == 1) { if (sender.hasPermission("nexuslobby.admin")) { suggestions.addAll(Arrays.asList( - "reload", "setspawn", "silentjoin", "parkour", "ball" + "reload", "setspawn", "silentjoin", "parkour", "ball", "cleanbubbles", "gadgetshield" )); } suggestions.add("sb"); @@ -49,9 +49,17 @@ public class LobbyTabCompleter implements TabCompleter { suggestions.addAll(Arrays.asList("admin", "spieler")); } case "silentjoin" -> suggestions.addAll(Arrays.asList("on", "off")); - case "parkour" -> suggestions.addAll(Arrays.asList( - "setstart", "setfinish", "setcheckpoint", "reset", "clear", "removeall" - )); + case "parkour" -> { + if (sender.hasPermission("nexuslobby.admin")) { + suggestions.addAll(Arrays.asList( + "setstart", "setfinish", "setcheckpoint", + "reset", "clear", "removeall", "info" + )); + } else { + // Normale Spieler können nur ihren Run abbrechen + suggestions.add("reset"); + } + } case "ball" -> { if (sender.hasPermission("nexuslobby.admin")) { suggestions.addAll(Arrays.asList( @@ -64,17 +72,20 @@ public class LobbyTabCompleter implements TabCompleter { // /nexuslobby } else if (args.length == 3) { switch (args[0].toLowerCase()) { + // Für setstart / setfinish / setcheckpoint → Strecken-Nummer 1 oder 2 case "parkour" -> { - if (args[1].equalsIgnoreCase("setcheckpoint")) - suggestions.addAll(Arrays.asList("1","2","3","4","5","6","7","8","9")); + String parkSub = args[1].toLowerCase(); + if (parkSub.equals("setstart") + || parkSub.equals("setfinish") + || parkSub.equals("setcheckpoint")) { + suggestions.addAll(Arrays.asList("1", "2")); + } } case "ball" -> { switch (args[1].toLowerCase()) { - // /nexuslobby ball goal - case "goal" -> suggestions.addAll(Arrays.asList( + case "goal" -> suggestions.addAll(Arrays.asList( "pos1", "pos2", "save", "delete", "list", "show", "debug" )); - // /nexuslobby ball score case "score" -> suggestions.add("reset"); } } @@ -84,19 +95,16 @@ public class LobbyTabCompleter implements TabCompleter { } else if (args.length == 4) { if (args[0].equalsIgnoreCase("ball") && args[1].equalsIgnoreCase("goal")) { switch (args[2].toLowerCase()) { - // /nexuslobby ball goal save → Tor-Name (Freitext) case "save" -> suggestions.add(""); - // /nexuslobby ball goal delete case "delete" -> { - // Tor-Namen aus Config laden und vorschlagen var section = NexusLobby.getInstance().getConfig() - .getConfigurationSection("ball.goals"); + .getConfigurationSection("ball.goals"); if (section != null) suggestions.addAll(section.getKeys(false)); } } } - // /nexuslobby ball goal save → Team 1 oder 2 + // /nexuslobby ball goal save → Team 1 oder 2 } else if (args.length == 5) { if (args[0].equalsIgnoreCase("ball") && args[1].equalsIgnoreCase("goal") @@ -111,15 +119,13 @@ public class LobbyTabCompleter implements TabCompleter { if (args.length == 1) { suggestions.addAll(Arrays.asList("create", "delete")); } else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) { - if (hologramModule != null) - suggestions.addAll(hologramModule.getHologramIds()); + if (hologramModule != null) suggestions.addAll(hologramModule.getHologramIds()); } } // ── Wartungsmodus ───────────────────────────────────────────────────── else if (cmdName.equals("maintenance")) { - if (args.length == 1) - suggestions.addAll(Arrays.asList("on", "off")); + if (args.length == 1) suggestions.addAll(Arrays.asList("on", "off")); } // ── Portalsystem ────────────────────────────────────────────────────── @@ -127,8 +133,7 @@ public class LobbyTabCompleter implements TabCompleter { if (args.length == 1) { suggestions.addAll(Arrays.asList("create", "delete", "list")); } else if (args.length == 2 && args[0].equalsIgnoreCase("delete")) { - if (portalManager != null) - suggestions.addAll(portalManager.getPortalNames()); + if (portalManager != null) suggestions.addAll(portalManager.getPortalNames()); } } @@ -143,8 +148,7 @@ public class LobbyTabCompleter implements TabCompleter { // ── Intro System ────────────────────────────────────────────────────── else if (cmdName.equals("intro")) { - if (args.length == 1) - suggestions.addAll(Arrays.asList("add", "clear", "start")); + if (args.length == 1) suggestions.addAll(Arrays.asList("add", "clear", "start")); } // ── WorldBorder ─────────────────────────────────────────────────────── @@ -160,7 +164,7 @@ public class LobbyTabCompleter implements TabCompleter { else if (cmdName.equals("nexuscmd") || cmdName.equals("ncmd") || cmdName.equals("ascmd") || cmdName.equals("conv")) { if (args.length == 1) { - suggestions.addAll(Arrays.asList("add", "remove", "list", "name", "lookat", "conv", "say")); + suggestions.addAll(Arrays.asList("add", "remove", "list", "name", "lookat", "conv", "say", "clearbubbles")); } else if (args.length == 2) { switch (args[0].toLowerCase()) { case "add" -> suggestions.addAll(Arrays.asList("0","1","2","3","4","5","6","7","8","9")); @@ -201,7 +205,6 @@ public class LobbyTabCompleter implements TabCompleter { } } - // Filtert die Liste basierend auf der bisherigen Eingabe return suggestions.stream() .filter(s -> s.toLowerCase().startsWith(args[args.length - 1].toLowerCase())) .collect(Collectors.toList()); diff --git a/src/main/java/de/nexuslobby/commands/NexusLobbyCommand.java b/src/main/java/de/nexuslobby/commands/NexusLobbyCommand.java index 71d624f..6856c8c 100644 --- a/src/main/java/de/nexuslobby/commands/NexusLobbyCommand.java +++ b/src/main/java/de/nexuslobby/commands/NexusLobbyCommand.java @@ -22,7 +22,7 @@ public class NexusLobbyCommand implements CommandExecutor { @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - + if (!(sender instanceof Player player)) { sender.sendMessage(de.nexuslobby.utils.LangManager.get("only_player")); return true; @@ -34,19 +34,18 @@ public class NexusLobbyCommand implements CommandExecutor { // --- DIREKTE KURZ-BEFEHLE --- if (cmdName.equalsIgnoreCase("setstart")) { if (!player.hasPermission("nexuslobby.admin")) return noPerm(player); - handleSetStart(player, pm); + // Standard: Strecke 1 setzen + pm.setStartLocation(player, 1); return true; } if (cmdName.equalsIgnoreCase("setcheckpoint")) { if (!player.hasPermission("nexuslobby.admin")) return noPerm(player); - pm.setCheckpoint(player, player.getLocation()); - player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_checkpoint_set")); + pm.setCheckpoint(player, 1); return true; } if (cmdName.equalsIgnoreCase("setfinish")) { if (!player.hasPermission("nexuslobby.admin")) return noPerm(player); - pm.setFinishLocation(player.getLocation()); - player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_finish_set")); + pm.setFinishLocation(player, 1); return true; } @@ -75,6 +74,7 @@ public class NexusLobbyCommand implements CommandExecutor { } switch (args[0].toLowerCase()) { + case "reload": if (!player.hasPermission("nexuslobby.admin")) return noPerm(player); NexusLobby.getInstance().reloadPlugin(); @@ -83,7 +83,6 @@ public class NexusLobbyCommand implements CommandExecutor { break; case "cleanbubbles": - // Bereinigt alle hängengebliebenen Sprechblasen-ArmorStands im gesamten Server. if (!player.hasPermission("nexuslobby.admin")) return noPerm(player); handleCleanBubbles(player); break; @@ -96,7 +95,7 @@ public class NexusLobbyCommand implements CommandExecutor { config.set("spawn.x", loc.getX()); config.set("spawn.y", loc.getY()); config.set("spawn.z", loc.getZ()); - config.set("spawn.yaw", (double) loc.getYaw()); + config.set("spawn.yaw", (double) loc.getYaw()); config.set("spawn.pitch", (double) loc.getPitch()); NexusLobby.getInstance().saveConfig(); player.sendMessage(de.nexuslobby.utils.LangManager.get("spawn_set")); @@ -117,7 +116,12 @@ public class NexusLobbyCommand implements CommandExecutor { handleScoreboard(player, args); break; - case "ball": // NEU: Weiterleitung an das SoccerModule + case "gadgetshield": + if (!player.hasPermission("nexuslobby.admin") && !player.isOp()) return noPerm(player); + de.nexuslobby.modules.gadgets.GadgetShield.toggle(player); + break; + + case "ball": if (NexusLobby.getInstance().getSoccerModule() != null) { return NexusLobby.getInstance().getSoccerModule().onCommand(sender, command, label, args); } @@ -125,42 +129,7 @@ public class NexusLobbyCommand implements CommandExecutor { break; case "parkour": - if (args.length < 2) { - player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_usage")); - return true; - } - - String sub = args[1].toLowerCase(); - if (!player.hasPermission("nexuslobby.admin") && !sub.equals("reset")) return noPerm(player); - - switch (sub) { - case "setstart": - handleSetStart(player, pm); - break; - case "setfinish": - pm.setFinishLocation(player.getLocation()); - player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_finish_set")); - break; - case "setcheckpoint": - pm.setCheckpoint(player, player.getLocation()); - break; - case "reset": - pm.stopParkour(player); - player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_run_aborted")); - break; - case "clear": - pm.clearStats(); - player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_besttimes_cleared")); - break; - case "removeall": - pm.removeAllPoints(); - player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_track_removed")); - player.playSound(player.getLocation(), Sound.ENTITY_ITEM_BREAK, 1f, 1f); - break; - default: - player.sendMessage(de.nexuslobby.utils.LangManager.get("unknown_subcommand")); - break; - } + handleParkour(player, args, pm); break; default: @@ -171,13 +140,109 @@ public class NexusLobbyCommand implements CommandExecutor { return true; } - private void handleSetStart(Player player, ParkourManager pm) { - // NPC Erkennung (Blickrichtung auf ArmorStand) + // ───────────────────────────────────────────────────────────────────────── + // Parkour-Handler + // ───────────────────────────────────────────────────────────────────────── + + private void handleParkour(Player player, String[] args, ParkourManager pm) { + if (args.length < 2) { + player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_usage")); + // Strecken-Info ausgeben + player.sendMessage(pm.getTrackInfo()); + return; + } + + String sub = args[1].toLowerCase(); + + // Nur "reset" kann auch ohne Admin-Recht ausgeführt werden (eigenen Lauf abbrechen) + if (!player.hasPermission("nexuslobby.admin") && !sub.equals("reset")) { + noPerm(player); + return; + } + + switch (sub) { + + // /nexus parkour setstart <1|2> + case "setstart": { + int track = parseTrack(args, 2, player); + if (track == -1) return; + handleSetStart(player, pm, track); + break; + } + + // /nexus parkour setfinish <1|2> + case "setfinish": { + int track = parseTrack(args, 2, player); + if (track == -1) return; + pm.setFinishLocation(player, track); + break; + } + + // /nexus parkour setcheckpoint <1|2> + case "setcheckpoint": { + int track = parseTrack(args, 2, player); + if (track == -1) return; + pm.setCheckpoint(player, track); + break; + } + + case "reset": + pm.stopParkour(player); + player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_run_aborted")); + break; + + case "clear": + pm.clearStats(); + player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_besttimes_cleared")); + break; + + case "removeall": + pm.removeAllPoints(); + player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_track_removed")); + player.playSound(player.getLocation(), Sound.ENTITY_ITEM_BREAK, 1f, 1f); + break; + + case "info": + player.sendMessage(pm.getTrackInfo()); + break; + + default: + player.sendMessage(de.nexuslobby.utils.LangManager.get("unknown_subcommand")); + break; + } + } + + /** + * Liest die Track-Nummer aus args[index]. + * Gibt 1 oder 2 zurück; bei Fehler sendet er eine Nachricht und gibt -1 zurück. + */ + private int parseTrack(String[] args, int index, Player player) { + if (args.length <= index) { + player.sendMessage("§8[§6Nexus§8] §cBitte gib eine Strecken-Nummer an: §e1 §7oder §e2"); + return -1; + } + int track; + try { + track = Integer.parseInt(args[index]); + } catch (NumberFormatException e) { + player.sendMessage("§8[§6Nexus§8] §cUngültige Strecken-Nummer. Bitte §e1 §7oder §e2 §7verwenden."); + return -1; + } + if (track < 1 || track > 2) { + player.sendMessage("§8[§6Nexus§8] §cNur Strecke §e1 §7oder §e2 §7sind erlaubt."); + return -1; + } + return track; + } + + private void handleSetStart(Player player, ParkourManager pm, int track) { + // NPC-Erkennung: schaut der Spieler auf einen ArmorStand? ArmorStand targetAs = null; List nearby = player.getNearbyEntities(4, 4, 4); for (Entity e : nearby) { if (e instanceof ArmorStand as) { - double dot = player.getLocation().getDirection().dot(as.getLocation().toVector().subtract(player.getLocation().toVector()).normalize()); + double dot = player.getLocation().getDirection() + .dot(as.getLocation().toVector().subtract(player.getLocation().toVector()).normalize()); if (dot > 0.9) { targetAs = as; break; @@ -189,13 +254,16 @@ public class NexusLobbyCommand implements CommandExecutor { targetAs.addScoreboardTag("parkour_npc"); player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_npc_marked")); } - pm.setStartLocation(player.getLocation()); - player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_start_set")); + pm.setStartLocation(player, track); } + // ───────────────────────────────────────────────────────────────────────── + // Sonstige Handler + // ───────────────────────────────────────────────────────────────────────── + private void handleCleanBubbles(Player player) { de.nexuslobby.modules.armorstandtools.ConversationManager cm = - NexusLobby.getInstance().getConversationManager(); + NexusLobby.getInstance().getConversationManager(); if (cm == null) { player.sendMessage("§8[§6Nexus§8] §cConversationManager ist nicht aktiv."); return; @@ -220,18 +288,17 @@ public class NexusLobbyCommand implements CommandExecutor { player.sendMessage(de.nexuslobby.utils.LangManager.get("scoreboard_module_disabled")); return; } - String sub = args[1].toLowerCase(); - switch (sub) { - case "on": sbModule.setVisibility(player, true); break; - case "off": sbModule.setVisibility(player, false); break; - case "admin": + switch (args[1].toLowerCase()) { + case "on" -> sbModule.setVisibility(player, true); + case "off" -> sbModule.setVisibility(player, false); + case "admin" -> { if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, true); else player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission")); - break; - case "spieler": + } + case "spieler" -> { if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, false); else player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission")); - break; + } } } @@ -248,13 +315,19 @@ public class NexusLobbyCommand implements CommandExecutor { player.sendMessage(de.nexuslobby.utils.LangManager.get("info_title")); player.sendMessage(""); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_spawn")); - player.sendMessage(de.nexuslobby.utils.LangManager.get("info_parkour")); + player.sendMessage("§e/nexus parkour setstart <1|2> §7- Start setzen"); + player.sendMessage("§e/nexus parkour setcheckpoint <1|2> §7- Checkpoint setzen"); + player.sendMessage("§e/nexus parkour setfinish <1|2> §7- Ziel setzen"); + player.sendMessage("§e/nexus parkour reset §7- Eigenen Run abbrechen"); + player.sendMessage("§e/nexus parkour clear §7- §cTop-10 Liste löschen"); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_removeall")); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_ball")); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_setspawn")); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_scoreboard")); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_reload")); player.sendMessage("§e/nexuslobby cleanbubbles §7- Hängende Sprechblasen entfernen"); + if (player.hasPermission("nexuslobby.admin") || player.isOp()) + player.sendMessage("§e/nexuslobby gadgetshield §7- Gadget-Schutz für Admins ein/ausschalten"); player.sendMessage(de.nexuslobby.utils.LangManager.get("info_footer")); } } \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/ScoreboardModule.java b/src/main/java/de/nexuslobby/modules/ScoreboardModule.java index 41c5338..3510fe0 100644 --- a/src/main/java/de/nexuslobby/modules/ScoreboardModule.java +++ b/src/main/java/de/nexuslobby/modules/ScoreboardModule.java @@ -10,8 +10,10 @@ import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scoreboard.*; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; @@ -23,10 +25,12 @@ public class ScoreboardModule implements Module, Listener { private final NexusLobby plugin = NexusLobby.getInstance(); private boolean placeholderAPIEnabled; - - // Speicher für die aktuellen Spieler-Einstellungen (bis zum Restart/Reload) + private final Set hiddenPlayers = new HashSet<>(); private final Set adminModePlayers = new HashSet<>(); + + // WICHTIG: Hier speichern wir die Scoreboards pro Spieler, um sie wiederzuverwenden + private final Map playerBoards = new HashMap<>(); @Override public String getName() { @@ -38,7 +42,6 @@ public class ScoreboardModule implements Module, Listener { FileConfiguration vConfig = plugin.getVisualsConfig(); if (!vConfig.getBoolean("scoreboard.enabled", true)) return; - // Listener registrieren Bukkit.getPluginManager().registerEvents(this, plugin); placeholderAPIEnabled = Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null; @@ -53,9 +56,6 @@ public class ScoreboardModule implements Module, Listener { }.runTaskTimer(plugin, 0L, vConfig.getLong("scoreboard.update_ticks", 20L)); } - /** - * Setzt Scoreboard-Status beim Join gemäß config.yml (scoreboard-default-visible) - */ @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); @@ -63,13 +63,19 @@ public class ScoreboardModule implements Module, Listener { if (!defaultVisible) { hiddenPlayers.add(player.getUniqueId()); player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard()); + // Sicherstellen, dass kein altes Board gespeichert ist + playerBoards.remove(player.getUniqueId()); } else { hiddenPlayers.remove(player.getUniqueId()); + // Beim Einloggen initialisieren, damit es nicht beim ersten Tick flackert + if (!playerBoards.containsKey(player.getUniqueId())) { + playerBoards.put(player.getUniqueId(), Bukkit.getScoreboardManager().getNewScoreboard()); + player.setScoreboard(playerBoards.get(player.getUniqueId())); + } } } private void updateSidebar(Player player) { - // 1. Prüfen, ob der Spieler das Scoreboard ausgeblendet hat if (hiddenPlayers.contains(player.getUniqueId())) { if (player.getScoreboard() != Bukkit.getScoreboardManager().getMainScoreboard()) { player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard()); @@ -78,49 +84,50 @@ public class ScoreboardModule implements Module, Listener { } FileConfiguration vConfig = plugin.getVisualsConfig(); - Scoreboard board = player.getScoreboard(); + + String configPath = (adminModePlayers.contains(player.getUniqueId()) + && player.hasPermission("nexuslobby.scoreboard.admin")) + ? "scoreboard.owner" + : "scoreboard.default"; + + String title = vConfig.getString(configPath + ".title", "&6&lNexusLobby"); + List lines = vConfig.getStringList(configPath + ".lines"); + + // --------------------------------------------------------- + // FIX: Statt getNewScoreboard() holen wir das gespeicherte Board + // oder erstellen es genau einmal beim ersten Mal. + // --------------------------------------------------------- + Scoreboard board = playerBoards.get(player.getUniqueId()); - if (board == Bukkit.getScoreboardManager().getMainScoreboard()) { + if (board == null) { board = Bukkit.getScoreboardManager().getNewScoreboard(); + playerBoards.put(player.getUniqueId(), board); player.setScoreboard(board); } - Objective obj = board.getObjective("lobby"); - if (obj == null) { - obj = board.registerNewObjective("lobby", "dummy", "title"); - obj.setDisplaySlot(DisplaySlot.SIDEBAR); + // Altes Objective entfernen, damit wir die Zeilen aktualisieren können + // Dasboard selbst bleibt bestehen (wichtig für Tablist-Teams!) + Objective oldObj = board.getObjective("lobby"); + if (oldObj != null) { + oldObj.unregister(); } - // 2. Pfad-Logik basierend auf dem Modus (/nexus sb admin/spieler) - String configPath = "scoreboard.default"; - - // Wenn im Admin-Modus UND Rechte vorhanden -> owner Sektion - if (adminModePlayers.contains(player.getUniqueId()) && player.hasPermission("nexuslobby.scoreboard.admin")) { - configPath = "scoreboard.owner"; - } else { - // Standardmäßig default nutzen - configPath = "scoreboard.default"; - } - - String title = vConfig.getString(configPath + ".title", "&6&lNexusLobby"); - obj.setDisplayName(translate(player, title)); - - List lines = vConfig.getStringList(configPath + ".lines"); - - // Scores zurücksetzen um Duplikate zu vermeiden - board.getEntries().forEach(board::resetScores); + Objective obj = board.registerNewObjective("lobby", "dummy", + translate(player, title)); + obj.setDisplaySlot(DisplaySlot.SIDEBAR); for (int i = 0; i < lines.size(); i++) { String line = translate(player, lines.get(i)); - - // Verhindert das Verschwinden leerer Zeilen - if (line.isEmpty() || line.trim().isEmpty()) { - line = "" + ChatColor.values()[i] + ChatColor.RESET; + + if (line.isEmpty() || line.isBlank()) { + line = ChatColor.values()[i % ChatColor.values().length].toString(); } - - Score score = obj.getScore(line); - score.setScore(lines.size() - i); + + obj.getScore(line).setScore(lines.size() - i); } + + // Kein player.setScoreboard(board) nötig, da wir das Objekt 'board' + // direkt verändert haben, das der Spieler bereits hat. } // --- Methoden für den /nexus sb Befehl --- @@ -128,7 +135,13 @@ public class ScoreboardModule implements Module, Listener { public void setVisibility(Player player, boolean visible) { if (visible) { hiddenPlayers.remove(player.getUniqueId()); + // Wenn wir es wieder einschalten, ggf. Board zurücksetzen oder neu holen + if (!playerBoards.containsKey(player.getUniqueId())) { + playerBoards.put(player.getUniqueId(), Bukkit.getScoreboardManager().getNewScoreboard()); + } + player.setScoreboard(playerBoards.get(player.getUniqueId())); player.sendMessage("§7[§6Nexus§7] §aScoreboard eingeschaltet."); + updateSidebar(player); // Sofort aktualisieren } else { hiddenPlayers.add(player.getUniqueId()); player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard()); @@ -144,7 +157,6 @@ public class ScoreboardModule implements Module, Listener { adminModePlayers.remove(player.getUniqueId()); player.sendMessage("§7[§6Nexus§7] §eModus: §6Spieler-Scoreboard §7(Default-Sektion)"); } - // Sofortiges Update erzwingen updateSidebar(player); } @@ -153,9 +165,7 @@ public class ScoreboardModule implements Module, Listener { if (placeholderAPIEnabled) { try { translated = PlaceholderAPI.setPlaceholders(player, text); - } catch (NoClassDefFoundError ignored) { - // PlaceholderAPI fehlt zur Laufzeit - } + } catch (NoClassDefFoundError ignored) {} } return ChatColor.translateAlternateColorCodes('&', translated); } @@ -168,5 +178,6 @@ public class ScoreboardModule implements Module, Listener { } hiddenPlayers.clear(); adminModePlayers.clear(); + playerBoards.clear(); // Speicher freigeben } } \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCmdExecutor.java b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCmdExecutor.java index 88076f9..dae4980 100644 --- a/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCmdExecutor.java +++ b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCmdExecutor.java @@ -238,6 +238,18 @@ public class ArmorStandCmdExecutor implements CommandExecutor { return true; } + if (args[0].equalsIgnoreCase("clearbubbles")) { + ConversationManager cm = NexusLobby.getInstance().getConversationManager(); + if (cm == null) { + p.sendMessage(prefix + "§cConversationManager ist nicht aktiv."); + return true; + } + cm.clearHangingBubbles(); + p.sendMessage(prefix + "§aAlle hängenden Sprechblasen wurden entfernt."); + p.playSound(p.getLocation(), org.bukkit.Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 2f); + return true; + } + return sendHelp(p); } @@ -284,6 +296,7 @@ public class ArmorStandCmdExecutor implements CommandExecutor { p.sendMessage("§e/nexuscmd conv §7- Gesprächs-Menü"); p.sendMessage("§e/nexuscmd list §7- Zeigt alle Tags"); p.sendMessage("§e/nexuscmd remove §7- Löscht Befehle"); + p.sendMessage("§e/nexuscmd clearbubbles §7- Entfernt hängende Sprechblasen"); return true; } } \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/gadgets/Balloon.java b/src/main/java/de/nexuslobby/modules/gadgets/Balloon.java index 9cd221f..9d7d18d 100644 --- a/src/main/java/de/nexuslobby/modules/gadgets/Balloon.java +++ b/src/main/java/de/nexuslobby/modules/gadgets/Balloon.java @@ -28,6 +28,8 @@ public class Balloon { this.balloonEntity.setInvulnerable(true); this.balloonEntity.setGravity(false); this.balloonEntity.setLeashHolder(player); + // Tag damit GadgetModule Rechtsklicks auf den Ballon abfangen kann + this.balloonEntity.addScoreboardTag("nexus_balloon"); // Der ArmorStand, der den farbigen Block trägt this.headStand = (ArmorStand) loc.getWorld().spawnEntity(loc, EntityType.ARMOR_STAND); diff --git a/src/main/java/de/nexuslobby/modules/gadgets/FreezeRay.java b/src/main/java/de/nexuslobby/modules/gadgets/FreezeRay.java index 8815a66..71e6892 100644 --- a/src/main/java/de/nexuslobby/modules/gadgets/FreezeRay.java +++ b/src/main/java/de/nexuslobby/modules/gadgets/FreezeRay.java @@ -14,7 +14,6 @@ import java.util.UUID; public class FreezeRay { - // FIX: private statt public – Zugriff nur über isFrozen() und unfreeze() private static final Set frozenPlayers = new HashSet<>(); public static boolean isFrozen(UUID uuid) { return frozenPlayers.contains(uuid); } @@ -26,25 +25,28 @@ public class FreezeRay { Location start = shooter.getEyeLocation(); Vector direction = start.getDirection(); - // Sound beim Schießen shooter.getWorld().playSound(start, Sound.ENTITY_SNOW_GOLEM_SHOOT, 1.0f, 1.5f); - // Wir prüfen in 0.3er Schritten bis zu 15 Blöcke weit for (double d = 0; d < 15; d += 0.3) { Location point = start.clone().add(direction.clone().multiply(d)); - - // Partikel-Strahl sichtbar machen + point.getWorld().spawnParticle(Particle.SNOWFLAKE, point, 1, 0, 0, 0, 0); - // Prüfung auf Spieler im Umkreis von 0.8 Blöcken (etwas großzügiger) for (Entity entity : point.getWorld().getNearbyEntities(point, 0.8, 0.8, 0.8)) { if (entity instanceof Player target && target != shooter) { + // Schutz: Parkour-Spieler und Admins mit aktivem Gadget-Schutz + if (GadgetShield.isProtected(target)) { + String reason = GadgetShield.isAdminShielded(target) + ? "Dieser Spieler hat Gadget-Schutz aktiviert." + : "Dieser Spieler ist gerade im Parkour."; + shooter.sendMessage("§8[§6Nexus§8] §7" + reason); + return; + } applyFreeze(target); - return; // Stoppt den Strahl beim ersten Treffer + return; } } - - // Stoppe den Strahl, falls er eine Wand trifft + if (point.getBlock().getType().isSolid()) { break; } @@ -55,11 +57,8 @@ public class FreezeRay { if (frozenPlayers.contains(target.getUniqueId())) return; frozenPlayers.add(target.getUniqueId()); - - // Fixiere die Position für den Stasis-Effekt final Location freezeLocation = target.getLocation(); - - // Feedback für getroffenen Spieler + target.sendMessage("§8[§6Nexus§8] §bDu wurdest eingefroren!"); target.getWorld().playSound(target.getLocation(), Sound.BLOCK_GLASS_BREAK, 1.0f, 0.5f); @@ -67,35 +66,37 @@ public class FreezeRay { int ticks = 0; @Override public void run() { - // Sicherheitscheck: Ist der Spieler noch online? - if (!target.isOnline() || ticks >= 60) { + if (!target.isOnline() || ticks >= 60) { frozenPlayers.remove(target.getUniqueId()); this.cancel(); return; } - - // Stasis-Effekt: Bewegung auf 0 setzen und Position fixieren + + // Falls der Spieler während des Einfrierens geschützt wird → sofort freigeben + if (GadgetShield.isProtected(target)) { + frozenPlayers.remove(target.getUniqueId()); + this.cancel(); + return; + } + target.setVelocity(new Vector(0, 0, 0)); - - // Alle 2 Ticks zurückteleportieren, falls er versucht zu laufen - // (Behält die Blickrichtung des Spielers bei) + Location current = target.getLocation(); if (current.getX() != freezeLocation.getX() || current.getZ() != freezeLocation.getZ()) { freezeLocation.setYaw(current.getYaw()); freezeLocation.setPitch(current.getPitch()); target.teleport(freezeLocation); } - - // Optischer Käfig (Ring-Effekt) + Location loc = target.getLocation(); for (int i = 0; i < 8; i++) { double angle = i * Math.PI / 4; double x = Math.cos(angle) * 0.7; double z = Math.sin(angle) * 0.7; - loc.getWorld().spawnParticle(Particle.SNOWFLAKE, loc.clone().add(x, 1, z), 1, 0, 0, 0, 0); + loc.getWorld().spawnParticle(Particle.SNOWFLAKE, loc.clone().add(x, 1, z), 1, 0, 0, 0, 0); loc.getWorld().spawnParticle(Particle.SNOWFLAKE, loc.clone().add(x, 0.2, z), 1, 0, 0, 0, 0); } - + ticks += 2; } }.runTaskTimer(NexusLobby.getInstance(), 0L, 2L); diff --git a/src/main/java/de/nexuslobby/modules/gadgets/GadgetModule.java b/src/main/java/de/nexuslobby/modules/gadgets/GadgetModule.java index d0d69f2..2ed5b69 100644 --- a/src/main/java/de/nexuslobby/modules/gadgets/GadgetModule.java +++ b/src/main/java/de/nexuslobby/modules/gadgets/GadgetModule.java @@ -11,10 +11,12 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; +import org.bukkit.event.entity.PlayerLeashEntityEvent; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.player.PlayerFishEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerUnleashEntityEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; @@ -30,16 +32,28 @@ import java.util.UUID; public class GadgetModule implements Module, Listener { - private final Map activeBalloons = new HashMap<>(); - private final Map activeEffects = new HashMap<>(); - private final Set activeShields = new HashSet<>(); + private final Map activeBalloons = new HashMap<>(); + private final Map activeEffects = new HashMap<>(); + private final Set activeShields = new HashSet<>(); - private final String MAIN_TITLE = "§b§lGadgets §8- §7Menü"; - private final String BALLOON_TITLE = "§b§lGadgets §8- §eBallons"; + // ── Cooldowns ───────────────────────────────────────────────────────────── + /** Letzter Einsatz-Zeitstempel pro Spieler */ + private final Map meteorCooldowns = new HashMap<>(); + private final Map freezeCooldowns = new HashMap<>(); + private final Map grapplingCooldowns = new HashMap<>(); + + /** Cooldown-Dauer in Millisekunden */ + private static final long METEOR_CD_MS = 15_000L; // 15 Sekunden + private static final long FREEZE_CD_MS = 10_000L; // 10 Sekunden + private static final long GRAPPLING_CD_MS = 3_000L; // 3 Sekunden + // ────────────────────────────────────────────────────────────────────────── + + private final String MAIN_TITLE = "§b§lGadgets §8- §7Menü"; + private final String BALLOON_TITLE = "§b§lGadgets §8- §eBallons"; private final String PARTICLE_TITLE = "§b§lGadgets §8- §dPartikel"; - private final String FUN_TITLE = "§b§lGadgets §8- §6Lustiges"; - private final String HAT_TITLE = "§b§lGadgets §8- §aHüte & Köpfe"; - private final String PET_TITLE = "§b§lGadgets §8- §dBegleiter"; + private final String FUN_TITLE = "§b§lGadgets §8- §6Lustiges"; + private final String HAT_TITLE = "§b§lGadgets §8- §aHüte & Köpfe"; + private final String PET_TITLE = "§b§lGadgets §8- §dBegleiter"; @Override public String getName() { return "Gadgets"; } @@ -47,13 +61,11 @@ public class GadgetModule implements Module, Listener { @Override public void onEnable() { Bukkit.getPluginManager().registerEvents(this, NexusLobby.getInstance()); - // FIX: PetManager-Listener korrekt registrieren (war vorher toter Code) PetManager.register(); - + Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> { PetManager.updatePets(); activeBalloons.values().forEach(Balloon::update); - for (Player p : Bukkit.getOnlinePlayers()) { UUID uuid = p.getUniqueId(); handleSpecialHatEffects(p); @@ -63,24 +75,115 @@ public class GadgetModule implements Module, Listener { }, 1L, 1L); } + // ───────────────────────────────────────────────────────────────────────── + // Cooldown-Hilfsmethoden + // ───────────────────────────────────────────────────────────────────────── + + /** + * Prüft ob der Spieler noch im Cooldown ist. + * @return true → Gadget darf benutzt werden (Cooldown abgelaufen / nicht gesetzt) + * false → Cooldown noch aktiv, Nachricht wird gesendet + */ + private boolean checkAndSetCooldown(Player player, Map cdMap, long durationMs, String gadgetName) { + long now = System.currentTimeMillis(); + long last = cdMap.getOrDefault(player.getUniqueId(), 0L); + long remaining = durationMs - (now - last); + if (remaining > 0) { + long secLeft = (long) Math.ceil(remaining / 1000.0); + player.sendMessage("§8[§6Nexus§8] §c" + gadgetName + " §7hat noch §e" + secLeft + "s §7Cooldown."); + player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 0.5f, 0.8f); + return false; + } + cdMap.put(player.getUniqueId(), now); + return true; + } + + // ───────────────────────────────────────────────────────────────────────── + // Event Handler + // ───────────────────────────────────────────────────────────────────────── + + /** + * Verhindert Rechtsklick auf den unsichtbaren Ballon-Pig + * (verhindert Leine lösen, Inventar-Öffnen, etc.). + */ + @EventHandler + public void onInteractAtEntity(org.bukkit.event.player.PlayerInteractAtEntityEvent event) { + if (event.getRightClicked().getScoreboardTags().contains("nexus_balloon")) { + event.setCancelled(true); + } + } + + /** + * Verhindert das Ablösen der Leine vom Ballon-Pig durch Rechtsklick. + * Ohne diesen Handler wird die Leine gedroppt und der Ballon verschwindet. + */ + @EventHandler + public void onUnleash(PlayerUnleashEntityEvent event) { + if (event.getEntity().getScoreboardTags().contains("nexus_balloon")) { + event.setCancelled(true); + } + } + @EventHandler public void onInteract(PlayerInteractEvent event) { ItemStack item = event.getItem(); if (item == null || !item.hasItemMeta() || !item.getItemMeta().hasDisplayName()) return; String name = item.getItemMeta().getDisplayName(); - - if (event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) { - if (name.equals("§b§lFreeze-Ray")) { - FreezeRay.shoot(event.getPlayer()); - event.setCancelled(true); - } else if (name.equals("§6§lPaintball-Gun")) { - PaintballGun.shoot(event.getPlayer()); - event.setCancelled(true); - } else if (name.equals("§c§lMeteorit")) { - MeteorStrike.launch(event.getPlayer()); + + if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) return; + + Player player = event.getPlayer(); + + // Wenn der Schütze selbst geschützt ist (Parkour oder Admin-Schutz) → kampfbezogene Gadgets sperren + if (GadgetShield.isProtected(player)) { + if (name.equals("§b§lFreeze-Ray") || name.equals("§c§lMeteorit")) { + player.sendMessage("§8[§6Parkour§8] §cGadgets können während des Parkours nicht benutzt werden."); event.setCancelled(true); + return; } } + + if (name.equals("§b§lFreeze-Ray")) { + event.setCancelled(true); + if (checkAndSetCooldown(player, freezeCooldowns, FREEZE_CD_MS, "§b§lFreeze-Ray")) { + FreezeRay.shoot(player); + } + + } else if (name.equals("§6§lPaintball-Gun")) { + PaintballGun.shoot(player); + event.setCancelled(true); + + } else if (name.equals("§c§lMeteorit")) { + event.setCancelled(true); + if (checkAndSetCooldown(player, meteorCooldowns, METEOR_CD_MS, "§c§lMeteorit")) { + MeteorStrike.launch(player); + } + } + } + + @EventHandler + public void onFish(PlayerFishEvent event) { + Player player = event.getPlayer(); + ItemStack item = player.getInventory().getItemInMainHand(); + if (item.getType() != Material.FISHING_ROD || !item.hasItemMeta() || !item.getItemMeta().hasDisplayName()) return; + if (!item.getItemMeta().getDisplayName().equals("§b§lEnterhaken")) return; + + if (event.getState() == PlayerFishEvent.State.IN_GROUND + || event.getState() == PlayerFishEvent.State.REEL_IN + || event.getState() == PlayerFishEvent.State.CAUGHT_ENTITY) { + + if (event.getHook() == null) return; + + // Parkour-Check: Schütze im Parkour oder Admin-Schutz → nicht benutzen + if (GadgetShield.isProtected(player)) { + player.sendMessage("§8[§6Nexus§8] §cDer Enterhaken ist gerade nicht verfügbar."); + return; + } + + if (!checkAndSetCooldown(player, grapplingCooldowns, GRAPPLING_CD_MS, "§b§lEnterhaken")) return; + + GrapplingHook.pullPlayer(player, event.getHook().getLocation()); + } } @EventHandler @@ -92,15 +195,19 @@ public class GadgetModule implements Module, Listener { } } + // ───────────────────────────────────────────────────────────────────────── + // GUI + // ───────────────────────────────────────────────────────────────────────── + private void handleSpecialHatEffects(Player p) { ItemStack hat = p.getInventory().getHelmet(); if (hat == null || hat.getType() == Material.AIR) return; switch (hat.getType()) { - case CAMPFIRE -> p.getWorld().spawnParticle(Particle.CAMPFIRE_COSY_SMOKE, p.getLocation().add(0, 2.2, 0), 1, 0.05, 0.05, 0.05, 0.02); - case SPAWNER -> p.getWorld().spawnParticle(Particle.FLAME, p.getLocation().add(0, 2.1, 0), 1, 0.12, 0.12, 0.12, 0.02); + case CAMPFIRE -> p.getWorld().spawnParticle(Particle.CAMPFIRE_COSY_SMOKE, p.getLocation().add(0, 2.2, 0), 1, 0.05, 0.05, 0.05, 0.02); + case SPAWNER -> p.getWorld().spawnParticle(Particle.FLAME, p.getLocation().add(0, 2.1, 0), 1, 0.12, 0.12, 0.12, 0.02); case SEA_LANTERN, BEACON -> p.getWorld().spawnParticle(Particle.END_ROD, p.getLocation().add(0, 2.1, 0), 1, 0.1, 0.1, 0.1, 0.03); - case ENCHANTING_TABLE -> p.getWorld().spawnParticle(Particle.ENCHANT, p.getLocation().add(0, 2.3, 0), 1, 0.2, 0.2, 0.2, 0.5); + case ENCHANTING_TABLE -> p.getWorld().spawnParticle(Particle.ENCHANT, p.getLocation().add(0, 2.3, 0), 1, 0.2, 0.2, 0.2, 0.5); default -> {} } } @@ -108,68 +215,64 @@ public class GadgetModule implements Module, Listener { public void openGUI(Player player) { Inventory gui = Bukkit.createInventory(null, 27, MAIN_TITLE); fillEdges(gui); - - gui.setItem(10, createItem(Material.LEAD, "§e§lBallons", "§7Wähle einen fliegenden Begleiter")); - gui.setItem(11, createItem(Material.GOLDEN_HELMET, "§a§lHüte", "§7Setze dir etwas auf den Kopf")); - gui.setItem(13, createItem(Material.BONE, "§d§lBegleiter", "§7Echte Tiere, die dir folgen")); - gui.setItem(15, createItem(Material.FIREWORK_ROCKET, "§6§lLustiges", "§7Witzige Effekte")); - gui.setItem(16, createItem(Material.NETHER_STAR, "§d§lPartikel", "§7Magische Auren & Effekte")); - - gui.setItem(22, createItem(Material.BARRIER, "§c§lStopp", "§7Alle Gadgets entfernen")); + gui.setItem(10, createItem(Material.LEAD, "§e§lBallons", "§7Wähle einen fliegenden Begleiter")); + gui.setItem(11, createItem(Material.GOLDEN_HELMET, "§a§lHüte", "§7Setze dir etwas auf den Kopf")); + gui.setItem(13, createItem(Material.BONE, "§d§lBegleiter", "§7Echte Tiere, die dir folgen")); + gui.setItem(15, createItem(Material.FIREWORK_ROCKET,"§6§lLustiges", "§7Witzige Effekte")); + gui.setItem(16, createItem(Material.NETHER_STAR, "§d§lPartikel", "§7Magische Auren & Effekte")); + gui.setItem(22, createItem(Material.BARRIER, "§c§lStopp", "§7Alle Gadgets entfernen")); player.openInventory(gui); } private void openHatGUI(Player player) { Inventory gui = Bukkit.createInventory(null, 45, HAT_TITLE); fillEdges(gui); - - gui.setItem(10, createItem(Material.JACK_O_LANTERN, "§6Kürbis-Hut", "§7Es ist immer Halloween!")); - gui.setItem(11, createItem(Material.SEA_LANTERN, "§bMeeres-Leuchten", "§7§oEffekt: Glitzern")); - gui.setItem(12, createItem(Material.GLOWSTONE, "§eGlowstone-Kopf", "§7Werde zur Lampe")); - gui.setItem(13, createItem(Material.TNT, "§cExplosiv-Hut", "§7Vorsicht, heiß!")); - gui.setItem(14, createItem(Material.GLASS, "§fAstronaut", "§7Bereit für den Mond?")); - gui.setItem(15, createItem(Material.DRAGON_HEAD, "§5Enderdrache", "§7Der König der Lüfte")); - gui.setItem(16, createItem(Material.CAKE, "§dKuchen-Kopf", "§7Jeder mag Kuchen!")); - gui.setItem(19, createItem(Material.SLIME_BLOCK, "§aGlibber-Block", "§7Ziemlich klebrig...")); - gui.setItem(20, createItem(Material.MELON, "§aMelonen-Helm", "§7Frisch und saftig")); - gui.setItem(21, createItem(Material.HAY_BLOCK, "§eStrohhut", "§7Sommer auf dem Land")); - gui.setItem(22, createItem(Material.SPAWNER, "§8Monster-Käfig", "§7§oEffekt: Flammen")); - gui.setItem(23, createItem(Material.CRAFTING_TABLE, "§6Werkbank", "§7Immer am Basteln")); - gui.setItem(24, createItem(Material.BOOKSHELF, "§fBücherregal", "§7Ein wahrer Schlaukopf")); - gui.setItem(25, createItem(Material.HONEY_BLOCK, "§6Honig-Hut", "§7Süß und klebrig")); - gui.setItem(28, createItem(Material.GOLD_BLOCK, "§6Gold-Bonze", "§7Zeig was du hast")); - gui.setItem(29, createItem(Material.DIAMOND_ORE, "§bDiamant-Erz", "§7Bau mich bloß nicht ab!")); - gui.setItem(30, createItem(Material.BEACON, "§fLeuchtfeuer", "§7§oEffekt: Glitzern")); - gui.setItem(31, createItem(Material.CONDUIT, "§3Auge des Meeres", "§7Die Macht von Atlantis")); - gui.setItem(32, createItem(Material.ENCHANTING_TABLE, "§dMagier", "§7§oEffekt: Runen")); - gui.setItem(33, createItem(Material.CAMPFIRE, "§cHeißer Kopf", "§7§oEffekt: Rauch")); - gui.setItem(34, createItem(Material.SKELETON_SKULL, "§7Skelett", "§7Ein wenig gruselig")); - - gui.setItem(40, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü")); + gui.setItem(10, createItem(Material.JACK_O_LANTERN, "§6Kürbis-Hut", "§7Es ist immer Halloween!")); + gui.setItem(11, createItem(Material.SEA_LANTERN, "§bMeeres-Leuchten", "§7§oEffekt: Glitzern")); + gui.setItem(12, createItem(Material.GLOWSTONE, "§eGlowstone-Kopf", "§7Werde zur Lampe")); + gui.setItem(13, createItem(Material.TNT, "§cExplosiv-Hut", "§7Vorsicht, heiß!")); + gui.setItem(14, createItem(Material.GLASS, "§fAstronaut", "§7Bereit für den Mond?")); + gui.setItem(15, createItem(Material.DRAGON_HEAD, "§5Enderdrache", "§7Der König der Lüfte")); + gui.setItem(16, createItem(Material.CAKE, "§dKuchen-Kopf", "§7Jeder mag Kuchen!")); + gui.setItem(19, createItem(Material.SLIME_BLOCK, "§aGlibber-Block", "§7Ziemlich klebrig...")); + gui.setItem(20, createItem(Material.MELON, "§aMelonen-Helm", "§7Frisch und saftig")); + gui.setItem(21, createItem(Material.HAY_BLOCK, "§eStrohhut", "§7Sommer auf dem Land")); + gui.setItem(22, createItem(Material.SPAWNER, "§8Monster-Käfig", "§7§oEffekt: Flammen")); + gui.setItem(23, createItem(Material.CRAFTING_TABLE, "§6Werkbank", "§7Immer am Basteln")); + gui.setItem(24, createItem(Material.BOOKSHELF, "§fBücherregal", "§7Ein wahrer Schlaukopf")); + gui.setItem(25, createItem(Material.HONEY_BLOCK, "§6Honig-Hut", "§7Süß und klebrig")); + gui.setItem(28, createItem(Material.GOLD_BLOCK, "§6Gold-Bonze", "§7Zeig was du hast")); + gui.setItem(29, createItem(Material.DIAMOND_ORE, "§bDiamant-Erz", "§7Bau mich bloß nicht ab!")); + gui.setItem(30, createItem(Material.BEACON, "§fLeuchtfeuer", "§7§oEffekt: Glitzern")); + gui.setItem(31, createItem(Material.CONDUIT, "§3Auge des Meeres", "§7Die Macht von Atlantis")); + gui.setItem(32, createItem(Material.ENCHANTING_TABLE,"§dMagier", "§7§oEffekt: Runen")); + gui.setItem(33, createItem(Material.CAMPFIRE, "§cHeißer Kopf", "§7§oEffekt: Rauch")); + gui.setItem(34, createItem(Material.SKELETON_SKULL, "§7Skelett", "§7Ein wenig gruselig")); + gui.setItem(40, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü")); player.openInventory(gui); } private void openPetGUI(Player player) { Inventory gui = Bukkit.createInventory(null, 27, PET_TITLE); fillEdges(gui); - gui.setItem(11, createItem(Material.BONE, "§fWolf", "§7Ein treuer Begleiter")); - gui.setItem(13, createItem(Material.CAT_SPAWN_EGG, "§6Katze", "§7Ein verschmuster Freund")); - gui.setItem(15, createItem(Material.PANDA_SPAWN_EGG, "§aPanda", "§7Ein gemütlicher Zeitgenosse")); - gui.setItem(22, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü")); + gui.setItem(11, createItem(Material.BONE, "§fWolf", "§7Ein treuer Begleiter")); + gui.setItem(13, createItem(Material.CAT_SPAWN_EGG, "§6Katze", "§7Ein verschmuster Freund")); + gui.setItem(15, createItem(Material.PANDA_SPAWN_EGG,"§aPanda", "§7Ein gemütlicher Zeitgenosse")); + gui.setItem(22, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü")); player.openInventory(gui); } private void openBalloonGUI(Player player) { Inventory gui = Bukkit.createInventory(null, 36, BALLOON_TITLE); fillEdges(gui); - Material[] wools = {Material.WHITE_WOOL, Material.ORANGE_WOOL, Material.MAGENTA_WOOL, Material.LIGHT_BLUE_WOOL, - Material.YELLOW_WOOL, Material.LIME_WOOL, Material.PINK_WOOL, Material.GRAY_WOOL, - Material.CYAN_WOOL, Material.PURPLE_WOOL, Material.BLUE_WOOL, Material.BROWN_WOOL, - Material.GREEN_WOOL, Material.RED_WOOL}; + Material[] wools = {Material.WHITE_WOOL, Material.ORANGE_WOOL, Material.MAGENTA_WOOL, + Material.LIGHT_BLUE_WOOL, Material.YELLOW_WOOL, Material.LIME_WOOL, Material.PINK_WOOL, + Material.GRAY_WOOL, Material.CYAN_WOOL, Material.PURPLE_WOOL, Material.BLUE_WOOL, + Material.BROWN_WOOL, Material.GREEN_WOOL, Material.RED_WOOL}; int slot = 10; for (Material m : wools) { if (slot == 17) slot = 19; - gui.setItem(slot++, createItem(m, "§fBallon: " + m.name().replace("_WOOL", ""), "§7Klicke zum Ausrüsten")); + gui.setItem(slot++, createItem(m, "§fBallon: " + m.name().replace("_WOOL",""), "§7Klicke zum Ausrüsten")); } gui.setItem(31, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü")); player.openInventory(gui); @@ -178,23 +281,23 @@ public class GadgetModule implements Module, Listener { private void openParticleGUI(Player player) { Inventory gui = Bukkit.createInventory(null, 27, PARTICLE_TITLE); fillEdges(gui); - gui.setItem(11, createItem(Material.POPPY, "§cHerzchen-Aura", "§7Verbreite Liebe in der Lobby")); - gui.setItem(13, createItem(Material.BLAZE_POWDER, "§6Flammen-Ring", "§7Lass es brennen!")); - gui.setItem(15, createItem(Material.WATER_BUCKET, "§bRegenwolke", "§7Deine persönliche Abkühlung")); - gui.setItem(22, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü")); + gui.setItem(11, createItem(Material.POPPY, "§cHerzchen-Aura", "§7Verbreite Liebe in der Lobby")); + gui.setItem(13, createItem(Material.BLAZE_POWDER, "§6Flammen-Ring", "§7Lass es brennen!")); + gui.setItem(15, createItem(Material.WATER_BUCKET, "§bRegenwolke", "§7Deine persönliche Abkühlung")); + gui.setItem(22, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü")); player.openInventory(gui); } private void openFunGUI(Player player) { Inventory gui = Bukkit.createInventory(null, 27, FUN_TITLE); fillEdges(gui); - gui.setItem(10, createItem(Material.FISHING_ROD, "§b§lEnterhaken", "§7Zieh dich durch die Luft!")); - gui.setItem(11, createItem(Material.PACKED_ICE, "§b§lFreeze-Ray", "§7Friere andere Spieler ein!")); - gui.setItem(12, createItem(Material.GOLDEN_HOE, "§6§lPaintball-Gun", "§7Male die Lobby bunt aus!")); - gui.setItem(14, createItem(Material.FIRE_CHARGE, "§c§lMeteorit", "§7Lass es krachen!")); - gui.setItem(15, createItem(Material.SHIELD, "§5§lSchutzzone", "§7Halte andere auf Distanz")); - gui.setItem(16, createItem(Material.EGG, "§f§lChicken-Rain", "§7Gack-Gack! Hühner überall!")); - gui.setItem(22, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü")); + gui.setItem(10, createItem(Material.FISHING_ROD, "§b§lEnterhaken", "§7Zieh dich durch die Luft! §8(3s CD)")); + gui.setItem(11, createItem(Material.PACKED_ICE, "§b§lFreeze-Ray", "§7Friere andere ein! §8(10s CD)")); + gui.setItem(12, createItem(Material.GOLDEN_HOE, "§6§lPaintball-Gun","§7Male die Lobby bunt aus!")); + gui.setItem(14, createItem(Material.FIRE_CHARGE, "§c§lMeteorit", "§7Lass es krachen! §8(15s CD)")); + gui.setItem(15, createItem(Material.SHIELD, "§5§lSchutzzone", "§7Halte andere auf Distanz")); + gui.setItem(16, createItem(Material.EGG, "§f§lChicken-Rain","§7Gack-Gack! Hühner überall!")); + gui.setItem(22, createItem(Material.ARROW, "§7Zurück", "§8Zum Hauptmenü")); player.openInventory(gui); } @@ -210,25 +313,23 @@ public class GadgetModule implements Module, Listener { if (item.getType() == Material.ARROW) { openGUI(player); return; } if (title.equals(MAIN_TITLE)) { - if (item.getType() == Material.LEAD) openBalloonGUI(player); - else if (item.getType() == Material.GOLDEN_HELMET) openHatGUI(player); - else if (item.getType() == Material.BONE) openPetGUI(player); - else if (item.getType() == Material.NETHER_STAR) openParticleGUI(player); + if (item.getType() == Material.LEAD) openBalloonGUI(player); + else if (item.getType() == Material.GOLDEN_HELMET) openHatGUI(player); + else if (item.getType() == Material.BONE) openPetGUI(player); + else if (item.getType() == Material.NETHER_STAR) openParticleGUI(player); else if (item.getType() == Material.FIREWORK_ROCKET) openFunGUI(player); - else if (item.getType() == Material.BARRIER) { removeGadgets(player); player.closeInventory(); } + else if (item.getType() == Material.BARRIER) { removeGadgets(player); player.closeInventory(); } } else if (title.equals(HAT_TITLE)) { if (item.getType() != Material.GRAY_STAINED_GLASS_PANE) { - String hatName = item.getType().name(); - if (item.hasItemMeta() && item.getItemMeta().hasDisplayName()) { - hatName = item.getItemMeta().getDisplayName(); - } + String hatName = item.hasItemMeta() && item.getItemMeta().hasDisplayName() + ? item.getItemMeta().getDisplayName() : item.getType().name(); HatManager.setHat(player, item.getType(), hatName); player.playSound(player.getLocation(), Sound.ITEM_ARMOR_EQUIP_GENERIC, 1, 1); player.closeInventory(); } } else if (title.equals(PET_TITLE)) { - if (item.getType() == Material.BONE) PetManager.spawnEntityPet(player, "WOLF"); - else if (item.getType() == Material.CAT_SPAWN_EGG) PetManager.spawnEntityPet(player, "CAT"); + if (item.getType() == Material.BONE) PetManager.spawnEntityPet(player, "WOLF"); + else if (item.getType() == Material.CAT_SPAWN_EGG) PetManager.spawnEntityPet(player, "CAT"); else if (item.getType() == Material.PANDA_SPAWN_EGG) PetManager.spawnEntityPet(player, "PANDA"); player.sendMessage("§8[§6Nexus§8] §dDein Pet wurde gerufen!"); player.closeInventory(); @@ -240,7 +341,7 @@ public class GadgetModule implements Module, Listener { player.closeInventory(); } } else if (title.equals(PARTICLE_TITLE)) { - if (item.getType() == Material.POPPY) activeEffects.put(player.getUniqueId(), new ParticleEffect("hearts")); + if (item.getType() == Material.POPPY) activeEffects.put(player.getUniqueId(), new ParticleEffect("hearts")); else if (item.getType() == Material.BLAZE_POWDER) activeEffects.put(player.getUniqueId(), new ParticleEffect("flames")); else if (item.getType() == Material.WATER_BUCKET) activeEffects.put(player.getUniqueId(), new ParticleEffect("cloud")); player.sendMessage("§8[§6Nexus§8] §aPartikel aktiviert!"); @@ -251,16 +352,16 @@ public class GadgetModule implements Module, Listener { player.sendMessage("§8[§6Nexus§8] §fHühnerregen gestartet!"); player.closeInventory(); } else if (item.getType() == Material.FISHING_ROD) { - player.getInventory().addItem(createItem(Material.FISHING_ROD, "§b§lEnterhaken", "§7Rechtsklick zum Katapultieren")); + player.getInventory().addItem(createItem(Material.FISHING_ROD, "§b§lEnterhaken", "§7Rechtsklick zum Katapultieren §8(3s CD)")); player.closeInventory(); } else if (item.getType() == Material.PACKED_ICE) { - player.getInventory().addItem(createItem(Material.PACKED_ICE, "§b§lFreeze-Ray", "§7Rechtsklick zum Einfrieren")); + player.getInventory().addItem(createItem(Material.PACKED_ICE, "§b§lFreeze-Ray", "§7Rechtsklick zum Einfrieren §8(10s CD)")); player.closeInventory(); } else if (item.getType() == Material.GOLDEN_HOE) { player.getInventory().addItem(createItem(Material.GOLDEN_HOE, "§6§lPaintball-Gun", "§7Rechtsklick zum Schießen")); player.closeInventory(); } else if (item.getType() == Material.FIRE_CHARGE) { - player.getInventory().addItem(createItem(Material.FIRE_CHARGE, "§c§lMeteorit", "§7Rechtsklick zum Markieren")); + player.getInventory().addItem(createItem(Material.FIRE_CHARGE, "§c§lMeteorit", "§7Rechtsklick zum Markieren §8(15s CD)")); player.closeInventory(); } else if (item.getType() == Material.SHIELD) { if (activeShields.contains(player.getUniqueId())) { @@ -275,20 +376,6 @@ public class GadgetModule implements Module, Listener { } } - @EventHandler - public void onFish(PlayerFishEvent event) { - Player player = event.getPlayer(); - ItemStack item = player.getInventory().getItemInMainHand(); - if (item.getType() == Material.FISHING_ROD && item.hasItemMeta() && item.getItemMeta().hasDisplayName() - && item.getItemMeta().getDisplayName().equals("§b§lEnterhaken")) { - if (event.getState() == PlayerFishEvent.State.IN_GROUND || event.getState() == PlayerFishEvent.State.REEL_IN || event.getState() == PlayerFishEvent.State.CAUGHT_ENTITY) { - if (event.getHook() != null) { - GrapplingHook.pullPlayer(player, event.getHook().getLocation()); - } - } - } - } - private void removeGadgets(Player player) { if (activeBalloons.containsKey(player.getUniqueId())) { activeBalloons.get(player.getUniqueId()).remove(); @@ -307,7 +394,6 @@ public class GadgetModule implements Module, Listener { } private void fillEdges(Inventory inv) { - // Bedrock braucht einen Space, um den Namen korrekt anzuzeigen ItemStack glass = createItem(Material.GRAY_STAINED_GLASS_PANE, " ", null); for (int i = 0; i < inv.getSize(); i++) { if (i < 9 || i >= inv.getSize() - 9 || i % 9 == 0 || (i + 1) % 9 == 0) inv.setItem(i, glass); @@ -319,19 +405,14 @@ public class GadgetModule implements Module, Listener { ItemMeta meta = item.getItemMeta(); if (meta != null) { meta.setDisplayName(name); - - // WICHTIG FÜR BEDROCK: Saubere ArrayList für Lore List l = new ArrayList<>(); if (lore != null && !lore.isEmpty()) { l.add(ChatColor.translateAlternateColorCodes('&', lore)); meta.setLore(l); } - - // VERSTECKT ATTRIBUTE: Verhindert "+Armor" etc., was bei Bedrock die Lore überdeckt meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES); meta.addItemFlags(ItemFlag.HIDE_UNBREAKABLE); meta.addItemFlags(ItemFlag.HIDE_ENCHANTS); - item.setItemMeta(meta); } return item; @@ -346,5 +427,6 @@ public class GadgetModule implements Module, Listener { activeBalloons.clear(); activeEffects.clear(); activeShields.clear(); + GadgetShield.clear(); } } \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/gadgets/GadgetShield.java b/src/main/java/de/nexuslobby/modules/gadgets/GadgetShield.java new file mode 100644 index 0000000..72318e5 --- /dev/null +++ b/src/main/java/de/nexuslobby/modules/gadgets/GadgetShield.java @@ -0,0 +1,59 @@ +package de.nexuslobby.modules.gadgets; + +import de.nexuslobby.NexusLobby; +import de.nexuslobby.modules.parkour.ParkourManager; +import org.bukkit.Sound; +import org.bukkit.entity.Player; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * Verwaltet den Gadget-Schutz für Admins und Parkour-Spieler. + * Eigenständige Klasse – kein Eingriff in GadgetModule nötig. + */ +public class GadgetShield { + + /** UUIDs der Admins, die ihren Gadget-Schutz aktiviert haben */ + private static final Set shielded = new HashSet<>(); + + /** + * Schaltet den Gadget-Schutz ein oder aus. + * Gibt true zurück wenn der Schutz jetzt aktiv ist. + */ + public static boolean toggle(Player player) { + UUID uuid = player.getUniqueId(); + if (shielded.contains(uuid)) { + shielded.remove(uuid); + player.sendMessage("§8[§6Nexus§8] §cGadget-Schutz §7deaktiviert."); + player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f); + return false; + } else { + shielded.add(uuid); + player.sendMessage("§8[§6Nexus§8] §aGadget-Schutz §7aktiviert. §8Du bist nun immun gegen Gadgets."); + player.playSound(player.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 1f, 1.5f); + return true; + } + } + + /** Gibt zurück ob ein Spieler explizit durch den Admin-Schutz geschützt ist. */ + public static boolean isAdminShielded(Player player) { + return shielded.contains(player.getUniqueId()); + } + + /** + * Gibt zurück ob ein Spieler durch irgendeinen Schutz immun gegen Gadgets ist + * (Admin-Schutz ODER aktiver Parkour-Run). + */ + public static boolean isProtected(Player player) { + if (shielded.contains(player.getUniqueId())) return true; + ParkourManager pm = NexusLobby.getInstance().getParkourManager(); + return pm != null && pm.isIngame(player); + } + + /** Beim Server-Stop / Plugin-Disable aufrufen */ + public static void clear() { + shielded.clear(); + } +} \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/gadgets/MeteorStrike.java b/src/main/java/de/nexuslobby/modules/gadgets/MeteorStrike.java index 98fd69a..3ea3e9c 100644 --- a/src/main/java/de/nexuslobby/modules/gadgets/MeteorStrike.java +++ b/src/main/java/de/nexuslobby/modules/gadgets/MeteorStrike.java @@ -30,14 +30,26 @@ public class MeteorStrike { shooter.sendMessage("§8[§6Nexus§8] §cMeteorit im Anflug..."); org.bukkit.Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> { - // EXPLOSION_EMITTER ist der moderne Name für HUGE_EXPLOSION finalTarget.getWorld().spawnParticle(Particle.EXPLOSION_EMITTER, finalTarget, 1); finalTarget.getWorld().spawnParticle(Particle.LAVA, finalTarget, 30, 0.5, 0.5, 0.5, 0.1); finalTarget.getWorld().playSound(finalTarget, Sound.ENTITY_GENERIC_EXPLODE, 1.0f, 0.8f); for (Entity entity : finalTarget.getWorld().getNearbyEntities(finalTarget, 4, 4, 4)) { if (entity instanceof Player p) { - Vector v = p.getLocation().toVector().subtract(finalTarget.toVector()).normalize().multiply(1.5).setY(0.5); + // Schutz: Admins mit Gadget-Schutz und Parkour-Spieler werden nicht weggeschleudert + if (GadgetShield.isProtected(p)) { + if (GadgetShield.isAdminShielded(p)) { + p.sendMessage("§8[§6Nexus§8] §7Dein Gadget-Schutzschild hat den Meteoriten abgelenkt!"); + } else { + p.sendMessage("§8[§6Parkour§8] §7Dein Parkour-Schutzschild hat den Meteoriten abgelenkt!"); + } + continue; + } + Vector v = p.getLocation().toVector() + .subtract(finalTarget.toVector()) + .normalize() + .multiply(1.5) + .setY(0.5); p.setVelocity(v); p.sendMessage("§cBUMM!"); } diff --git a/src/main/java/de/nexuslobby/modules/gadgets/ShieldTask.java b/src/main/java/de/nexuslobby/modules/gadgets/ShieldTask.java index b5bea1d..3a37d2b 100644 --- a/src/main/java/de/nexuslobby/modules/gadgets/ShieldTask.java +++ b/src/main/java/de/nexuslobby/modules/gadgets/ShieldTask.java @@ -6,8 +6,19 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.util.Vector; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + public class ShieldTask { + /** + * Cooldown: Ein Spieler darf nur alle 3 Sekunden weggedrückt werden. + * Verhindert das permanente "Kleben" am Schutzschild-Träger. + */ + private static final Map pushCooldowns = new HashMap<>(); + private static final long PUSH_COOLDOWN_MS = 3_000L; + public static void handleShield(Player owner) { // Erzeuge einen Partikel-Ring um den Spieler for (double i = 0; i < Math.PI * 2; i += Math.PI / 8) { @@ -16,17 +27,27 @@ public class ShieldTask { owner.getWorld().spawnParticle(Particle.WITCH, owner.getLocation().add(x, 0.5, z), 1, 0, 0, 0, 0); } + long now = System.currentTimeMillis(); + // Stoße andere Spieler weg for (Entity entity : owner.getNearbyEntities(2.2, 2.0, 2.2)) { - if (entity instanceof Player && entity != owner) { - Player target = (Player) entity; - - Vector direction = target.getLocation().toVector().subtract(owner.getLocation().toVector()).normalize(); - direction.multiply(0.4).setY(0.2); - - target.setVelocity(direction); - target.playSound(target.getLocation(), Sound.ENTITY_CHICKEN_EGG, 0.5f, 0.5f); - } + if (!(entity instanceof Player target) || entity == owner) continue; + + // FIX: OP-Admins mit aktivem Gadget-Schutz und Parkour-Spieler sind immun + if (GadgetShield.isProtected(target)) continue; + + // FIX: 3-Sekunden-Cooldown – jedes Ziel wird max. 1x alle 3s weggedrückt + long lastPush = pushCooldowns.getOrDefault(target.getUniqueId(), 0L); + if (now - lastPush < PUSH_COOLDOWN_MS) continue; + pushCooldowns.put(target.getUniqueId(), now); + + Vector direction = target.getLocation().toVector() + .subtract(owner.getLocation().toVector()) + .normalize(); + direction.multiply(0.4).setY(0.2); + + target.setVelocity(direction); + target.playSound(target.getLocation(), Sound.ENTITY_CHICKEN_EGG, 0.5f, 0.5f); } } } \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/parkour/ParkourListener.java b/src/main/java/de/nexuslobby/modules/parkour/ParkourListener.java index 7b259fd..818a6df 100644 --- a/src/main/java/de/nexuslobby/modules/parkour/ParkourListener.java +++ b/src/main/java/de/nexuslobby/modules/parkour/ParkourListener.java @@ -18,48 +18,43 @@ import java.util.UUID; public class ParkourListener implements Listener { private final ParkourManager manager; - private final String NPC_TAG = "parkour_npc"; + private final String NPC_TAG = "parkour_npc"; private final HashMap startCooldown = new HashMap<>(); public ParkourListener(ParkourManager manager) { this.manager = manager; } - /** - * Startet den Parkour per Rechtsklick auf den ArmorStand - */ @EventHandler public void onInteract(PlayerInteractAtEntityEvent event) { if (!(event.getRightClicked() instanceof ArmorStand as)) return; - - // Prüfen, ob der ArmorStand den richtigen Tag hat - if (as.getScoreboardTags().contains(NPC_TAG)) { - Player player = event.getPlayer(); - - // Abbrechen, wenn der Spieler schon im Parkour ist - if (manager.isIngame(player)) return; + if (!as.getScoreboardTags().contains(NPC_TAG)) return; - // Cooldown-Check (3 Sekunden) - if (startCooldown.containsKey(player.getUniqueId())) { - if (System.currentTimeMillis() - startCooldown.get(player.getUniqueId()) < 3000) { - player.sendMessage("§cBitte warte einen Moment, bevor du erneut startest."); - return; - } + Player player = event.getPlayer(); + event.setCancelled(true); + + if (manager.isIngame(player)) return; + + // Cooldown-Check (3 Sekunden) + if (startCooldown.containsKey(player.getUniqueId())) { + if (System.currentTimeMillis() - startCooldown.get(player.getUniqueId()) < 3000) { + player.sendMessage("§cBitte warte einen Moment, bevor du erneut startest."); + return; } - - // Parkour starten - manager.startParkour(player, as.getLocation()); - player.sendMessage("§8[§6Parkour§8] §eViel Erfolg! Erreiche das Ziel so schnell wie möglich."); - player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f); - - // Event abbrechen, damit man keine Ausrüstung vom ArmorStand klaut - event.setCancelled(true); } + + manager.startParkour(player, as.getLocation()); + // Strecken-Nummer dem Spieler mitteilen + int track = manager.getActiveTrack(player); + if (track != -1) { + player.sendMessage("§8[§6Parkour§8] §eViel Erfolg auf §bStrecke " + track + "§e! Erreiche das Ziel so schnell wie möglich."); + } + player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f); } @EventHandler public void onMove(PlayerMoveEvent event) { - // Performance: Nur berechnen, wenn ein voller Block gewechselt wurde + // Performance: Nur bei Block-Wechsel berechnen if (event.getFrom().getBlockX() == event.getTo().getBlockX() && event.getFrom().getBlockY() == event.getTo().getBlockY() && event.getFrom().getBlockZ() == event.getTo().getBlockZ()) return; @@ -70,8 +65,7 @@ public class ParkourListener implements Listener { Location loc = player.getLocation(); // --- 1. ABSTURZ-CHECK --- - // Passe die Höhe '50' an deine Map an! - if (loc.getY() < 50) { + if (loc.getY() < 50) { Location lastCp = manager.getCheckpoint(player); if (lastCp != null) { player.teleport(lastCp); @@ -81,27 +75,25 @@ public class ParkourListener implements Listener { return; } - // --- 2. CHECKPOINT-CHECK --- - List cps = manager.getOrderedCheckpoints(); + // --- 2. CHECKPOINT-CHECK (Track-spezifisch) --- + List cps = manager.getOrderedCheckpoints(player); for (int i = 0; i < cps.size(); i++) { if (isNearby(loc, cps.get(i))) { manager.reachCheckpoint(player, i); } } - // --- 3. ZIEL-CHECK --- - Location finish = manager.getFinishLocation(); + // --- 3. ZIEL-CHECK (Track-spezifisch) --- + Location finish = manager.getFinishLocation(player); if (finish != null && isNearby(loc, finish)) { manager.finishParkour(player); - // Nach dem Ziel setzen wir einen Cooldown startCooldown.put(player.getUniqueId(), System.currentTimeMillis()); } } private boolean isNearby(Location playerLoc, Location targetLoc) { if (playerLoc.getWorld() == null || !playerLoc.getWorld().equals(targetLoc.getWorld())) return false; - // 2.25 entspricht einem Radius von ca. 1.5 Blöcken - return playerLoc.distanceSquared(targetLoc) <= 2.25; + return playerLoc.distanceSquared(targetLoc) <= 2.25; // ~1.5 Blöcke Radius } @EventHandler diff --git a/src/main/java/de/nexuslobby/modules/parkour/ParkourManager.java b/src/main/java/de/nexuslobby/modules/parkour/ParkourManager.java index 976e45f..40a0968 100644 --- a/src/main/java/de/nexuslobby/modules/parkour/ParkourManager.java +++ b/src/main/java/de/nexuslobby/modules/parkour/ParkourManager.java @@ -17,71 +17,186 @@ import java.io.IOException; import java.util.*; import java.util.stream.Collectors; +/** + * ParkourManager – unterstützt zwei Strecken (Track 1 & 2). + * + * Config-Struktur (parkour.yml): + * tracks: + * 1: + * start: + * finish: + * checkpoints: + * 1: + * 2: + * ... + * 2: + * start: + * finish: + * checkpoints: + * 1: + * ... + * besttimes: + * : + * names: + * : + * + * Beim Start wird zufällig eine der beiden konfigurierten Strecken ausgewählt. + * Beide Strecken müssen die gleiche Anzahl an Checkpoints haben – andernfalls + * wird nur die vollständige gewählt. + */ public class ParkourManager { private final NexusLobby plugin; private final File file; private FileConfiguration config; - - private final Map startTime = new HashMap<>(); - private final Map lastCheckpointLoc = new HashMap<>(); - private final Map nextCheckpointIndex = new HashMap<>(); + + // Aktive Läufe + private final Map startTime = new HashMap<>(); + private final Map lastCheckpointLoc = new HashMap<>(); + private final Map nextCheckpointIndex= new HashMap<>(); + /** Welche Track-Nummer (1 oder 2) der Spieler gerade läuft */ + private final Map activeTrack = new HashMap<>(); + + private final Random random = new Random(); public ParkourManager(NexusLobby plugin) { this.plugin = plugin; - this.file = new File(plugin.getDataFolder(), "parkour.yml"); + this.file = new File(plugin.getDataFolder(), "parkour.yml"); loadConfig(); startParticleTask(); } + // ───────────────────────────────────────────────────────────────────────── + // Config I/O + // ───────────────────────────────────────────────────────────────────────── + private void loadConfig() { if (!file.exists()) { file.getParentFile().mkdirs(); - try { - file.createNewFile(); - } catch (IOException e) { + try { + file.createNewFile(); + } catch (IOException e) { plugin.getLogger().severe("Fehler beim Erstellen der Parkour-Datei: " + e.getMessage()); } } config = YamlConfiguration.loadConfiguration(file); } - public void setCheckpoint(Player player, Location loc) { - int nextId = 1; - if (config.contains("locations.checkpoints") && config.getConfigurationSection("locations.checkpoints") != null) { - nextId = config.getConfigurationSection("locations.checkpoints").getKeys(false).size() + 1; + private void save() { + try { + config.save(file); + } catch (IOException e) { + plugin.getLogger().severe("Fehler beim Speichern der Parkour-Config: " + e.getMessage()); } - - addCheckpointLocation(String.valueOf(nextId), loc); - player.sendMessage("§8[§6Parkour§8] §7Checkpoint §e#" + nextId + " §7an deiner Position gesetzt."); + } + + // ───────────────────────────────────────────────────────────────────────── + // Setup-Befehle (Admin) + // ───────────────────────────────────────────────────────────────────────── + + public void setStartLocation(Player player, int track) { + config.set("tracks." + track + ".start", player.getLocation()); + save(); + player.sendMessage("§8[§6Parkour§8] §7Start von §eStrecke " + track + " §7an deiner Position gesetzt."); + } + + public void setFinishLocation(Player player, int track) { + config.set("tracks." + track + ".finish", player.getLocation()); + save(); + player.sendMessage("§8[§6Parkour§8] §7Ziel von §eStrecke " + track + " §7an deiner Position gesetzt."); + } + + public void setCheckpoint(Player player, int track) { + String cpBase = "tracks." + track + ".checkpoints"; + int nextId = 1; + if (config.contains(cpBase) && config.getConfigurationSection(cpBase) != null) { + nextId = config.getConfigurationSection(cpBase).getKeys(false).size() + 1; + } + config.set(cpBase + "." + nextId, player.getLocation()); + save(); + player.sendMessage("§8[§6Parkour§8] §7Checkpoint §e#" + nextId + " §7(Strecke " + track + ") §7gesetzt."); player.playSound(player.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 1.0f, 1.5f); } /** - * Löscht die gesamte Strecke (Checkpoints und Ziel) und bricht aktuelle Läufe ab. + * Löscht alle Punkte beider Strecken und bricht laufende Runs ab. */ public void removeAllPoints() { - config.set("locations.checkpoints", null); - config.set("locations.finish", null); + config.set("tracks", null); save(); - - // Alle Spieler aus dem Parkour werfen, damit keine Partikel zu gelöschten Zielen führen for (UUID uuid : new HashSet<>(startTime.keySet())) { Player p = Bukkit.getPlayer(uuid); if (p != null) { stopParkour(p); - p.sendMessage("§8[§6Parkour§8] §cDie aktuelle Strecke wurde soeben gelöscht."); + p.sendMessage("§8[§6Parkour§8] §cDie Strecken wurden soeben gelöscht."); } } } - public void startParkour(Player player, Location startLoc) { + // ───────────────────────────────────────────────────────────────────────── + // Getter (Track-spezifisch) + // ───────────────────────────────────────────────────────────────────────── + + public Location getStartLocation(int track) { + return config.getLocation("tracks." + track + ".start"); + } + + public Location getFinishLocation(int track) { + return config.getLocation("tracks." + track + ".finish"); + } + + public List getOrderedCheckpoints(int track) { + String cpBase = "tracks." + track + ".checkpoints"; + if (!config.contains(cpBase) || config.getConfigurationSection(cpBase) == null) + return new ArrayList<>(); + + return config.getConfigurationSection(cpBase).getKeys(false).stream() + .sorted(Comparator.comparingInt(Integer::parseInt)) + .map(key -> config.getLocation(cpBase + "." + key)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + /** + * Gibt die aktive Track-Nummer des Spielers zurück (1 oder 2), oder -1 wenn nicht aktiv. + */ + public int getActiveTrack(Player player) { + return activeTrack.getOrDefault(player.getUniqueId(), -1); + } + + // Convenience-Wrapper für ParkourListener (nutzt den aktiven Track des Spielers) + public List getOrderedCheckpoints(Player player) { + return getOrderedCheckpoints(getActiveTrack(player)); + } + + public Location getFinishLocation(Player player) { + return getFinishLocation(getActiveTrack(player)); + } + + // ───────────────────────────────────────────────────────────────────────── + // Parkour-Ablauf + // ───────────────────────────────────────────────────────────────────────── + + /** + * Startet den Parkour für den Spieler. + * Es wird zufällig eine der beiden Strecken gewählt, sofern beide vollständig + * konfiguriert sind (Start + Finish + mindestens gleiche Checkpoint-Anzahl). + * Falls nur eine Strecke konfiguriert ist, wird diese gewählt. + */ + public void startParkour(Player player, Location npcLocation) { if (startTime.containsKey(player.getUniqueId())) return; + int chosenTrack = pickRandomTrack(); + if (chosenTrack == -1) { + player.sendMessage("§8[§6Parkour§8] §cKeine Strecke konfiguriert. Bitte einen Admin kontaktieren."); + return; + } + startTime.put(player.getUniqueId(), System.currentTimeMillis()); - lastCheckpointLoc.put(player.getUniqueId(), startLoc); - nextCheckpointIndex.put(player.getUniqueId(), 0); - + lastCheckpointLoc.put(player.getUniqueId(), npcLocation); + nextCheckpointIndex.put(player.getUniqueId(), 0); + activeTrack.put(player.getUniqueId(), chosenTrack); + player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, 1.0f, 1.2f); new BukkitRunnable() { @@ -93,12 +208,45 @@ public class ParkourManager { } long diff = System.currentTimeMillis() - startTime.get(player.getUniqueId()); double sec = diff / 1000.0; - player.spigot().sendMessage(ChatMessageType.ACTION_BAR, - new TextComponent("§6⏱ Zeit: §e" + String.format("%.2f", sec) + "s §8| §bNächster Punkt: §7Partikel folgen")); + player.spigot().sendMessage(ChatMessageType.ACTION_BAR, + new TextComponent("§6⏱ Zeit: §e" + String.format("%.2f", sec) + "s §8| §bNächster Punkt: §7Partikel folgen")); } }.runTaskTimer(plugin, 0L, 1L); } + /** + * Wählt zufällig einen validen Track (1 oder 2). + * Bevorzugt Tracks mit gleichem Checkpoint-Count; fällt auf jeden konfigurierten zurück. + * Gibt -1 zurück, wenn kein Track konfiguriert ist. + */ + private int pickRandomTrack() { + boolean t1 = isTrackReady(1); + boolean t2 = isTrackReady(2); + + if (t1 && t2) { + // Beide vollständig – prüfen ob gleiche Länge + int len1 = getOrderedCheckpoints(1).size(); + int len2 = getOrderedCheckpoints(2).size(); + if (len1 != len2) { + // Warnung ins Log, aber trotzdem zufällig wählen + plugin.getLogger().warning("[Parkour] Strecke 1 hat " + len1 + " Checkpoints, Strecke 2 hat " + len2 + ". Bitte angleichen!"); + } + return random.nextBoolean() ? 1 : 2; + } else if (t1) { + return 1; + } else if (t2) { + return 2; + } + return -1; + } + + /** Gibt zurück, ob Start, Finish und mindestens 1 Checkpoint für den Track gesetzt sind. */ + public boolean isTrackReady(int track) { + return getStartLocation(track) != null + && getFinishLocation(track) != null + && !getOrderedCheckpoints(track).isEmpty(); + } + private void startParticleTask() { new BukkitRunnable() { @Override @@ -107,8 +255,9 @@ public class ParkourManager { Player p = Bukkit.getPlayer(uuid); if (p == null) continue; + int track = activeTrack.getOrDefault(uuid, 1); int nextIdx = nextCheckpointIndex.getOrDefault(uuid, 0); - List cps = getOrderedCheckpoints(); + List cps = getOrderedCheckpoints(track); if (nextIdx < cps.size()) { Location nextCp = cps.get(nextIdx); @@ -116,7 +265,7 @@ public class ParkourManager { p.spawnParticle(Particle.SOUL_FIRE_FLAME, nextCp.clone().add(0, 0.5, 0), 5, 0.1, 0.3, 0.1, 0.02); } } else { - Location finish = getFinishLocation(); + Location finish = getFinishLocation(track); if (finish != null && p.getWorld().equals(finish.getWorld())) { p.spawnParticle(Particle.HAPPY_VILLAGER, finish.clone().add(0, 0.5, 0), 8, 0.2, 0.5, 0.2, 0.02); } @@ -128,23 +277,24 @@ public class ParkourManager { public void reachCheckpoint(Player player, int reachedIndex) { int currentNext = nextCheckpointIndex.getOrDefault(player.getUniqueId(), 0); - - if (reachedIndex == currentNext) { - List cps = getOrderedCheckpoints(); - if (reachedIndex < cps.size()) { - lastCheckpointLoc.put(player.getUniqueId(), cps.get(reachedIndex)); - nextCheckpointIndex.put(player.getUniqueId(), reachedIndex + 1); - - player.sendMessage("§8[§6Parkour§8] §bCheckpoint #" + (reachedIndex + 1) + " erreicht!"); - player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.8f, 1.5f); - } - } + if (reachedIndex != currentNext) return; + + int track = activeTrack.getOrDefault(player.getUniqueId(), 1); + List cps = getOrderedCheckpoints(track); + if (reachedIndex >= cps.size()) return; + + lastCheckpointLoc.put(player.getUniqueId(), cps.get(reachedIndex)); + nextCheckpointIndex.put(player.getUniqueId(), reachedIndex + 1); + + player.sendMessage("§8[§6Parkour§8] §bCheckpoint #" + (reachedIndex + 1) + " erreicht!"); + player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.8f, 1.5f); } public void finishParkour(Player player) { if (!startTime.containsKey(player.getUniqueId())) return; - - if (nextCheckpointIndex.getOrDefault(player.getUniqueId(), 0) < getOrderedCheckpoints().size()) { + + int track = activeTrack.getOrDefault(player.getUniqueId(), 1); + if (nextCheckpointIndex.getOrDefault(player.getUniqueId(), 0) < getOrderedCheckpoints(track).size()) { player.sendMessage("§cDu hast Checkpoints übersprungen! Folge den Partikeln."); return; } @@ -153,12 +303,12 @@ public class ParkourManager { double seconds = duration / 1000.0; player.sendMessage("§8§m--------------------------------------"); - player.sendMessage("§8[§6Parkour§8] §a§lZiel erreicht!"); + player.sendMessage("§8[§6Parkour§8] §a§lZiel erreicht! §8(Strecke " + track + ")"); player.sendMessage("§7Deine Zeit: §e" + String.format("%.2f", seconds) + "s"); player.sendMessage("§8§m--------------------------------------"); - + player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.0f); - + saveBestTime(player, seconds); stopParkour(player); } @@ -167,42 +317,12 @@ public class ParkourManager { startTime.remove(player.getUniqueId()); lastCheckpointLoc.remove(player.getUniqueId()); nextCheckpointIndex.remove(player.getUniqueId()); + activeTrack.remove(player.getUniqueId()); } - public void setStartLocation(Location loc) { config.set("locations.start", loc); save(); } - public void setFinishLocation(Location loc) { config.set("locations.finish", loc); save(); } - public void addCheckpointLocation(String id, Location loc) { config.set("locations.checkpoints." + id, loc); save(); } - - public Location getStartLocation() { return config.getLocation("locations.start"); } - public Location getFinishLocation() { return config.getLocation("locations.finish"); } - - public List getOrderedCheckpoints() { - if (!config.contains("locations.checkpoints") || config.getConfigurationSection("locations.checkpoints") == null) - return new ArrayList<>(); - - return config.getConfigurationSection("locations.checkpoints").getKeys(false).stream() - .sorted(Comparator.comparingInt(Integer::parseInt)) - .map(key -> config.getLocation("locations.checkpoints." + key)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - private void save() { - try { - config.save(file); - } catch (IOException e) { - plugin.getLogger().severe("Fehler beim Speichern der Parkour-Config: " + e.getMessage()); - } - } - - public void clearStats() { - config.set("besttimes", null); - config.set("names", null); - save(); - } - - public boolean isIngame(Player player) { return startTime.containsKey(player.getUniqueId()); } - public Location getCheckpoint(Player player) { return lastCheckpointLoc.get(player.getUniqueId()); } + // ───────────────────────────────────────────────────────────────────────── + // Bestzeiten + // ───────────────────────────────────────────────────────────────────────── private void saveBestTime(Player player, double time) { String path = "besttimes." + player.getUniqueId(); @@ -215,8 +335,19 @@ public class ParkourManager { } } + /** + * Löscht NUR die Bestzeiten-Liste (besttimes + names). + * Die Strecken-Konfiguration (tracks) bleibt vollständig erhalten. + * Wird durch "/nexus parkour clear" aufgerufen. + */ + public void clearStats() { + config.set("besttimes", null); + config.set("names", null); + save(); + } + public String getTopTen() { - if (!config.contains("besttimes") || config.getConfigurationSection("besttimes") == null) + if (!config.contains("besttimes") || config.getConfigurationSection("besttimes") == null) return "§6§l🏆 TOP 10 PARKOUR 🏆\n§7Noch keine Rekorde."; Map allTimes = new HashMap<>(); @@ -233,9 +364,31 @@ public class ParkourManager { int rank = 1; for (Map.Entry entry : sortedList) { String name = config.getString("names." + entry.getKey(), "Unbekannt"); - builder.append("\n§e#").append(rank).append(" §f").append(name).append(" §8» §a").append(String.format("%.2f", entry.getValue())).append("s"); + builder.append("\n§e#").append(rank).append(" §f").append(name) + .append(" §8» §a").append(String.format("%.2f", entry.getValue())).append("s"); rank++; } return builder.toString(); } + + // ───────────────────────────────────────────────────────────────────────── + // Hilfsmethoden + // ───────────────────────────────────────────────────────────────────────── + + public boolean isIngame(Player player) { return startTime.containsKey(player.getUniqueId()); } + public Location getCheckpoint(Player player) { return lastCheckpointLoc.get(player.getUniqueId()); } + + /** Gibt die Strecken-Info als lesbaren String aus (für Admin-Feedback). */ + public String getTrackInfo() { + StringBuilder sb = new StringBuilder("§8[§6Parkour§8] §7Track-Status:\n"); + for (int t = 1; t <= 2; t++) { + int cps = getOrderedCheckpoints(t).size(); + boolean ready = isTrackReady(t); + sb.append(" §eStrecke ").append(t).append(": ") + .append(ready ? "§a✔" : "§c✘") + .append(" §7(").append(cps).append(" Checkpoints)"); + if (t < 2) sb.append("\n"); + } + return sb.toString(); + } } \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 1f473a9..1df6bbe 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: NexusLobby main: de.nexuslobby.NexusLobby -version: "1.1.3" +version: "1.1.4" api-version: "1.21" author: M_Viper description: Modular Lobby Plugin with an invisible Particle-Parkour system.