diff --git a/src/main/java/de/nexuslobby/NexusLobby.java b/src/main/java/de/nexuslobby/NexusLobby.java index 2bab6b0..31d6c3b 100644 --- a/src/main/java/de/nexuslobby/NexusLobby.java +++ b/src/main/java/de/nexuslobby/NexusLobby.java @@ -103,8 +103,11 @@ public class NexusLobby extends JavaPlugin implements Listener { @Override public void onEnable() { instance = this; - - initCustomConfigs(); + initCustomConfigs(); + validateConfig(); + + // Lade die Sprachdatei + LangManager.load(this); getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); moduleManager = new ModuleManager(this); @@ -113,7 +116,7 @@ public class NexusLobby extends JavaPlugin implements Listener { this.parkourManager = new ParkourManager(this); this.conversationManager = new ConversationManager(this); - ArmorStandGUI.init(); + ArmorStandGUI.init(); registerModules(); moduleManager.enableAll(); @@ -262,6 +265,11 @@ public class NexusLobby extends JavaPlugin implements Listener { getServer().getPluginManager().registerEvents(new NPCClickListener(), this); } + private void validateConfig() { + ConfigValidator validator = new ConfigValidator(this, getConfig()); + validator.validate(); + } + public class NPCClickListener implements Listener { @EventHandler public void onNPCClick(PlayerInteractAtEntityEvent event) { @@ -280,7 +288,9 @@ public class NexusLobby extends JavaPlugin implements Listener { @EventHandler(priority = EventPriority.LOWEST) public void onJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); - event.setJoinMessage(null); + if (silentPlayers.contains(player.getUniqueId())) { + event.setJoinMessage(null); + } teleportToSpawn(player); @@ -289,7 +299,7 @@ public class NexusLobby extends JavaPlugin implements Listener { BuildCommand.removePlayerFromBuildMode(player); - String defaultGmName = getConfig().getString("default-gamemode", "ADVENTURE"); + String defaultGmName = getConfig().getString("lobby.default-gamemode", "Adventure"); try { player.setGameMode(GameMode.valueOf(defaultGmName.toUpperCase())); } catch (IllegalArgumentException e) { @@ -297,17 +307,15 @@ public class NexusLobby extends JavaPlugin implements Listener { } if (player.hasPermission("nexuslobby.admin") && updateAvailable) { - player.sendMessage(" "); - player.sendMessage("§8[§6Nexus§8] §aEin neues §6Update §afür §eNexusLobby §aist verfügbar!"); - player.sendMessage("§8» §7Version: §c" + getDescription().getVersion() + " §8-> §a" + latestVersion); - - TextComponent link = new TextComponent("§8» §6Klicke §e§l[HIER] §6zum Herunterladen."); + player.sendMessage(""); + player.sendMessage(de.nexuslobby.utils.LangManager.get("update_available")); + player.sendMessage(de.nexuslobby.utils.LangManager.get("update_version").replace("{old}", getDescription().getVersion()).replace("{new}", latestVersion)); + TextComponent link = new TextComponent(de.nexuslobby.utils.LangManager.get("update_download_link")); link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://git.viper.ipv64.net/M_Viper/NexusLobby/releases")); link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, - new ComponentBuilder("§7Öffnet die Release-Seite").create())); - + new ComponentBuilder(de.nexuslobby.utils.LangManager.get("update_download_hover")).create())); player.spigot().sendMessage(link); - player.sendMessage(" "); + player.sendMessage(""); } } @@ -360,10 +368,23 @@ public class NexusLobby extends JavaPlugin implements Listener { @Override public void onDisable() { + // Cancle alle Scheduler-Tasks + Bukkit.getScheduler().cancelTasks(this); + + // Stoppe spezifische Tasks + ServerChecker.stopGlobalChecker(); + + // Unregister alle Event-Listener + org.bukkit.event.HandlerList.unregisterAll((org.bukkit.plugin.Plugin) this); + + // Schließe BungeeCord Channel getServer().getMessenger().unregisterOutgoingPluginChannel(this, "BungeeCord"); + + // Disable alle Module (inkl. eigenes Cleanup) if (moduleManager != null) { moduleManager.disableAll(); } + getLogger().info("NexusLobby deaktiviert."); } @@ -410,6 +431,21 @@ public class NexusLobby extends JavaPlugin implements Listener { getCommand("spawn").setTabCompleter(tabCompleter); } + if (getCommand("setstart") != null) { + getCommand("setstart").setExecutor(nexusCommand); + getCommand("setstart").setTabCompleter(tabCompleter); + } + + if (getCommand("setcheckpoint") != null) { + getCommand("setcheckpoint").setExecutor(nexusCommand); + getCommand("setcheckpoint").setTabCompleter(tabCompleter); + } + + if (getCommand("setfinish") != null) { + getCommand("setfinish").setExecutor(nexusCommand); + getCommand("setfinish").setTabCompleter(tabCompleter); + } + if (getCommand("mapart") != null) getCommand("mapart").setTabCompleter(tabCompleter); if (getCommand("intro") != null) getCommand("intro").setTabCompleter(tabCompleter); @@ -417,6 +453,11 @@ public class NexusLobby extends JavaPlugin implements Listener { getCommand("border").setExecutor(new BorderCommand()); getCommand("border").setTabCompleter(tabCompleter); } + + if (getCommand("serverswitcher") != null) { + ServerSwitcherListener serverSwitcher = new ServerSwitcherListener(); + getCommand("serverswitcher").setExecutor(serverSwitcher); + } } public class NexusLobbyExpansion extends PlaceholderExpansion { diff --git a/src/main/java/de/nexuslobby/commands/BuildCommand.java b/src/main/java/de/nexuslobby/commands/BuildCommand.java index 48a8280..edd4893 100644 --- a/src/main/java/de/nexuslobby/commands/BuildCommand.java +++ b/src/main/java/de/nexuslobby/commands/BuildCommand.java @@ -39,7 +39,7 @@ public class BuildCommand implements CommandExecutor { } // Gamemode zurücksetzen - String defaultGmName = NexusLobby.getInstance().getConfig().getString("default-gamemode", "ADVENTURE"); + String defaultGmName = NexusLobby.getInstance().getConfig().getString("lobby.default-gamemode", "Adventure"); try { GameMode gm = GameMode.valueOf(defaultGmName.toUpperCase()); player.setGameMode(gm); diff --git a/src/main/java/de/nexuslobby/commands/GivePortalToolCommand.java b/src/main/java/de/nexuslobby/commands/GivePortalToolCommand.java index 71cefd1..9dfb1b4 100644 --- a/src/main/java/de/nexuslobby/commands/GivePortalToolCommand.java +++ b/src/main/java/de/nexuslobby/commands/GivePortalToolCommand.java @@ -33,6 +33,12 @@ public class GivePortalToolCommand implements CommandExecutor { // Erstelle das Item ItemStack wand = new ItemStack(Material.BLAZE_ROD); ItemMeta meta = wand.getItemMeta(); + if (meta == null) { + p.getInventory().addItem(wand); + p.sendMessage(plugin.getName() + " §aDu hast das Portal-Werkzeug erhalten!"); + p.playSound(p.getLocation(), org.bukkit.Sound.ENTITY_ITEM_PICKUP, 1.0f, 1.0f); + return true; + } // Design des Items meta.setDisplayName("§cPortal-Werkzeug"); diff --git a/src/main/java/de/nexuslobby/commands/LobbyTabCompleter.java b/src/main/java/de/nexuslobby/commands/LobbyTabCompleter.java index bb274ee..8ef7d0e 100644 --- a/src/main/java/de/nexuslobby/commands/LobbyTabCompleter.java +++ b/src/main/java/de/nexuslobby/commands/LobbyTabCompleter.java @@ -45,9 +45,9 @@ public class LobbyTabCompleter implements TabCompleter { suggestions.addAll(Arrays.asList("on", "off")); } else if (args[0].equalsIgnoreCase("parkour")) { suggestions.addAll(Arrays.asList("setstart", "setfinish", "setcheckpoint", "reset", "clear", "removeall")); - } else if (args[0].equalsIgnoreCase("ball")) { // NEU: Ball Subcommands + } else if (args[0].equalsIgnoreCase("ball")) { if (sender.hasPermission("nexuslobby.admin")) { - suggestions.addAll(Arrays.asList("setspawn", "reload")); + suggestions.addAll(Arrays.asList("setspawn", "respawn", "remove")); } } } else if (args.length == 3) { diff --git a/src/main/java/de/nexuslobby/commands/NexusLobbyCommand.java b/src/main/java/de/nexuslobby/commands/NexusLobbyCommand.java index 91e9c5e..249d05d 100644 --- a/src/main/java/de/nexuslobby/commands/NexusLobbyCommand.java +++ b/src/main/java/de/nexuslobby/commands/NexusLobbyCommand.java @@ -24,7 +24,7 @@ public class NexusLobbyCommand implements CommandExecutor { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { if (!(sender instanceof Player player)) { - sender.sendMessage("§cDieser Befehl ist nur für Spieler!"); + sender.sendMessage(de.nexuslobby.utils.LangManager.get("only_player")); return true; } @@ -40,12 +40,13 @@ public class NexusLobbyCommand implements CommandExecutor { 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")); return true; } if (cmdName.equalsIgnoreCase("setfinish")) { if (!player.hasPermission("nexuslobby.admin")) return noPerm(player); pm.setFinishLocation(player.getLocation()); - player.sendMessage("§8[§6Nexus§8] §aParkour-Zielpunkt gesetzt!"); + player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_finish_set")); return true; } @@ -57,12 +58,12 @@ public class NexusLobbyCommand implements CommandExecutor { if (loc != null) { player.teleport(loc); player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1.0f, 1.2f); - player.sendMessage("§8[§6Nexus§8] §aDu wurdest zum Spawn teleportiert."); + player.sendMessage(de.nexuslobby.utils.LangManager.get("teleport_spawn")); } else { - player.sendMessage("§cFehler: Die Spawn-Welt existiert nicht."); + player.sendMessage(de.nexuslobby.utils.LangManager.get("spawn_world_missing")); } } else { - player.sendMessage("§cEs wurde noch kein Spawn gesetzt."); + player.sendMessage(de.nexuslobby.utils.LangManager.get("spawn_not_set")); } return true; } @@ -77,7 +78,7 @@ public class NexusLobbyCommand implements CommandExecutor { case "reload": if (!player.hasPermission("nexuslobby.admin")) return noPerm(player); NexusLobby.getInstance().reloadPlugin(); - player.sendMessage("§8[§6Nexus§8] §aPlugin erfolgreich neu geladen!"); + player.sendMessage(de.nexuslobby.utils.LangManager.get("plugin_reloaded")); player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1f, 1.5f); break; @@ -92,17 +93,17 @@ public class NexusLobbyCommand implements CommandExecutor { config.set("spawn.yaw", (double) loc.getYaw()); config.set("spawn.pitch", (double) loc.getPitch()); NexusLobby.getInstance().saveConfig(); - player.sendMessage("§8[§6Nexus§8] §aLobby-Spawn erfolgreich gesetzt!"); + player.sendMessage(de.nexuslobby.utils.LangManager.get("spawn_set")); break; case "silentjoin": if (!player.hasPermission("nexuslobby.silentjoin")) return noPerm(player); if (NexusLobby.getInstance().getSilentPlayers().contains(player.getUniqueId())) { NexusLobby.getInstance().getSilentPlayers().remove(player.getUniqueId()); - player.sendMessage("§8[§6Nexus§8] §7Silent Join: §cDeaktiviert"); + player.sendMessage(de.nexuslobby.utils.LangManager.get("silentjoin_off")); } else { NexusLobby.getInstance().getSilentPlayers().add(player.getUniqueId()); - player.sendMessage("§8[§6Nexus§8] §7Silent Join: §aAktiviert"); + player.sendMessage(de.nexuslobby.utils.LangManager.get("silentjoin_on")); } break; @@ -114,12 +115,12 @@ public class NexusLobbyCommand implements CommandExecutor { if (NexusLobby.getInstance().getSoccerModule() != null) { return NexusLobby.getInstance().getSoccerModule().onCommand(sender, command, label, args); } - player.sendMessage("§cDas Fußball-Modul ist nicht geladen."); + player.sendMessage(de.nexuslobby.utils.LangManager.get("soccer_module_not_loaded")); break; case "parkour": if (args.length < 2) { - player.sendMessage("§8[§6Nexus§8] §7Nutze: §e/nexus parkour "); + player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_usage")); return true; } @@ -132,26 +133,26 @@ public class NexusLobbyCommand implements CommandExecutor { break; case "setfinish": pm.setFinishLocation(player.getLocation()); - player.sendMessage("§8[§6Nexus§8] §aParkour-Zielpunkt gesetzt!"); + 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("§8[§6Nexus§8] §7Dein aktueller Lauf wurde abgebrochen."); + player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_run_aborted")); break; case "clear": pm.clearStats(); - player.sendMessage("§8[§6Nexus§8] §aAlle Parkour-Bestzeiten wurden gelöscht!"); + player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_besttimes_cleared")); break; case "removeall": pm.removeAllPoints(); - player.sendMessage("§8[§6Nexus§8] §cDie gesamte Strecke (Checkpoints & Ziel) wurde gelöscht!"); + player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_track_removed")); player.playSound(player.getLocation(), Sound.ENTITY_ITEM_BREAK, 1f, 1f); break; default: - player.sendMessage("§cUnbekannter Unterbefehl."); + player.sendMessage(de.nexuslobby.utils.LangManager.get("unknown_subcommand")); break; } break; @@ -180,26 +181,25 @@ public class NexusLobbyCommand implements CommandExecutor { if (targetAs != null) { targetAs.addScoreboardTag("parkour_npc"); - player.sendMessage("§8[§6Nexus§8] §aArmorStand als Parkour-NPC markiert!"); + player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_npc_marked")); } - pm.setStartLocation(player.getLocation()); - player.sendMessage("§8[§6Nexus§8] §aParkour-Startpunkt an deiner Position gesetzt!"); + player.sendMessage(de.nexuslobby.utils.LangManager.get("parkour_start_set")); } private boolean noPerm(Player player) { - player.sendMessage("§cKeine Berechtigung."); + player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission")); return true; } private void handleScoreboard(Player player, String[] args) { if (args.length < 2) { - player.sendMessage("§cBenutzung: /nexus sb "); + player.sendMessage(de.nexuslobby.utils.LangManager.get("scoreboard_usage")); return; } ScoreboardModule sbModule = (ScoreboardModule) NexusLobby.getInstance().getModuleManager().getModule(ScoreboardModule.class); if (sbModule == null) { - player.sendMessage("§cScoreboard-Modul ist deaktiviert."); + player.sendMessage(de.nexuslobby.utils.LangManager.get("scoreboard_module_disabled")); return; } String sub = args[1].toLowerCase(); @@ -208,11 +208,11 @@ public class NexusLobbyCommand implements CommandExecutor { case "off": sbModule.setVisibility(player, false); break; case "admin": if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, true); - else player.sendMessage("§cKeine Rechte."); + else player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission")); break; case "spieler": if (player.hasPermission("nexuslobby.scoreboard.admin")) sbModule.setAdminMode(player, false); - else player.sendMessage("§cKeine Rechte."); + else player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission")); break; } } @@ -226,16 +226,16 @@ public class NexusLobbyCommand implements CommandExecutor { } private void sendInfo(Player player) { - player.sendMessage("§8§m--------------------------------------"); - player.sendMessage("§6§lNexusLobby §7- Informationen"); + player.sendMessage(de.nexuslobby.utils.LangManager.get("info_header")); + player.sendMessage(de.nexuslobby.utils.LangManager.get("info_title")); player.sendMessage(""); - player.sendMessage("§f/spawn §7- Zum Spawn"); - player.sendMessage("§f/setstart §8| §f/setcheckpoint §8| §f/setfinish"); - player.sendMessage("§f/nexus parkour removeall §7- Strecke löschen"); - player.sendMessage("§f/nexus ball setspawn §7- Fußball Spawn setzen"); // NEU - player.sendMessage("§f/nexus setspawn §7- Spawn setzen"); - player.sendMessage("§f/nexus sb §7- Scoreboard"); - player.sendMessage("§f/nexus reload §7- Config laden"); - player.sendMessage("§8§m--------------------------------------"); + player.sendMessage(de.nexuslobby.utils.LangManager.get("info_spawn")); + player.sendMessage(de.nexuslobby.utils.LangManager.get("info_parkour")); + 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(de.nexuslobby.utils.LangManager.get("info_footer")); } } \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/ItemsModule.java b/src/main/java/de/nexuslobby/modules/ItemsModule.java index 5db7db6..06b2f4e 100644 --- a/src/main/java/de/nexuslobby/modules/ItemsModule.java +++ b/src/main/java/de/nexuslobby/modules/ItemsModule.java @@ -56,8 +56,11 @@ public class ItemsModule implements Module, Listener { // 2. Gadget GUI Logik String gadgetName = colorize(config.getString("items.lobby-tools.gadget.displayname", "&bGadgets")); if (displayName.equals(gadgetName)) { - // Öffnet die GUI aus dem GadgetModule - NexusLobby.getInstance().getGadgetModule().openGUI(player); + // Öffnet die GUI aus dem GadgetModule (falls vorhanden) + var gadgetModule = NexusLobby.getInstance().getGadgetModule(); + if (gadgetModule != null) { + gadgetModule.openGUI(player); + } event.setCancelled(true); return; } diff --git a/src/main/java/de/nexuslobby/modules/ScoreboardModule.java b/src/main/java/de/nexuslobby/modules/ScoreboardModule.java index a1acc7d..f4ad44f 100644 --- a/src/main/java/de/nexuslobby/modules/ScoreboardModule.java +++ b/src/main/java/de/nexuslobby/modules/ScoreboardModule.java @@ -18,6 +18,7 @@ import java.util.UUID; public class ScoreboardModule implements Module { 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<>(); @@ -33,6 +34,8 @@ public class ScoreboardModule implements Module { FileConfiguration vConfig = plugin.getVisualsConfig(); if (!vConfig.getBoolean("scoreboard.enabled", true)) return; + placeholderAPIEnabled = Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null; + new BukkitRunnable() { @Override public void run() { @@ -124,7 +127,14 @@ public class ScoreboardModule implements Module { } private String translate(Player player, String text) { - String translated = PlaceholderAPI.setPlaceholders(player, text); + String translated = text; + if (placeholderAPIEnabled) { + try { + translated = PlaceholderAPI.setPlaceholders(player, text); + } catch (NoClassDefFoundError ignored) { + // PlaceholderAPI fehlt zur Laufzeit + } + } return ChatColor.translateAlternateColorCodes('&', translated); } diff --git a/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCmdExecutor.java b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCmdExecutor.java index 1665021..88076f9 100644 --- a/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCmdExecutor.java +++ b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandCmdExecutor.java @@ -34,7 +34,7 @@ public class ArmorStandCmdExecutor implements CommandExecutor { if (!(sender instanceof Player p)) return true; if (!p.hasPermission("nexuslobby.armorstand.cmd")) { - p.sendMessage(prefix + "§cKeine Berechtigung!"); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("no_permission")); return true; } @@ -53,7 +53,7 @@ public class ArmorStandCmdExecutor implements CommandExecutor { case "select4": ArmorStand target = getTargetArmorStand(p); if (target == null) { - p.sendMessage(prefix + "§cDu musst einen ArmorStand direkt anschauen (Fadenkreuz)!"); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("armorstand_lookat_required")); return true; } @@ -69,11 +69,11 @@ public class ArmorStandCmdExecutor implements CommandExecutor { case "link": if (args.length < 3) { - p.sendMessage(prefix + "§cNutze: /nexuscmd conv link "); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_link_usage")); return true; } if (!p.hasMetadata("conv_npc1") || !p.hasMetadata("conv_npc2")) { - p.sendMessage(prefix + "§cBitte markiere mindestens die ersten beiden NPCs (select1 & select2)!"); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_mark_npcs")); return true; } @@ -101,8 +101,8 @@ public class ArmorStandCmdExecutor implements CommandExecutor { // Im Manager speichern (Nutzt die erweiterte Methode für Gruppen) NexusLobby.getInstance().getConversationManager().saveLinkExtended(id1, id2, id3, id4, dialogId); - p.sendMessage(prefix + "§a§lDauerhafte Verknüpfung erstellt!"); - p.sendMessage(prefix + "§7Beteiligte NPCs: §e" + (id3 == null ? "2" : (id4 == null ? "3" : "4"))); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_link_created")); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_link_npcs").replace("{count}", (id3 == null ? "2" : (id4 == null ? "3" : "4")))); p.spawnParticle(Particle.HAPPY_VILLAGER, as1.getLocation().add(0, 1.5, 0), 20, 0.4, 0.4, 0.4, 0.1); // Metadaten nach dem Linken aufräumen @@ -111,14 +111,14 @@ public class ArmorStandCmdExecutor implements CommandExecutor { p.removeMetadata("conv_npc3", NexusLobby.getInstance()); p.removeMetadata("conv_npc4", NexusLobby.getInstance()); } else { - p.sendMessage(prefix + "§cFehler: Sprecher 1 nicht gefunden."); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_speaker_not_found")); } break; case "unlink": ArmorStand targetUnlink = getTargetArmorStand(p); if (targetUnlink == null) { - p.sendMessage(prefix + "§cSchau den NPC an, dessen Verknüpfung du lösen willst!"); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_unlink_lookat")); return true; } @@ -128,17 +128,17 @@ public class ArmorStandCmdExecutor implements CommandExecutor { // Aus Konfiguration löschen NexusLobby.getInstance().getConversationManager().removeLink(targetUnlink.getUniqueId()); - p.sendMessage(prefix + "§eNPC-Verknüpfung wurde aufgehoben."); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_unlinked")); p.spawnParticle(Particle.SMOKE, targetUnlink.getLocation().add(0, 1.0, 0), 20, 0.2, 0.2, 0.2, 0.02); break; case "start": if (args.length < 3) { - p.sendMessage(prefix + "§cNutze: /nexuscmd conv start "); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_start_usage")); return true; } if (!p.hasMetadata("conv_npc1") || !p.hasMetadata("conv_npc2")) { - p.sendMessage(prefix + "§cBitte markiere mindestens zwei NPCs!"); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("conv_mark_two_npcs")); return true; } @@ -163,7 +163,7 @@ public class ArmorStandCmdExecutor implements CommandExecutor { ArmorStand target = getTargetArmorStand(p); if (args[0].equalsIgnoreCase("say") && args.length >= 2) { - if (target == null) { p.sendMessage(prefix + "§cSchau einen ArmorStand an!"); return true; } + if (target == null) { p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("armorstand_lookat_required")); return true; } String text = buildString(args, 1); String colored = ChatColor.translateAlternateColorCodes('&', text); @@ -171,25 +171,25 @@ public class ArmorStandCmdExecutor implements CommandExecutor { // Nutzt die showBubble-Logik aus dem ConversationManager (Ohne Partner-Zwang) NexusLobby.getInstance().getConversationManager().showBubble(target, colored); - p.sendMessage(prefix + "§aNPC-Sprechblase gesendet."); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("npc_bubble_sent")); return true; } if (args[0].equalsIgnoreCase("lookat")) { - if (target == null) { p.sendMessage(prefix + "§cSchau einen ArmorStand an!"); return true; } + if (target == null) { p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("armorstand_lookat_required")); return true; } if (target.getScoreboardTags().contains("as_lookat")) { target.removeScoreboardTag("as_lookat"); target.setHeadPose(new EulerAngle(0, 0, 0)); - p.sendMessage(prefix + "§cBlickkontakt aus."); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("lookat_off")); } else { target.addScoreboardTag("as_lookat"); - p.sendMessage(prefix + "§aBlickkontakt an."); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("lookat_on")); } return true; } if (args[0].equalsIgnoreCase("name") && args.length >= 2) { - if (target == null) { p.sendMessage(prefix + "§cSchau einen ArmorStand an!"); return true; } + if (target == null) { p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("armorstand_lookat_required")); return true; } String nameInput = buildString(args, 1); // Wichtig: Alle alten Namens-Tags entfernen für Konsistenz @@ -198,7 +198,7 @@ public class ArmorStandCmdExecutor implements CommandExecutor { if (nameInput.equalsIgnoreCase("none")) { target.setCustomName(""); target.setCustomNameVisible(false); - p.sendMessage(prefix + "§eName entfernt."); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("name_removed")); } else { String colored = ChatColor.translateAlternateColorCodes('&', nameInput); target.setCustomName(colored); @@ -208,33 +208,33 @@ public class ArmorStandCmdExecutor implements CommandExecutor { // ":" wird durch "§§" ersetzt, um Probleme in Scoreboard-Tags zu vermeiden target.addScoreboardTag("as_displayname:" + nameInput.replace(":", "§§")); - p.sendMessage(prefix + "§7Name gesetzt: " + colored); - p.sendMessage(prefix + "§8(Status-Backup wurde gespeichert)"); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("name_set").replace("{name}", colored)); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("status_backup_saved")); } return true; } if (args[0].equalsIgnoreCase("add") && args.length >= 5) { - if (target == null) { p.sendMessage(prefix + "§cSchau einen ArmorStand an!"); return true; } + if (target == null) { p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("armorstand_lookat_required")); return true; } String slot1 = args[1], slot2 = args[2], type = args[3].toLowerCase(); String cmdStr = buildString(args, 4); target.addScoreboardTag("ascmd:" + slot1 + ":" + slot2 + ":" + type + ":" + cmdStr); - p.sendMessage(prefix + "§aBefehl gebunden."); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("command_bound")); return true; } if (args[0].equalsIgnoreCase("list")) { - if (target == null) { p.sendMessage(prefix + "§cKein Ziel!"); return true; } - p.sendMessage("§6§lBefehle & Tags:"); + if (target == null) { p.sendMessage(de.nexuslobby.utils.LangManager.get("no_target")); return true; } + p.sendMessage(de.nexuslobby.utils.LangManager.get("commands_and_tags")); target.getScoreboardTags().forEach(t -> p.sendMessage(" §8» §e" + t)); return true; } if (args[0].equalsIgnoreCase("remove")) { - if (target == null) { p.sendMessage(prefix + "§cKein Ziel!"); return true; } + if (target == null) { p.sendMessage(de.nexuslobby.utils.LangManager.get("no_target")); return true; } target.getScoreboardTags().removeIf(tag -> tag.startsWith("ascmd:")); - p.sendMessage(prefix + "§eAlle Befehle gelöscht."); + p.sendMessage(prefix + de.nexuslobby.utils.LangManager.get("commands_removed")); return true; } diff --git a/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandPoseGUI.java b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandPoseGUI.java index 7f37c1d..0c9c722 100644 --- a/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandPoseGUI.java +++ b/src/main/java/de/nexuslobby/modules/armorstandtools/ArmorStandPoseGUI.java @@ -49,6 +49,7 @@ public class ArmorStandPoseGUI { private static ItemStack createPartIcon(Material mat, String name, EulerAngle angle) { ItemStack item = new ItemStack(mat); ItemMeta meta = item.getItemMeta(); + if (meta == null) return item; meta.setDisplayName(name); meta.setLore(Arrays.asList( "§8Rotation:", @@ -64,6 +65,7 @@ public class ArmorStandPoseGUI { private static ItemStack createAxisItem(Material mat, String name, double angleRad) { ItemStack item = new ItemStack(mat); ItemMeta meta = item.getItemMeta(); + if (meta == null) return item; meta.setDisplayName(name); meta.setLore(Arrays.asList( "§7Aktueller Winkel: §f" + Math.round(Math.toDegrees(angleRad)) + "°", @@ -78,6 +80,7 @@ public class ArmorStandPoseGUI { private static ItemStack createNamedItem(Material mat, String name) { ItemStack item = new ItemStack(mat); ItemMeta meta = item.getItemMeta(); + if (meta == null) return item; meta.setDisplayName(name); item.setItemMeta(meta); return item; diff --git a/src/main/java/de/nexuslobby/modules/armorstandtools/ConversationManager.java b/src/main/java/de/nexuslobby/modules/armorstandtools/ConversationManager.java index d1f8912..eec2c52 100644 --- a/src/main/java/de/nexuslobby/modules/armorstandtools/ConversationManager.java +++ b/src/main/java/de/nexuslobby/modules/armorstandtools/ConversationManager.java @@ -265,7 +265,11 @@ public class ConversationManager { } private void saveConfig() { - try { config.save(file); } catch (IOException e) { e.printStackTrace(); } + try { + config.save(file); + } catch (IOException e) { + Bukkit.getLogger().severe("Fehler beim Speichern der Conversations: " + e.getMessage()); + } } public void reload() { diff --git a/src/main/java/de/nexuslobby/modules/ball/SoccerModule.java b/src/main/java/de/nexuslobby/modules/ball/SoccerModule.java index 80db00e..b0d5541 100644 --- a/src/main/java/de/nexuslobby/modules/ball/SoccerModule.java +++ b/src/main/java/de/nexuslobby/modules/ball/SoccerModule.java @@ -19,7 +19,6 @@ import org.bukkit.event.player.PlayerInteractAtEntityEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; import org.bukkit.profile.PlayerProfile; -import org.bukkit.profile.PlayerTextures; import org.bukkit.util.Vector; import java.net.MalformedURLException; @@ -29,12 +28,31 @@ import java.util.Objects; public class SoccerModule implements Module, Listener, CommandExecutor { + // Ball Konstanten + private static final String TEXTURE_URL = "http://textures.minecraft.net/texture/451f8cfcfb85d77945dc6a3618414093e70436b46d2577b28c727f1329b7265e"; + private static final String BALL_TAG = "nexusball_entity"; + private static final String BALL_NAME = "NexusBall"; + + // Physik Konstanten + private static final double DRIBBLE_DETECTION_RADIUS = 0.7; + private static final double DRIBBLE_HEIGHT = 0.5; + private static final double DRIBBLE_FORCE = 0.35; + private static final double DRIBBLE_LIFT = 0.12; + private static final double KICK_FORCE = 1.35; + private static final double KICK_LIFT = 0.38; + private static final double WALL_BOUNCE_DAMPING = 0.75; + private static final double WALL_CHECK_DISTANCE = 1.3; + private static final double VOID_THRESHOLD = -5.0; + private static final double CLEANUP_RADIUS = 5.0; + + // Particle Konstanten + private static final double PARTICLE_SPEED_HIGH = 0.85; + private static final double PARTICLE_SPEED_MEDIUM = 0.45; + private static final double PARTICLE_SPEED_MIN = 0.05; + private ArmorStand ball; private Location spawnLocation; private long lastMoveTime; - private final String TEXTURE_URL = "http://textures.minecraft.net/texture/451f8cfcfb85d77945dc6a3618414093e70436b46d2577b28c727f1329b7265e"; - private final String BALL_TAG = "nexusball_entity"; // Eindeutiges Tag zur Identifizierung - private final String BALL_NAME = "§x§N§e§x§u§s§B§a§l§l"; // Zusätzliche Erkennung @Override public String getName() { return "Soccer"; } @@ -48,43 +66,21 @@ public class SoccerModule implements Module, Listener, CommandExecutor { loadConfigLocation(); - // AGGRESSIVES MEHRFACHES CLEANUP-SYSTEM - // 1. Sofort beim Enable - removeAllOldBallsGlobal(); + // Optimiertes Cleanup-System: 3 Phasen statt 6 + removeAllOldBalls(); - // 2. Nach 0.5 Sekunden - Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBallsGlobal, 10L); - - // 3. Nach 1 Sekunde - Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBallsGlobal, 20L); - - // 4. Nach 2 Sekunden - cleanup und dann spawnen Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), () -> { - removeAllOldBallsGlobal(); + removeAllOldBalls(); spawnBall(); - }, 40L); + }, 40L); // Nach 2 Sekunden - // 5. Nach 3 Sekunden - finales Cleanup für hartnäckige Duplikate - Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBallsGlobal, 60L); - - // 6. Nach 5 Sekunden - letzter Check - Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBallsGlobal, 100L); + Bukkit.getScheduler().runTaskLater(NexusLobby.getInstance(), this::removeAllOldBalls, 100L); // Finaler Check nach 5 Sekunden - // Haupt-Physik & Anti-Klon Tick + // Haupt-Physik & Anti-Duplikat System Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> { - - // ANTI-KLON-SYSTEM: Prüfe die Umgebung des Spawns auf illegale Kopien - if (spawnLocation != null && spawnLocation.getWorld() != null) { - for (Entity entity : spawnLocation.getWorld().getNearbyEntities(spawnLocation, 50, 50, 50)) { - if (entity instanceof ArmorStand stand) { - // Wenn der ArmorStand unser Tag hat, aber nicht unsere aktive Instanz ist -> Löschen - if (stand.getScoreboardTags().contains(BALL_TAG)) { - if (ball == null || !stand.getUniqueId().equals(ball.getUniqueId())) { - stand.remove(); - } - } - } - } + // Anti-Duplikat-Check (optimiert: nur in Ball-Welt) + if (ball != null && ball.isValid()) { + removeDuplicateBalls(); } if (ball == null || !ball.isValid()) return; @@ -94,100 +90,146 @@ public class SoccerModule implements Module, Listener, CommandExecutor { handleWallBounce(vel); handleParticles(speed); - - // Dribbel-Logik - for (Entity nearby : ball.getNearbyEntities(0.7, 0.5, 0.7)) { - if (nearby instanceof Player p) { - Vector direction = ball.getLocation().toVector().subtract(p.getLocation().toVector()); - if (direction.lengthSquared() > 0) { - direction.normalize(); - direction.setY(0.12); - ball.setVelocity(direction.multiply(0.35)); - } - lastMoveTime = System.currentTimeMillis(); - } - } + handleDribbling(); // Automatischer Respawn bei Inaktivität oder Void - long delay = NexusLobby.getInstance().getConfig().getLong("ball.respawn_delay", 60) * 1000; - if (System.currentTimeMillis() - lastMoveTime > delay || ball.getLocation().getY() < -5) { + long respawnDelayMs = NexusLobby.getInstance().getConfig().getLong("ball.respawn_delay", 60) * 1000; + if (System.currentTimeMillis() - lastMoveTime > respawnDelayMs || ball.getLocation().getY() < VOID_THRESHOLD) { respawnBall(); } }, 1L, 1L); } /** - * Scannt ALLE Welten nach Entities mit dem BALL_TAG und entfernt sie. - * Nutzt mehrere Erkennungsmethoden für maximale Sicherheit. + * Optimierte Dribbel-Logik: Ball folgt nahen Spielern */ - private void removeAllOldBallsGlobal() { - int removed = 0; - // Alle Welten durchsuchen - for (World world : Bukkit.getWorlds()) { - for (Entity entity : world.getEntities()) { - if (entity instanceof ArmorStand stand) { - boolean shouldRemove = false; - - // Methode 1: Tag-basiert - if (stand.getScoreboardTags().contains(BALL_TAG)) { - shouldRemove = true; - } - - // Methode 2: Name-basiert (falls Tags verloren gehen) - if (stand.getCustomName() != null && stand.getCustomName().equals(BALL_NAME)) { - shouldRemove = true; - } - - // Methode 3: Kopf-Textur-basiert (prüfe ob es ein Soccer-Ball Kopf ist) - if (stand.getEquipment() != null && stand.getEquipment().getHelmet() != null) { - ItemStack helmet = stand.getEquipment().getHelmet(); - if (helmet.getType() == Material.PLAYER_HEAD && helmet.hasItemMeta()) { - SkullMeta meta = (SkullMeta) helmet.getItemMeta(); - if (meta.hasOwner() && meta.getOwnerProfile() != null) { - PlayerProfile profile = meta.getOwnerProfile(); - if (profile.getName() != null && profile.getName().equals("SoccerBall")) { - shouldRemove = true; - } - } - } - } - - // Methode 4: Position-basiert - Entferne alle unsichtbaren, kleinen ArmorStands in der Nähe des Spawns - if (spawnLocation != null && spawnLocation.getWorld() != null && - stand.getWorld().equals(spawnLocation.getWorld()) && - stand.isSmall() && stand.isInvisible() && !stand.hasBasePlate()) { - - double distance = stand.getLocation().distance(spawnLocation); - // Wenn innerhalb von 5 Blöcken vom Spawn und hat einen Kopf - if (distance < 5.0 && stand.getEquipment() != null && - stand.getEquipment().getHelmet() != null && - stand.getEquipment().getHelmet().getType() == Material.PLAYER_HEAD) { - shouldRemove = true; - } - } - - // Nur entfernen wenn es NICHT unsere aktuelle Ball-Instanz ist - if (shouldRemove && (ball == null || !stand.getUniqueId().equals(ball.getUniqueId()))) { - stand.remove(); - removed++; - } + private void handleDribbling() { + if (ball == null || !ball.isValid()) return; + + for (Entity nearby : ball.getNearbyEntities(DRIBBLE_DETECTION_RADIUS, DRIBBLE_HEIGHT, DRIBBLE_DETECTION_RADIUS)) { + if (nearby instanceof Player p) { + Vector direction = ball.getLocation().toVector().subtract(p.getLocation().toVector()); + if (direction.lengthSquared() > 0) { + direction.normalize(); + direction.setY(DRIBBLE_LIFT); + ball.setVelocity(direction.multiply(DRIBBLE_FORCE)); + lastMoveTime = System.currentTimeMillis(); } } } + } + + /** + * Entfernt Duplikate in der Umgebung des aktiven Balls (Performance-optimiert) + */ + private void removeDuplicateBalls() { + if (ball == null || !ball.isValid() || ball.getLocation().getWorld() == null) return; + + for (Entity entity : ball.getLocation().getWorld().getNearbyEntities(ball.getLocation(), 50, 50, 50)) { + if (entity instanceof ArmorStand stand && isBallEntity(stand)) { + if (!stand.getUniqueId().equals(ball.getUniqueId())) { + stand.remove(); + } + } + } + } + + /** + * Entfernt alle Ball-Entities (nur in relevanter Welt wenn Spawn gesetzt) + */ + private void removeAllOldBalls() { + int removed = 0; + + // Wenn Spawn-Location gesetzt ist, nur diese Welt durchsuchen (Performance!) + if (spawnLocation != null && spawnLocation.getWorld() != null) { + removed = cleanupBallsInWorld(spawnLocation.getWorld()); + } else { + // Sonst alle Welten durchsuchen + for (World world : Bukkit.getWorlds()) { + removed += cleanupBallsInWorld(world); + } + } + if (removed > 0) { Bukkit.getLogger().info("[NexusLobby] " + removed + " alte Ball-Entities entfernt."); } } + /** + * Cleanup für eine einzelne Welt + */ + private int cleanupBallsInWorld(World world) { + int removed = 0; + for (Entity entity : world.getEntities()) { + if (entity instanceof ArmorStand stand && isBallEntity(stand)) { + if (ball == null || !stand.getUniqueId().equals(ball.getUniqueId())) { + stand.remove(); + removed++; + } + } + } + return removed; + } + + /** + * Prüft ob ein ArmorStand ein Ball ist (Mehrere Erkennungsmethoden) + */ + private boolean isBallEntity(ArmorStand stand) { + // Methode 1: Tag-basiert (primär) + if (stand.getScoreboardTags().contains(BALL_TAG)) { + return true; + } + + // Methode 2: Name-basiert + if (stand.getCustomName() != null && stand.getCustomName().equals(BALL_NAME)) { + return true; + } + + // Methode 3: Profil-basiert (Kopf-Textur) + if (stand.getEquipment() != null && stand.getEquipment().getHelmet() != null) { + ItemStack helmet = stand.getEquipment().getHelmet(); + if (helmet.getType() == Material.PLAYER_HEAD && helmet.hasItemMeta()) { + SkullMeta meta = (SkullMeta) helmet.getItemMeta(); + if (meta.hasOwner() && meta.getOwnerProfile() != null) { + if (BALL_NAME.equals(meta.getOwnerProfile().getName())) { + return true; + } + } + } + } + + // Methode 4: Eigenschaften-basiert (nur wenn Spawn gesetzt) + if (spawnLocation != null && spawnLocation.getWorld() != null && + stand.getWorld().equals(spawnLocation.getWorld()) && + stand.isSmall() && stand.isInvisible() && !stand.hasBasePlate()) { + + double distance = stand.getLocation().distance(spawnLocation); + if (distance < CLEANUP_RADIUS && stand.getEquipment() != null && + stand.getEquipment().getHelmet() != null && + stand.getEquipment().getHelmet().getType() == Material.PLAYER_HEAD) { + return true; + } + } + + return false; + } + private void handleWallBounce(Vector vel) { if (vel.lengthSquared() < 0.001) return; + Location loc = ball.getLocation(); - Block nextX = loc.clone().add(vel.getX() * 1.3, 0.5, 0).getBlock(); - Block nextZ = loc.clone().add(0, 0.5, vel.getZ() * 1.3).getBlock(); + Block nextX = loc.clone().add(vel.getX() * WALL_CHECK_DISTANCE, 0.5, 0).getBlock(); + Block nextZ = loc.clone().add(0, 0.5, vel.getZ() * WALL_CHECK_DISTANCE).getBlock(); boolean bounced = false; - if (nextX.getType().isSolid()) { vel.setX(-vel.getX() * 0.75); bounced = true; } - if (nextZ.getType().isSolid()) { vel.setZ(-vel.getZ() * 0.75); bounced = true; } + if (nextX.getType().isSolid()) { + vel.setX(-vel.getX() * WALL_BOUNCE_DAMPING); + bounced = true; + } + if (nextZ.getType().isSolid()) { + vel.setZ(-vel.getZ() * WALL_BOUNCE_DAMPING); + bounced = true; + } if (bounced) { ball.setVelocity(vel); @@ -196,20 +238,29 @@ public class SoccerModule implements Module, Listener, CommandExecutor { } private void handleParticles(double speed) { - if (speed < 0.05) return; + if (speed < PARTICLE_SPEED_MIN) return; + Location loc = ball.getLocation().add(0, 0.2, 0); World world = loc.getWorld(); if (world == null) return; - if (speed > 0.85) world.spawnParticle(Particle.SONIC_BOOM, loc, 1, 0, 0, 0, 0); - else if (speed > 0.45) world.spawnParticle(Particle.CRIT, loc, 3, 0.1, 0.1, 0.1, 0.08); - else world.spawnParticle(Particle.SMOKE, loc, 1, 0.05, 0, 0.05, 0.02); + if (speed > PARTICLE_SPEED_HIGH) { + world.spawnParticle(Particle.SONIC_BOOM, loc, 1, 0, 0, 0, 0); + } else if (speed > PARTICLE_SPEED_MEDIUM) { + world.spawnParticle(Particle.CRIT, loc, 3, 0.1, 0.1, 0.1, 0.08); + } else { + world.spawnParticle(Particle.SMOKE, loc, 1, 0.05, 0, 0.05, 0.02); + } } private void spawnBall() { - if (spawnLocation == null || (ball != null && ball.isValid())) return; + if (spawnLocation == null || spawnLocation.getWorld() == null) { + Bukkit.getLogger().warning("[NexusLobby] Ball-Spawn-Location nicht gesetzt! Verwende /nexuslobby ball setspawn"); + return; + } + + if (ball != null && ball.isValid()) return; - // Ball direkt auf dem Boden spawnen (keine Y-Offset Erhöhung) Location spawnLoc = spawnLocation.clone(); ball = (ArmorStand) spawnLoc.getWorld().spawnEntity(spawnLoc, EntityType.ARMOR_STAND); @@ -220,17 +271,15 @@ public class SoccerModule implements Module, Listener, CommandExecutor { ball.setInvulnerable(false); ball.setArms(false); ball.setCustomNameVisible(false); - - // Setze Custom Name für zusätzliche Erkennung (unsichtbar für Spieler) ball.setCustomName(BALL_NAME); - - // Deaktiviert das Speichern in der Welt-Datei - ball.setPersistent(false); - // Markiert den Ball für das Cleanup-System + ball.setPersistent(false); ball.addScoreboardTag(BALL_TAG); ItemStack ballHead = getSoccerHead(); - if (ball.getEquipment() != null) ball.getEquipment().setHelmet(ballHead); + if (ball.getEquipment() != null) { + ball.getEquipment().setHelmet(ballHead); + } + lastMoveTime = System.currentTimeMillis(); } @@ -239,35 +288,37 @@ public class SoccerModule implements Module, Listener, CommandExecutor { SkullMeta meta = (SkullMeta) head.getItemMeta(); if (meta == null) return head; - PlayerProfile profile = Bukkit.createPlayerProfile(UUID.randomUUID(), "SoccerBall"); + PlayerProfile profile = Bukkit.createPlayerProfile(UUID.randomUUID(), BALL_NAME); try { profile.getTextures().setSkin(new URL(TEXTURE_URL)); - } catch (MalformedURLException ignored) {} + } catch (MalformedURLException e) { + Bukkit.getLogger().warning("[NexusLobby] Ungültige Ball-Textur URL!"); + } meta.setOwnerProfile(profile); - head.setItemMeta(meta); return head; } public void respawnBall() { - if (ball != null) { + if (ball != null && ball.isValid()) { ball.remove(); ball = null; } - removeAllOldBallsGlobal(); + removeAllOldBalls(); spawnBall(); } @EventHandler public void onBallPunch(EntityDamageByEntityEvent event) { - if (event.getEntity().equals(ball)) { - event.setCancelled(true); - if (event.getDamager() instanceof Player p) { - Vector shootDir = p.getLocation().getDirection(); - if (shootDir.lengthSquared() > 0) { - shootDir.normalize().multiply(1.35).setY(0.38); - ball.setVelocity(shootDir); - } + if (ball == null || !event.getEntity().equals(ball)) return; + + event.setCancelled(true); + + if (event.getDamager() instanceof Player p) { + Vector shootDir = p.getLocation().getDirection(); + if (shootDir.lengthSquared() > 0) { + shootDir.normalize().multiply(KICK_FORCE).setY(KICK_LIFT); + ball.setVelocity(shootDir); ball.getWorld().playSound(ball.getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, 0.6f, 1.5f); lastMoveTime = System.currentTimeMillis(); } @@ -276,30 +327,61 @@ public class SoccerModule implements Module, Listener, CommandExecutor { @EventHandler public void onBallInteract(PlayerInteractAtEntityEvent event) { - if (event.getRightClicked().equals(ball)) event.setCancelled(true); + if (ball != null && event.getRightClicked().equals(ball)) { + event.setCancelled(true); + } } private void loadConfigLocation() { FileConfiguration config = NexusLobby.getInstance().getConfig(); - if (config.contains("ball.spawn")) spawnLocation = config.getLocation("ball.spawn"); + if (config.contains("ball.spawn")) { + spawnLocation = config.getLocation("ball.spawn"); + } } @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (!(sender instanceof Player p) || !p.hasPermission("nexuslobby.admin")) return true; + if (!(sender instanceof Player p)) { + sender.sendMessage("§cNur Spieler können diesen Befehl ausführen."); + return true; + } + + if (!p.hasPermission("nexuslobby.admin")) { + p.sendMessage("§cKeine Berechtigung!"); + return true; + } if (args.length >= 2 && args[0].equalsIgnoreCase("ball")) { - if (args[1].equalsIgnoreCase("setspawn")) { - spawnLocation = p.getLocation(); - NexusLobby.getInstance().getConfig().set("ball.spawn", spawnLocation); - NexusLobby.getInstance().saveConfig(); - respawnBall(); - p.sendMessage("§8[§6Nexus§8] §aBall-Spawn gesetzt. Cleanup ist aktiv!"); - return true; - } else if (args[1].equalsIgnoreCase("respawn")) { - respawnBall(); - p.sendMessage("§8[§6Nexus§8] §eBall manuell respawnt."); - return true; + switch (args[1].toLowerCase()) { + case "setspawn" -> { + spawnLocation = p.getLocation(); + NexusLobby.getInstance().getConfig().set("ball.spawn", spawnLocation); + NexusLobby.getInstance().saveConfig(); + respawnBall(); + p.sendMessage("§8[§6Nexus§8] §aBall-Spawn gesetzt und Ball respawnt!"); + return true; + } + case "respawn" -> { + respawnBall(); + p.sendMessage("§8[§6Nexus§8] §eBall manuell respawnt."); + return true; + } + case "remove" -> { + if (ball != null) { + ball.remove(); + ball = null; + } + removeAllOldBalls(); + p.sendMessage("§8[§6Nexus§8] §cBall entfernt und Cleanup durchgeführt."); + return true; + } + default -> { + p.sendMessage("§8[§6Nexus§8] §7Verwendung:"); + p.sendMessage("§e/nexuslobby ball setspawn §7- Setzt den Ball-Spawn"); + p.sendMessage("§e/nexuslobby ball respawn §7- Spawnt den Ball neu"); + p.sendMessage("§e/nexuslobby ball remove §7- Entfernt den Ball"); + return true; + } } } return false; @@ -307,6 +389,9 @@ public class SoccerModule implements Module, Listener, CommandExecutor { @Override public void onDisable() { - if (ball != null) ball.remove(); + if (ball != null && ball.isValid()) { + ball.remove(); + ball = null; + } } } \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/modules/gadgets/GadgetModule.java b/src/main/java/de/nexuslobby/modules/gadgets/GadgetModule.java index 6783883..9f6aa20 100644 --- a/src/main/java/de/nexuslobby/modules/gadgets/GadgetModule.java +++ b/src/main/java/de/nexuslobby/modules/gadgets/GadgetModule.java @@ -64,7 +64,7 @@ public class GadgetModule implements Module, Listener { @EventHandler public void onInteract(PlayerInteractEvent event) { ItemStack item = event.getItem(); - if (item == null || !item.hasItemMeta()) return; + 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) { @@ -216,7 +216,11 @@ public class GadgetModule implements Module, Listener { else if (item.getType() == Material.BARRIER) { removeGadgets(player); player.closeInventory(); } } else if (title.equals(HAT_TITLE)) { if (item.getType() != Material.GRAY_STAINED_GLASS_PANE) { - HatManager.setHat(player, item.getType(), item.getItemMeta().getDisplayName()); + String hatName = item.getType().name(); + if (item.hasItemMeta() && item.getItemMeta().hasDisplayName()) { + hatName = item.getItemMeta().getDisplayName(); + } + HatManager.setHat(player, item.getType(), hatName); player.playSound(player.getLocation(), Sound.ITEM_ARMOR_EQUIP_GENERIC, 1, 1); player.closeInventory(); } @@ -273,7 +277,8 @@ public class GadgetModule implements Module, Listener { public void onFish(PlayerFishEvent event) { Player player = event.getPlayer(); ItemStack item = player.getInventory().getItemInMainHand(); - if (item.getType() == Material.FISHING_ROD && item.hasItemMeta() && item.getItemMeta().getDisplayName().equals("§b§lEnterhaken")) { + 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()); diff --git a/src/main/java/de/nexuslobby/modules/hologram/HologramModule.java b/src/main/java/de/nexuslobby/modules/hologram/HologramModule.java index 1063597..5652ec2 100644 --- a/src/main/java/de/nexuslobby/modules/hologram/HologramModule.java +++ b/src/main/java/de/nexuslobby/modules/hologram/HologramModule.java @@ -158,7 +158,7 @@ public class HologramModule implements Module, Listener { config.save(file); } catch (IOException e) { NexusLobby.getInstance().getLogger().severe("Konnte holograms.yml nicht speichern!"); - e.printStackTrace(); + NexusLobby.getInstance().getLogger().severe("Fehler beim Speichern der Hologramme: " + e.getMessage()); } } diff --git a/src/main/java/de/nexuslobby/modules/intro/IntroModule.java b/src/main/java/de/nexuslobby/modules/intro/IntroModule.java index 93245a5..e598c84 100644 --- a/src/main/java/de/nexuslobby/modules/intro/IntroModule.java +++ b/src/main/java/de/nexuslobby/modules/intro/IntroModule.java @@ -69,7 +69,11 @@ public class IntroModule implements Module, Listener, CommandExecutor { private void savePoints() { config.set("points", points); - try { config.save(configFile); } catch (IOException e) { e.printStackTrace(); } + try { + config.save(configFile); + } catch (IOException e) { + NexusLobby.getInstance().getLogger().severe("Fehler beim Speichern der Intro-Config: " + e.getMessage()); + } } @EventHandler diff --git a/src/main/java/de/nexuslobby/modules/mapart/MapArtModule.java b/src/main/java/de/nexuslobby/modules/mapart/MapArtModule.java index 8e0a40e..415ce5e 100644 --- a/src/main/java/de/nexuslobby/modules/mapart/MapArtModule.java +++ b/src/main/java/de/nexuslobby/modules/mapart/MapArtModule.java @@ -67,7 +67,7 @@ public class MapArtModule implements Module, CommandExecutor { try { storageFile.createNewFile(); } catch (IOException e) { - e.printStackTrace(); + NexusLobby.getInstance().getLogger().severe("Fehler beim Erstellen der mapart.yml: " + e.getMessage()); } } storageConfig = YamlConfiguration.loadConfiguration(storageFile); @@ -77,7 +77,7 @@ public class MapArtModule implements Module, CommandExecutor { try { storageConfig.save(storageFile); } catch (IOException e) { - e.printStackTrace(); + NexusLobby.getInstance().getLogger().severe("Fehler beim Speichern der mapart.yml: " + e.getMessage()); } } diff --git a/src/main/java/de/nexuslobby/modules/parkour/ParkourManager.java b/src/main/java/de/nexuslobby/modules/parkour/ParkourManager.java index 6ee6c8f..976e45f 100644 --- a/src/main/java/de/nexuslobby/modules/parkour/ParkourManager.java +++ b/src/main/java/de/nexuslobby/modules/parkour/ParkourManager.java @@ -37,7 +37,11 @@ public class ParkourManager { private void loadConfig() { if (!file.exists()) { file.getParentFile().mkdirs(); - try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } + try { + file.createNewFile(); + } catch (IOException e) { + plugin.getLogger().severe("Fehler beim Erstellen der Parkour-Datei: " + e.getMessage()); + } } config = YamlConfiguration.loadConfiguration(file); } @@ -183,7 +187,13 @@ public class ParkourManager { .collect(Collectors.toList()); } - private void save() { try { config.save(file); } catch (IOException e) { e.printStackTrace(); } } + 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); diff --git a/src/main/java/de/nexuslobby/modules/player/PlayerInspectModule.java b/src/main/java/de/nexuslobby/modules/player/PlayerInspectModule.java index 35f8679..a52a1ef 100644 --- a/src/main/java/de/nexuslobby/modules/player/PlayerInspectModule.java +++ b/src/main/java/de/nexuslobby/modules/player/PlayerInspectModule.java @@ -94,9 +94,15 @@ public class PlayerInspectModule implements Module, Listener { List lore = new ArrayList<>(); lore.add("§8§m-----------------------"); - // LuckPerms Prefix über PlaceholderAPI auslesen + // LuckPerms Prefix über PlaceholderAPI auslesen (falls vorhanden) String prefix = "%luckperms_prefix%"; - prefix = PlaceholderAPI.setPlaceholders(target, prefix); + if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { + try { + prefix = PlaceholderAPI.setPlaceholders(target, prefix); + } catch (NoClassDefFoundError ignored) { + // PlaceholderAPI fehlt zur Laufzeit + } + } // KORREKTUR: Farbcodes (&) in echte Minecraft-Farben (§) umwandeln prefix = ChatColor.translateAlternateColorCodes('&', prefix); diff --git a/src/main/java/de/nexuslobby/modules/portal/PortalManager.java b/src/main/java/de/nexuslobby/modules/portal/PortalManager.java index 5dbbb5e..59ba165 100644 --- a/src/main/java/de/nexuslobby/modules/portal/PortalManager.java +++ b/src/main/java/de/nexuslobby/modules/portal/PortalManager.java @@ -46,6 +46,7 @@ public class PortalManager implements Module, Listener { private Location borderMin; private Location borderMax; private boolean borderEnabled = false; + private String borderType; public PortalManager(NexusLobby plugin) { this.plugin = plugin; @@ -77,17 +78,25 @@ public class PortalManager implements Module, Listener { } public void loadBorderSettings() { - if (plugin.getConfig().contains("border.pos1") && plugin.getConfig().contains("border.pos2")) { - Location p1 = plugin.getConfig().getLocation("border.pos1"); - Location p2 = plugin.getConfig().getLocation("border.pos2"); + this.borderType = plugin.getConfig().getString("worldborder.type", "SQUARE"); + boolean enabled = plugin.getConfig().getBoolean("worldborder.enabled", false); + if (!enabled || !"SQUARE".equalsIgnoreCase(borderType)) { + this.borderEnabled = false; + return; + } + + if (plugin.getConfig().contains("worldborder.pos1") && plugin.getConfig().contains("worldborder.pos2")) { + Location p1 = plugin.getConfig().getLocation("worldborder.pos1"); + Location p2 = plugin.getConfig().getLocation("worldborder.pos2"); if (p1 != null && p2 != null) { this.borderMin = getMinLocation(p1, p2); this.borderMax = getMaxLocation(p1, p2); this.borderEnabled = true; + return; } - } else { - this.borderEnabled = false; } + + this.borderEnabled = false; } public Set getPortalNames() { @@ -221,8 +230,7 @@ public class PortalManager implements Module, Listener { try { config.save(new java.io.File(plugin.getDataFolder(), "portals.yml")); } catch (IOException e) { - plugin.getLogger().severe("Konnte portals.yml nicht speichern!"); - e.printStackTrace(); + plugin.getLogger().severe("Fehler beim Speichern der portals.yml: " + e.getMessage()); } } @@ -402,7 +410,7 @@ public class PortalManager implements Module, Listener { out.writeUTF(serverName); player.sendPluginMessage(plugin, "BungeeCord", b.toByteArray()); } catch (IOException e) { - e.printStackTrace(); + plugin.getLogger().warning("Fehler beim Senden der BungeeCord-Nachricht: " + e.getMessage()); } } diff --git a/src/main/java/de/nexuslobby/modules/servers/ServerChecker.java b/src/main/java/de/nexuslobby/modules/servers/ServerChecker.java index 91d6241..2a2fcde 100644 --- a/src/main/java/de/nexuslobby/modules/servers/ServerChecker.java +++ b/src/main/java/de/nexuslobby/modules/servers/ServerChecker.java @@ -5,6 +5,7 @@ import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.World; import org.bukkit.entity.ArmorStand; +import org.bukkit.scheduler.BukkitTask; import java.io.IOException; import java.net.InetSocketAddress; @@ -13,6 +14,8 @@ import java.util.concurrent.CompletableFuture; public class ServerChecker { + private static BukkitTask globalCheckerTask = null; + /** * Prüft asynchron, ob ein Server unter der angegebenen IP und Port erreichbar ist. */ @@ -32,8 +35,11 @@ public class ServerChecker { * Startet den Scheduler, der alle ArmorStands in allen Welten regelmäßig prüft. */ public static void startGlobalChecker() { + // Cancel alten Task falls vorhanden + stopGlobalChecker(); + // WICHTIG: runTaskTimer (synchron), um den Main-Thread für getEntitiesByClass zu nutzen - Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> { + globalCheckerTask = Bukkit.getScheduler().runTaskTimer(NexusLobby.getInstance(), () -> { for (World world : Bukkit.getWorlds()) { // Das Abrufen der Entities muss auf dem Main-Thread passieren for (ArmorStand as : world.getEntitiesByClass(ArmorStand.class)) { @@ -43,6 +49,16 @@ public class ServerChecker { }, 100L, 200L); } + /** + * Stoppt den Server-Checker Task + */ + public static void stopGlobalChecker() { + if (globalCheckerTask != null && !globalCheckerTask.isCancelled()) { + globalCheckerTask.cancel(); + globalCheckerTask = null; + } + } + /** * Analysiert die Tags eines ArmorStands und aktualisiert den Namen basierend auf dem Serverstatus. */ diff --git a/src/main/java/de/nexuslobby/modules/servers/ServerSwitcherGUI.java b/src/main/java/de/nexuslobby/modules/servers/ServerSwitcherGUI.java index 71a09ac..f310819 100644 --- a/src/main/java/de/nexuslobby/modules/servers/ServerSwitcherGUI.java +++ b/src/main/java/de/nexuslobby/modules/servers/ServerSwitcherGUI.java @@ -111,7 +111,7 @@ public class ServerSwitcherGUI { private static void connectToServer(Player player, String serverName, Plugin plugin) { try { if (!Bukkit.getMessenger().isOutgoingChannelRegistered(plugin, "BungeeCord")) { - player.sendMessage(ChatColor.RED + "Proxy-Verbindung nicht möglich (BungeeCord Kanal nicht registriert)."); + player.sendMessage(de.nexuslobby.utils.LangManager.get("proxy_not_registered")); return; } @@ -120,13 +120,13 @@ public class ServerSwitcherGUI { out.writeUTF("Connect"); out.writeUTF(serverName); player.sendPluginMessage(plugin, "BungeeCord", b.toByteArray()); - player.sendMessage(ChatColor.YELLOW + "Verbinde zu: " + ChatColor.WHITE + serverName); + player.sendMessage(de.nexuslobby.utils.LangManager.get("connecting_to_server").replace("{server}", serverName)); } catch (IOException ex) { - ex.printStackTrace(); - player.sendMessage(ChatColor.RED + "Fehler beim Verbinden zum Server."); + NexusLobby.getInstance().getLogger().warning("Fehler beim Senden der BungeeCord-Nachricht: " + ex.getMessage()); + player.sendMessage(de.nexuslobby.utils.LangManager.get("server_connect_error")); } catch (RuntimeException ex) { // Schutz gegen unerwartete RuntimeExceptions bei plugin messaging - player.sendMessage(ChatColor.RED + "Proxy-Verbindung nicht möglich."); + player.sendMessage(de.nexuslobby.utils.LangManager.get("proxy_connect_error")); plugin.getLogger().warning("Fehler beim Senden der Plugin-Message: " + ex.getMessage()); } } diff --git a/src/main/java/de/nexuslobby/modules/servers/ServerSwitcherListener.java b/src/main/java/de/nexuslobby/modules/servers/ServerSwitcherListener.java index 5d56000..9c3925c 100644 --- a/src/main/java/de/nexuslobby/modules/servers/ServerSwitcherListener.java +++ b/src/main/java/de/nexuslobby/modules/servers/ServerSwitcherListener.java @@ -4,6 +4,9 @@ import de.nexuslobby.NexusLobby; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -19,7 +22,7 @@ import org.bukkit.inventory.meta.ItemMeta; import java.util.ArrayList; import java.util.List; -public class ServerSwitcherListener implements Listener { +public class ServerSwitcherListener implements Listener, CommandExecutor { @EventHandler public void onPlayerJoin(PlayerJoinEvent e) { @@ -51,6 +54,23 @@ public class ServerSwitcherListener implements Listener { } } + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(de.nexuslobby.utils.LangManager.get("only_player")); + return true; + } + + Player player = (Player) sender; + if (!player.hasPermission("nexuslobby.serverswitcher")) { + player.sendMessage(de.nexuslobby.utils.LangManager.get("no_permission")); + return true; + } + + openServerGUI(player); + return true; + } + @EventHandler public void onCompassClick(PlayerInteractEvent e) { Player p = e.getPlayer(); diff --git a/src/main/java/de/nexuslobby/modules/settings/LobbySettingsModule.java b/src/main/java/de/nexuslobby/modules/settings/LobbySettingsModule.java index 0ce25ba..01b1055 100644 --- a/src/main/java/de/nexuslobby/modules/settings/LobbySettingsModule.java +++ b/src/main/java/de/nexuslobby/modules/settings/LobbySettingsModule.java @@ -61,6 +61,7 @@ public class LobbySettingsModule implements Module, Listener { } // Hilfsmethode zur sicheren Anwendung von GameRules in 1.21 + @SuppressWarnings("unchecked") private void updateGameRule(World world, String ruleName, Object value) { GameRule rule = GameRule.getByName(ruleName); if (rule == null) return; @@ -117,6 +118,7 @@ public class LobbySettingsModule implements Module, Listener { Material mat = isBool ? ((Boolean)value ? Material.LIME_DYE : Material.GRAY_DYE) : Material.BOOK; ItemStack item = new ItemStack(mat); ItemMeta meta = item.getItemMeta(); + if (meta == null) return item; meta.setDisplayName("§e" + key); List lore = new ArrayList<>(); lore.add("§7Aktueller Wert: §f" + value); @@ -160,13 +162,17 @@ public class LobbySettingsModule implements Module, Listener { private void handleSubClick(InventoryClickEvent event) { Player p = (Player) event.getWhoClicked(); - if (event.getCurrentItem() == null || event.getCurrentItem().getType() == Material.AIR) return; - if (event.getCurrentItem().getType() == Material.BARRIER) { + ItemStack current = event.getCurrentItem(); + if (current == null || current.getType() == Material.AIR) return; + if (current.getType() == Material.BARRIER) { openMainMenu(p); return; } - - String key = event.getCurrentItem().getItemMeta().getDisplayName().substring(2); + ItemMeta meta = current.getItemMeta(); + if (meta == null || !meta.hasDisplayName()) return; + String displayName = meta.getDisplayName(); + if (displayName.length() < 3) return; + String key = displayName.substring(2); String path = settingsConfig.contains("gamerules." + key) ? "gamerules." + key : key; Object value = settingsConfig.get(path); String category = event.getView().getTitle().replace(subTitlePrefix, ""); @@ -203,12 +209,17 @@ public class LobbySettingsModule implements Module, Listener { } private void saveSettings() { - try { settingsConfig.save(configFile); } catch (IOException e) { e.printStackTrace(); } + try { + settingsConfig.save(configFile); + } catch (IOException e) { + NexusLobby.getInstance().getLogger().severe("Fehler beim Speichern der settings.yml: " + e.getMessage()); + } } private ItemStack createItem(Material mat, String name, String lore) { ItemStack item = new ItemStack(mat); ItemMeta meta = item.getItemMeta(); + if (meta == null) return item; meta.setDisplayName(name); if (!lore.isEmpty()) { List lores = new ArrayList<>(); diff --git a/src/main/java/de/nexuslobby/modules/suppressor/GlobalChatSuppressor.java b/src/main/java/de/nexuslobby/modules/suppressor/GlobalChatSuppressor.java index 0f9a180..880e776 100644 --- a/src/main/java/de/nexuslobby/modules/suppressor/GlobalChatSuppressor.java +++ b/src/main/java/de/nexuslobby/modules/suppressor/GlobalChatSuppressor.java @@ -84,7 +84,7 @@ public class GlobalChatSuppressor implements Module, PluginMessageListener, List } } } catch (IOException e) { - e.printStackTrace(); + Bukkit.getLogger().severe("Fehler beim Abrufen des Conversation-Managers: " + e.getMessage()); } } diff --git a/src/main/java/de/nexuslobby/utils/ConfigUpdater.java b/src/main/java/de/nexuslobby/utils/ConfigUpdater.java index 675f7f2..8873deb 100644 --- a/src/main/java/de/nexuslobby/utils/ConfigUpdater.java +++ b/src/main/java/de/nexuslobby/utils/ConfigUpdater.java @@ -1,6 +1,7 @@ package de.nexuslobby.utils; import de.nexuslobby.NexusLobby; +import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; @@ -23,7 +24,13 @@ public class ConfigUpdater { FileConfiguration serverConfig = YamlConfiguration.loadConfiguration(configFile); List newFileContent = new ArrayList<>(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(plugin.getResource(fileName), StandardCharsets.UTF_8))) { + InputStream resourceStream = plugin.getResource(fileName); + if (resourceStream == null) { + plugin.getLogger().severe("Config-Update fehlgeschlagen: Resource fehlt: " + fileName); + return; + } + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceStream, StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { String trimmed = line.trim(); @@ -52,7 +59,7 @@ public class ConfigUpdater { } } } catch (IOException e) { - e.printStackTrace(); + Bukkit.getLogger().severe("Fehler beim Config-Update: " + e.getMessage()); } // 3. Datei sauber speichern diff --git a/src/main/java/de/nexuslobby/utils/ConfigValidator.java b/src/main/java/de/nexuslobby/utils/ConfigValidator.java new file mode 100644 index 0000000..4470523 --- /dev/null +++ b/src/main/java/de/nexuslobby/utils/ConfigValidator.java @@ -0,0 +1,155 @@ +package de.nexuslobby.utils; + +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.ArrayList; +import java.util.List; + +/** + * Validiert Config-Werte und setzt Default-Werte wenn nötig + */ +public class ConfigValidator { + + private final JavaPlugin plugin; + private final FileConfiguration config; + private final List warnings = new ArrayList<>(); + private boolean modified = false; + + public ConfigValidator(JavaPlugin plugin, FileConfiguration config) { + this.plugin = plugin; + this.config = config; + } + + /** + * Validiert alle Config-Werte und gibt Warnings aus + */ + public void validate() { + validateSpawn(); + validateLobbySettings(); + validateTablist(); + validateBall(); + validateModules(); + + // Warnings ausgeben + if (!warnings.isEmpty()) { + plugin.getLogger().warning("=== Config-Validierung ==="); + warnings.forEach(msg -> plugin.getLogger().warning(" " + msg)); + plugin.getLogger().warning("========================="); + } + + // Config speichern wenn Änderungen vorgenommen wurden + if (modified) { + plugin.saveConfig(); + plugin.getLogger().info("Config wurde mit Default-Werten aktualisiert."); + } + } + + private void validateSpawn() { + ensureDefault("spawn.world", "world"); + ensureDefault("spawn.x", 0.5); + ensureDefault("spawn.y", 64.0); + ensureDefault("spawn.z", 0.5); + ensureDefault("spawn.yaw", 0.0); + ensureDefault("spawn.pitch", 0.0); + + // Validiere Y-Koordinate + double y = config.getDouble("spawn.y", 64.0); + if (y < -64 || y > 320) { + warnings.add("spawn.y (" + y + ") außerhalb des gültigen Bereichs (-64 bis 320)"); + } + } + + private void validateLobbySettings() { + ensureDefault("lobby.allow-fly", false); + ensureDefault("lobby.pvp-enabled", false); + ensureDefault("lobby.build-enabled", false); + ensureDefault("lobby.default-gamemode", "Adventure"); + ensureDefault("lobby.clear-inventory-on-join", true); + + // Validiere GameMode + String gamemode = config.getString("lobby.default-gamemode", "Adventure"); + if (!isValidGameMode(gamemode)) { + warnings.add("lobby.default-gamemode '" + gamemode + "' ist ungültig. Nutze: Survival, Creative, Adventure oder Spectator"); + } + } + + private void validateTablist() { + ensureDefault("tablist.enabled", true); + ensureDefault("tablist.header", "&6Willkommen auf &eNexusLobby"); + ensureDefault("tablist.footer", "&7Viel Spaß!"); + ensureDefault("tablist.refresh-interval", 40); + + // Validiere Refresh-Interval + int interval = config.getInt("tablist.refresh-interval", 40); + if (interval < 1) { + warnings.add("tablist.refresh-interval (" + interval + ") ist zu klein. Minimum ist 1 Tick."); + config.set("tablist.refresh-interval", 1); + modified = true; + } else if (interval > 200) { + warnings.add("tablist.refresh-interval (" + interval + ") ist sehr hoch. Empfohlen: 20-100 Ticks."); + } + } + + private void validateBall() { + ensureDefault("ball.respawn_delay", 60); + + // Validiere Respawn-Delay + long delay = config.getLong("ball.respawn_delay", 60); + if (delay < 5) { + warnings.add("ball.respawn_delay (" + delay + "s) ist zu kurz. Minimum empfohlen: 5 Sekunden."); + } else if (delay > 600) { + warnings.add("ball.respawn_delay (" + delay + "s) ist sehr lang. Empfohlen: 30-120 Sekunden."); + } + + // Prüfe ob Spawn gesetzt ist + if (!config.contains("ball.spawn")) { + warnings.add("ball.spawn nicht gesetzt. Nutze /nexuslobby ball setspawn"); + } + } + + private void validateModules() { + // Worldborder + if (config.contains("worldborder.enabled")) { + ensureDefault("worldborder.type", "SQUARE"); + ensureDefault("worldborder.radius", 50.0); + + double radius = config.getDouble("worldborder.radius", 50.0); + if (radius < 10) { + warnings.add("worldborder.radius (" + radius + ") ist sehr klein. Minimum empfohlen: 10 Blöcke."); + } else if (radius > 10000) { + warnings.add("worldborder.radius (" + radius + ") ist extrem groß. Performance-Warnung!"); + } + + String type = config.getString("worldborder.type", "SQUARE"); + if (!type.equalsIgnoreCase("SQUARE") && !type.equalsIgnoreCase("CIRCLE")) { + warnings.add("worldborder.type '" + type + "' ist ungültig. Nutze: SQUARE oder CIRCLE"); + } + } + + // Items Modul + if (config.contains("items.lobby-tools")) { + // Prüfe ob Slot-Nummern gültig sind (0-8) + for (String toolKey : config.getConfigurationSection("items.lobby-tools").getKeys(false)) { + int slot = config.getInt("items.lobby-tools." + toolKey + ".slot", -1); + if (slot < 0 || slot > 8) { + warnings.add("items.lobby-tools." + toolKey + ".slot (" + slot + ") ist ungültig (0-8)"); + } + } + } + } + + private void ensureDefault(String path, Object defaultValue) { + if (!config.contains(path)) { + config.set(path, defaultValue); + modified = true; + } + } + + private boolean isValidGameMode(String mode) { + return mode.equalsIgnoreCase("Survival") || + mode.equalsIgnoreCase("Creative") || + mode.equalsIgnoreCase("Adventure") || + mode.equalsIgnoreCase("Spectator"); + } +} diff --git a/src/main/java/de/nexuslobby/utils/LangManager.java b/src/main/java/de/nexuslobby/utils/LangManager.java new file mode 100644 index 0000000..83c8b9e --- /dev/null +++ b/src/main/java/de/nexuslobby/utils/LangManager.java @@ -0,0 +1,32 @@ +package de.nexuslobby.utils; + +import org.bukkit.plugin.java.JavaPlugin; +import org.yaml.snakeyaml.Yaml; + +import java.io.InputStream; +import java.util.Map; + +public class LangManager { + private static Map> langMap; + private static String currentLang = "de"; + + public static void load(JavaPlugin plugin) { + Yaml yaml = new Yaml(); + try (InputStream in = plugin.getResource("lang.yml")) { + langMap = yaml.load(in); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void setLanguage(String lang) { + currentLang = lang; + } + + public static String get(String key) { + if (langMap == null) return key; + Map translations = langMap.get(key); + if (translations == null) return key; + return translations.getOrDefault(currentLang, key); + } +} diff --git a/src/main/java/de/nexuslobby/utils/PlayerHider.java b/src/main/java/de/nexuslobby/utils/PlayerHider.java index 078baaa..184418d 100644 --- a/src/main/java/de/nexuslobby/utils/PlayerHider.java +++ b/src/main/java/de/nexuslobby/utils/PlayerHider.java @@ -23,14 +23,23 @@ public class PlayerHider implements Listener { @EventHandler public void onJoin(PlayerJoinEvent event) { if (!NexusLobby.getInstance().getConfig().getBoolean("hider.enabled", true)) return; - - Material material = Material.valueOf(NexusLobby.getInstance().getConfig().getString("hider.item", "REDSTONE")); - int slot = NexusLobby.getInstance().getConfig().getInt("hider.slot", 8); + + var config = NexusLobby.getInstance().getConfig(); + Material material; + try { + material = Material.valueOf(config.getString("hider.item", "REDSTONE")); + } catch (IllegalArgumentException e) { + material = Material.REDSTONE; + } + int slot = config.getInt("hider.slot", 8); ItemStack item = new ItemStack(material); ItemMeta meta = item.getItemMeta(); - meta.setDisplayName(NexusLobby.getInstance().getConfig().getString("hider.messages.all").replace("&", "§")); - item.setItemMeta(meta); + if (meta != null) { + String allMsg = colorize(config.getString("hider.messages.all", "&aAlle Spieler")); + meta.setDisplayName(allMsg); + item.setItemMeta(meta); + } event.getPlayer().getInventory().setItem(slot, item); } @@ -39,32 +48,40 @@ public class PlayerHider implements Listener { public void onInteract(PlayerInteractEvent event) { if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) return; if (event.getItem() == null) return; + if (!event.getItem().hasItemMeta()) return; + if (event.getItem().getItemMeta() == null) return; + if (!event.getItem().getItemMeta().hasDisplayName()) return; Player player = event.getPlayer(); - String allMsg = NexusLobby.getInstance().getConfig().getString("hider.messages.all").replace("&", "§"); - String noneMsg = NexusLobby.getInstance().getConfig().getString("hider.messages.none").replace("&", "§"); + var config = NexusLobby.getInstance().getConfig(); + String allMsg = de.nexuslobby.utils.LangManager.get("hider_all"); + String noneMsg = de.nexuslobby.utils.LangManager.get("hider_none"); if (event.getItem().getItemMeta().getDisplayName().equals(allMsg)) { // Verstecken hidden.add(player.getUniqueId()); for (Player target : Bukkit.getOnlinePlayers()) player.hidePlayer(NexusLobby.getInstance(), target); - updateItem(player, noneMsg); player.sendMessage(noneMsg); } else if (event.getItem().getItemMeta().getDisplayName().equals(noneMsg)) { // Zeigen hidden.remove(player.getUniqueId()); for (Player target : Bukkit.getOnlinePlayers()) player.showPlayer(NexusLobby.getInstance(), target); - updateItem(player, allMsg); player.sendMessage(allMsg); } } private void updateItem(Player player, String name) { - ItemStack item = player.getInventory().getItemInHand(); + ItemStack item = player.getInventory().getItemInMainHand(); + if (item == null || !item.hasItemMeta()) return; ItemMeta meta = item.getItemMeta(); + if (meta == null) return; meta.setDisplayName(name); item.setItemMeta(meta); } + + private String colorize(String s) { + return s == null ? "" : s.replace("&", "§"); + } } \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/utils/UpdateChecker.java b/src/main/java/de/nexuslobby/utils/UpdateChecker.java index d3ead38..a45bcd3 100644 --- a/src/main/java/de/nexuslobby/utils/UpdateChecker.java +++ b/src/main/java/de/nexuslobby/utils/UpdateChecker.java @@ -1,18 +1,24 @@ package de.nexuslobby.utils; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; import de.nexuslobby.NexusLobby; import org.bukkit.Bukkit; + +import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; import java.net.URL; -import java.util.Scanner; +import java.nio.charset.StandardCharsets; import java.util.function.Consumer; public class UpdateChecker { private final NexusLobby plugin; - // URL zur Gitea API für das neueste Release - private final String url = "https://git.viper.ipv64.net/api/v1/repos/M_Viper/NexusLobby/releases/latest"; + private static final String API_URL = "https://git.viper.ipv64.net/api/v1/repos/M_Viper/NexusLobby/releases/latest"; + private static final int TIMEOUT_MS = 5000; public UpdateChecker(NexusLobby plugin) { this.plugin = plugin; @@ -20,23 +26,60 @@ public class UpdateChecker { public void getVersion(final Consumer consumer) { Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> { - try (InputStream inputStream = new URL(url).openStream(); Scanner scanner = new Scanner(inputStream)) { - StringBuilder response = new StringBuilder(); - while (scanner.hasNextLine()) { - response.append(scanner.nextLine()); + HttpURLConnection connection = null; + try { + URL url = new URL(API_URL); + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(TIMEOUT_MS); + connection.setReadTimeout(TIMEOUT_MS); + connection.setRequestProperty("Accept", "application/json"); + connection.setRequestProperty("User-Agent", "NexusLobby-UpdateChecker"); + + int responseCode = connection.getResponseCode(); + if (responseCode != HttpURLConnection.HTTP_OK) { + plugin.getLogger().warning("Update-Check fehlgeschlagen: HTTP " + responseCode); + return; } - - String content = response.toString(); - // Einfaches Parsing des JSON "tag_name" Feldes - if (content.contains("\"tag_name\":")) { - String version = content.split("\"tag_name\":\"")[1].split("\"")[0]; - // Entferne ein eventuelles 'v' Präfix (z.B. v1.0.0 -> 1.0.0) - version = version.replace("v", ""); - consumer.accept(version); + + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { + + StringBuilder response = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + + parseAndNotify(response.toString(), consumer); + } + + } catch (IOException e) { + plugin.getLogger().warning("Update-Check fehlgeschlagen: " + e.getMessage()); + } finally { + if (connection != null) { + connection.disconnect(); } - } catch (IOException exception) { - this.plugin.getLogger().warning("Update-Check fehlgeschlagen: " + exception.getMessage()); } }); } + + private void parseAndNotify(String jsonResponse, Consumer consumer) { + try { + JsonObject json = JsonParser.parseString(jsonResponse).getAsJsonObject(); + + if (json.has("tag_name")) { + String version = json.get("tag_name").getAsString(); + // Entferne 'v' Präfix falls vorhanden (z.B. v1.0.0 -> 1.0.0) + if (version.startsWith("v")) { + version = version.substring(1); + } + consumer.accept(version); + } else { + plugin.getLogger().warning("Update-Check: 'tag_name' nicht in API-Response gefunden"); + } + } catch (JsonSyntaxException e) { + plugin.getLogger().warning("Update-Check: Ungültiges JSON-Format - " + e.getMessage()); + } + } } \ No newline at end of file diff --git a/src/main/java/de/nexuslobby/utils/VoidProtection.java b/src/main/java/de/nexuslobby/utils/VoidProtection.java index 9efc30c..00bdb5f 100644 --- a/src/main/java/de/nexuslobby/utils/VoidProtection.java +++ b/src/main/java/de/nexuslobby/utils/VoidProtection.java @@ -19,9 +19,9 @@ public class VoidProtection implements Listener { if (NexusLobby.getInstance().getConfig().getBoolean("void_protection.teleport_to_spawn", true)) { Location spawn = player.getWorld().getSpawnLocation(); player.teleport(spawn); - String msg = NexusLobby.getInstance().getConfig().getString("void_protection.message"); + String msg = de.nexuslobby.utils.LangManager.get("void_protection_message"); if (msg != null && !msg.isEmpty()) { - player.sendMessage(msg.replace("&", "§")); + player.sendMessage(msg); } } } diff --git a/src/main/resources/lang.yml b/src/main/resources/lang.yml new file mode 100644 index 0000000..d94b1f8 --- /dev/null +++ b/src/main/resources/lang.yml @@ -0,0 +1,192 @@ +soccer_module_not_loaded: + de: "§cDas Fußball-Modul ist nicht geladen." + en: "§cThe soccer module is not loaded." +parkour_usage: + de: "§8[§6Nexus§8] §7Nutze: §e/nexus parkour " + en: "§8[§6Nexus§8] §7Usage: §e/nexus parkour " +parkour_run_aborted: + de: "§8[§6Nexus§8] §7Dein aktueller Lauf wurde abgebrochen." + en: "§8[§6Nexus§8] §7Your current run was aborted." +parkour_besttimes_cleared: + de: "§8[§6Nexus§8] §aAlle Parkour-Bestzeiten wurden gelöscht!" + en: "§8[§6Nexus§8] §aAll parkour best times have been cleared!" +parkour_track_removed: + de: "§8[§6Nexus§8] §cDie gesamte Strecke (Checkpoints & Ziel) wurde gelöscht!" + en: "§8[§6Nexus§8] §cThe entire track (checkpoints & finish) has been removed!" +unknown_subcommand: + de: "§cUnbekannter Unterbefehl." + en: "§cUnknown subcommand." +parkour_npc_marked: + de: "§8[§6Nexus§8] §aArmorStand als Parkour-NPC markiert!" + en: "§8[§6Nexus§8] §aArmorStand marked as parkour NPC!" +parkour_start_set: + de: "§8[§6Nexus§8] §aParkour-Startpunkt an deiner Position gesetzt!" + en: "§8[§6Nexus§8] §aParkour start set at your position!" +scoreboard_usage: + de: "§cBenutzung: /nexus sb " + en: "§cUsage: /nexus sb " +scoreboard_module_disabled: + de: "§cScoreboard-Modul ist deaktiviert." + en: "§cScoreboard module is disabled." +info_header: + de: "§8§m--------------------------------------" + en: "§8§m--------------------------------------" +info_title: + de: "§6§lNexusLobby §7- Informationen" + en: "§6§lNexusLobby §7- Information" +info_spawn: + de: "§f/spawn §7- Zum Spawn" + en: "§f/spawn §7- To spawn" +info_parkour: + de: "§f/setstart §8| §f/setcheckpoint §8| §f/setfinish" + en: "§f/setstart §8| §f/setcheckpoint §8| §f/setfinish" +info_removeall: + de: "§f/nexus parkour removeall §7- Strecke löschen" + en: "§f/nexus parkour removeall §7- Remove track" +info_ball: + de: "§f/nexus ball setspawn §7- Fußball Spawn setzen" + en: "§f/nexus ball setspawn §7- Set soccer spawn" +info_setspawn: + de: "§f/nexus setspawn §7- Spawn setzen" + en: "§f/nexus setspawn §7- Set spawn" +info_scoreboard: + de: "§f/nexus sb §7- Scoreboard" + en: "§f/nexus sb §7- Scoreboard" +info_reload: + de: "§f/nexus reload §7- Config laden" + en: "§f/nexus reload §7- Reload config" +info_footer: + de: "§8§m--------------------------------------" + en: "§8§m--------------------------------------" +parkour_trainer_greeting: + de: "§6§lTrainer §8» §aViel Erfolg beim Parkour! Gib dein Bestes!" + en: "§6§lTrainer §8» §aGood luck with the parkour! Give it your best!" +update_available: + de: "§8[§6Nexus§8] §aEin neues §6Update §afür §eNexusLobby §aist verfügbar!" + en: "§8[§6Nexus§8] §aA new §6update §afor §eNexusLobby §ais available!" +update_version: + de: "§8» §7Version: §c{old} §8-> §a{new}" + en: "§8» §7Version: §c{old} §8-> §a{new}" +update_download_link: + de: "§8» §6Klicke §e§l[HIER] §6zum Herunterladen." + en: "§8» §6Click §e§l[HERE] §6to download." +update_download_hover: + de: "§7Öffnet die Release-Seite" + en: "§7Opens the release page" +hider_all: + de: "§aAlle Spieler" + en: "§aAll players" +hider_none: + de: "§cKeine Spieler" + en: "§cNo players" +void_protection_message: + de: "§cDu wurdest vor dem Void gerettet!" + en: "§cYou were saved from the void!" +proxy_not_registered: + de: "§cProxy-Verbindung nicht möglich (BungeeCord Kanal nicht registriert)." + en: "§cProxy connection not possible (BungeeCord channel not registered)." +connecting_to_server: + de: "§eVerbinde zu: §f{server}" + en: "§eConnecting to: §f{server}" +server_connect_error: + de: "§cFehler beim Verbinden zum Server." + en: "§cError connecting to server." +proxy_connect_error: + de: "§cProxy-Verbindung nicht möglich." + en: "§cProxy connection not possible." +armorstand_lookat_required: + de: "§cDu musst einen ArmorStand direkt anschauen (Fadenkreuz)!" + en: "§cYou must look directly at an ArmorStand (crosshair)!" +conv_link_usage: + de: "§cNutze: /nexuscmd conv link " + en: "§cUsage: /nexuscmd conv link " +conv_mark_npcs: + de: "§cBitte markiere mindestens die ersten beiden NPCs (select1 & select2)!" + en: "§cPlease mark at least the first two NPCs (select1 & select2)!" +conv_link_created: + de: "§a§lDauerhafte Verknüpfung erstellt!" + en: "§a§lPermanent link created!" +conv_link_npcs: + de: "§7Beteiligte NPCs: §e{count}" + en: "§7Linked NPCs: §e{count}" +conv_speaker_not_found: + de: "§cFehler: Sprecher 1 nicht gefunden." + en: "§cError: Speaker 1 not found." +conv_unlink_lookat: + de: "§cSchau den NPC an, dessen Verknüpfung du lösen willst!" + en: "§cLook at the NPC you want to unlink!" +conv_unlinked: + de: "§eNPC-Verknüpfung wurde aufgehoben." + en: "§eNPC link has been removed." +conv_start_usage: + de: "§cNutze: /nexuscmd conv start " + en: "§cUsage: /nexuscmd conv start " +conv_mark_two_npcs: + de: "§cBitte markiere mindestens zwei NPCs!" + en: "§cPlease mark at least two NPCs!" +npc_bubble_sent: + de: "§aNPC-Sprechblase gesendet." + en: "§aNPC speech bubble sent." +lookat_off: + de: "§cBlickkontakt aus." + en: "§cLook-at disabled." +lookat_on: + de: "§aBlickkontakt an." + en: "§aLook-at enabled." +name_removed: + de: "§eName entfernt." + en: "§eName removed." +name_set: + de: "§7Name gesetzt: {name}" + en: "§7Name set: {name}" +status_backup_saved: + de: "§8(Status-Backup wurde gespeichert)" + en: "§8(Status backup saved)" +command_bound: + de: "§aBefehl gebunden." + en: "§aCommand bound." +no_target: + de: "§cKein Ziel!" + en: "§cNo target!" +commands_and_tags: + de: "§6§lBefehle & Tags:" + en: "§6§lCommands & Tags:" +commands_removed: + de: "§eAlle Befehle gelöscht." + en: "§eAll commands removed." +welcome: + de: "Willkommen auf dem Server!" + en: "Welcome to the server!" +no_permission: + de: "§cKeine Berechtigung." + en: "§cNo permission." +only_player: + de: "§cDieser Befehl ist nur für Spieler!" + en: "§cThis command is for players only!" +parkour_checkpoint_set: + de: "§8[§6Nexus§8] §aParkour-Checkpoint gesetzt!" + en: "§8[§6Nexus§8] §aParkour checkpoint set!" +parkour_finish_set: + de: "§8[§6Nexus§8] §aParkour-Zielpunkt gesetzt!" + en: "§8[§6Nexus§8] §aParkour finish set!" +teleport_spawn: + de: "§8[§6Nexus§8] §aDu wurdest zum Spawn teleportiert." + en: "§8[§6Nexus§8] §aYou have been teleported to spawn." +spawn_world_missing: + de: "§cFehler: Die Spawn-Welt existiert nicht." + en: "§cError: The spawn world does not exist." +spawn_not_set: + de: "§cEs wurde noch kein Spawn gesetzt." + en: "§cNo spawn has been set yet." +plugin_reloaded: + de: "§8[§6Nexus§8] §aPlugin erfolgreich neu geladen!" + en: "§8[§6Nexus§8] §aPlugin successfully reloaded!" +spawn_set: + de: "§8[§6Nexus§8] §aLobby-Spawn erfolgreich gesetzt!" + en: "§8[§6Nexus§8] §aLobby spawn set successfully!" +silentjoin_on: + de: "§8[§6Nexus§8] §7Silent Join: §aAktiviert" + en: "§8[§6Nexus§8] §7Silent Join: §aEnabled" +silentjoin_off: + de: "§8[§6Nexus§8] §7Silent Join: §cDeaktiviert" + en: "§8[§6Nexus§8] §7Silent Join: §cDisabled" diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 7ec7d1e..b7ae426 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.0" +version: "1.1.1" api-version: "1.21" author: M_Viper description: Modular Lobby Plugin with an invisible Particle-Parkour system. @@ -125,6 +125,18 @@ permissions: nexuslobby.silentjoin: description: Versteckt die Join-Nachricht für den Spieler (Silent Join) default: op + nexuslobby.join.maintenance: + description: Darf den Server trotz Wartungsmodus betreten + default: op + nexuslobby.security.bypass: + description: Umgeht den Security-Check (VPN/Country) + default: op + nexuslobby.scoreboard.admin: + description: Darf das Scoreboard im Admin-Modus nutzen + default: op + nexuslobby.border.bypass: + description: Umgeht die Lobby-Grenze + default: op nexuslobby.parkour.admin: description: Erlaubt das Setzen der unsichtbaren Parkour-Punkte (Start, Ziel, Checkpoints) default: op \ No newline at end of file