From 8e9d7bec215a13d6903900bacbce5a3b5a370581 Mon Sep 17 00:00:00 2001 From: Git Manager GUI Date: Tue, 26 May 2026 14:33:22 +0200 Subject: [PATCH] Upload folder via GUI - src --- .../main/java/net/viper/status/StatusAPI.java | 90 ++- .../java/net/viper/status/UpdateChecker.java | 4 +- .../java/net/viper/status/module/Module.java | 2 +- .../viper/status/module/ModuleManager.java | 6 +- .../AutoMessage/AutoMessageModule.java | 16 +- .../viper/status/modules/afk/AfkModule.java | 621 ++++++++++++++++++ .../status/modules/antibot/AntiBotModule.java | 4 +- .../modules/broadcast/BroadcastModule.java | 10 +- .../modules/chat/AccountLinkManager.java | 58 +- .../status/modules/chat/BlockManager.java | 10 +- .../status/modules/chat/ChatChannel.java | 6 +- .../viper/status/modules/chat/ChatConfig.java | 24 +- .../viper/status/modules/chat/ChatFilter.java | 28 +- .../viper/status/modules/chat/ChatLogger.java | 20 +- .../viper/status/modules/chat/ChatModule.java | 136 ++-- .../status/modules/chat/EmojiParser.java | 10 +- .../status/modules/chat/MuteManager.java | 10 +- .../modules/chat/PrivateMsgManager.java | 18 +- .../status/modules/chat/ReportManager.java | 28 +- .../status/modules/chat/VanishProvider.java | 6 +- .../modules/chat/bridge/DiscordBridge.java | 18 +- .../modules/chat/bridge/TelegramBridge.java | 34 +- .../commandblocker/CommandBlockerModule.java | 2 +- .../customcommands/CustomCommandModule.java | 6 +- .../modules/economy/EcoAdminCommand.java | 2 +- .../modules/economy/EconomyDatabase.java | 18 +- .../modules/economy/EconomyListener.java | 12 +- .../modules/economy/EconomyManager.java | 2 +- .../status/modules/economy/EconomyModule.java | 14 +- .../status/modules/economy/PayCommand.java | 4 +- .../modules/forum/ForumBridgeModule.java | 38 +- .../modules/forum/ForumNotifStorage.java | 8 +- .../modules/forum/ForumNotification.java | 14 +- .../viper/status/modules/help/HelpModule.java | 32 +- .../modules/network/MultiAccountGuard.java | 28 +- .../modules/network/NetworkInfoModule.java | 16 +- .../modules/scoreboard/ScoreboardModule.java | 259 ++++++-- .../serverswitcher/ServerSwitcherModule.java | 8 +- .../status/modules/tablist/TablistModule.java | 93 +-- .../status/modules/vanish/VanishModule.java | 20 +- .../status/modules/verify/VerifyModule.java | 8 +- .../net/viper/status/stats/PlayerStats.java | 6 +- .../net/viper/status/stats/StatsModule.java | 22 +- StatusAPI/src/main/resources/plugin.yml | 12 +- 44 files changed, 1294 insertions(+), 489 deletions(-) create mode 100644 StatusAPI/src/main/java/net/viper/status/modules/afk/AfkModule.java diff --git a/StatusAPI/src/main/java/net/viper/status/StatusAPI.java b/StatusAPI/src/main/java/net/viper/status/StatusAPI.java index eee60e6..7f6721e 100644 --- a/StatusAPI/src/main/java/net/viper/status/StatusAPI.java +++ b/StatusAPI/src/main/java/net/viper/status/StatusAPI.java @@ -56,10 +56,13 @@ public class StatusAPI extends Plugin implements Runnable { // Kontostand pro Spieler (UUID -> Balance), wird von StatusAPIBridge gepusht public static final ConcurrentHashMap playerBalances = new ConcurrentHashMap<>(); - // PlaceholderAPI-Werte pro Spieler (UUID -> (placeholder -> aufgelöster Wert)) + // PlaceholderAPI-Werte pro Spieler (UUID -> (placeholder -> aufgel\u00f6ster Wert)) public static final ConcurrentHashMap> playerPapi = new ConcurrentHashMap<>(); - /** Alle %token%-Tokens aus den Config-Dateien – als JSON-Array für GET /papi/tokens */ + // AFK-Status pro Spieler (UUID -> true wenn AFK), wird von StatusAPIBridge gepusht + public static final ConcurrentHashMap playerAfk = new ConcurrentHashMap<>(); + + /** Alle %token%-Tokens aus den Config-Dateien – als JSON-Array f\u00fcr GET /papi/tokens */ public static volatile String papiTokensJson = "[]"; // Debug-Modus (aus verify.properties) @@ -102,7 +105,7 @@ public class StatusAPI extends Plugin implements Runnable { try { port = Integer.parseInt(portStr); } catch (NumberFormatException e) { - getLogger().warning("Ungültiger Port in verify.properties, nutze Standard-Port 9191."); + getLogger().warning("Ung\u00fcltiger Port in verify.properties, nutze Standard-Port 9191."); port = 9191; } @@ -112,7 +115,8 @@ public class StatusAPI extends Plugin implements Runnable { moduleManager = new ModuleManager(); // Module in korrekter Reihenfolge registrieren - // VanishModule MUSS vor ChatModule registriert werden (VanishProvider-Abhängigkeit) + // VanishModule MUSS vor ChatModule registriert werden (VanishProvider-Abh\u00e4ngigkeit) + moduleManager.registerModule(new net.viper.status.modules.afk.AfkModule()); moduleManager.registerModule(new StatsModule()); moduleManager.registerModule(new HelpModule()); moduleManager.registerModule(new VerifyModule()); @@ -280,7 +284,7 @@ public class StatusAPI extends Plugin implements Runnable { if (updateChecker.isUpdateAvailable(currentVersion)) { String newVersion = updateChecker.getLatestVersion(); getLogger().warning("----------------------------------------"); - getLogger().warning("Neue Version verfügbar: " + newVersion); + getLogger().warning("Neue Version verf\u00fcgbar: " + newVersion); getLogger().warning("Download: " + updateChecker.getLatestUrl()); getLogger().warning("----------------------------------------"); } @@ -308,7 +312,7 @@ public class StatusAPI extends Plugin implements Runnable { Socket clientSocket = localServerSocket.accept(); submitConnection(clientSocket); } catch (SocketTimeoutException ignored) { - // Poll-Schleife für Interrupt/Shutdown + // Poll-Schleife f\u00fcr Interrupt/Shutdown } catch (IOException e) { if (!shuttingDown) { getLogger().warning("Fehler beim Akzeptieren einer Verbindung: " + e.getMessage()); @@ -578,7 +582,7 @@ public class StatusAPI extends Plugin implements Runnable { playerMap.put("kills", ps.kills); playerMap.put("deaths", ps.deaths); playerMap.put("online", ProxyServer.getInstance().getPlayer(ps.uuid) != null); - // Balance direkt aus MySQL (serverübergreifend) + // Balance direkt aus MySQL (server\u00fcbergreifend) double playerBalance = playerBalances.getOrDefault(ps.uuid, 0.0); Map economy = new LinkedHashMap<>(); economy.put("balance", playerBalance); @@ -603,7 +607,7 @@ public class StatusAPI extends Plugin implements Runnable { // Kein Cache – UUID und Balance kommen direkt aus der DB if ("GET".equalsIgnoreCase(method) && "/economy/player".equalsIgnoreCase(pathOnly)) { Map qp = parseQueryParams(path); - // UUID auflösen aus Query-Param + // UUID aufl\u00f6sen aus Query-Param UUID ecoUuid = null; String ecoName = null; String uuidParam = qp.get("uuid"); @@ -616,7 +620,7 @@ public class StatusAPI extends Plugin implements Runnable { return; } if (ecoName == null) ecoName = uuidParam; - // Balance aus playerBalances Map lesen (befüllt von StatusAPIBridge via NexEco) + // Balance aus playerBalances Map lesen (bef\u00fcllt von StatusAPIBridge via NexEco) double directBalance = playerBalances.getOrDefault(ecoUuid, 0.0); Map payload = new LinkedHashMap<>(); payload.put("success", true); @@ -630,11 +634,11 @@ public class StatusAPI extends Plugin implements Runnable { } // POST /economy/update - // Empfängt Balance-Updates von StatusAPIBridge (Vault/NexEco → HTTP) - // Schreibt NUR in playerBalances für Tablist/Scoreboard – KEINE DB-Schreiboperationen + // Empf\u00e4ngt Balance-Updates von StatusAPIBridge (Vault/NexEco → HTTP) + // Schreibt NUR in playerBalances f\u00fcr Tablist/Scoreboard – KEINE DB-Schreiboperationen if ("POST".equalsIgnoreCase(method) && "/economy/update".equalsIgnoreCase(pathOnly)) { String body = readBody(in, headers); - // UUID auflösen + // UUID aufl\u00f6sen UUID ecoUpdUuid = null; String uuidBody = extractJsonString(body, "uuid"); if (uuidBody != null && !uuidBody.isEmpty()) { @@ -644,8 +648,8 @@ public class StatusAPI extends Plugin implements Runnable { sendHttpResponse(out, "{\"success\":false,\"error\":\"player_not_found\"}", 404); return; } - // Balance NUR in playerBalances Map speichern (für Tablist/Scoreboard) - // Die echte DB-Verwaltung macht ausschließlich NexEco + // Balance NUR in playerBalances Map speichern (f\u00fcr Tablist/Scoreboard) + // Die echte DB-Verwaltung macht ausschlie\u00dflich NexEco String balStr = extractJsonString(body, "balance"); if (balStr != null && !balStr.isEmpty()) { try { @@ -733,7 +737,7 @@ public class StatusAPI extends Plugin implements Runnable { // Playtime auch updaten String playtimeStr = extractJsonString(body, "playtime"); synchronized (psUpd) { - // HÖCHSTER WERT gewinnt – mehrere Unterserver können unterschiedliche Werte haben + // H\u00d6CHSTER WERT gewinnt – mehrere Unterserver k\u00f6nnen unterschiedliche Werte haben try { if (killsStr != null && !killsStr.isEmpty()) { int v = Integer.parseInt(killsStr.trim()); if (v > psUpd.kills) psUpd.kills = v; @@ -775,6 +779,13 @@ public class StatusAPI extends Plugin implements Runnable { if (uuidStr != null && !uuidStr.isEmpty() && compassStr != null && !compassStr.isEmpty()) { try { UUID cUuid = UUID.fromString(uuidStr.trim()); + // Yaw-\u00c4nderung = Mausbewegung → AFK aufheben + String oldCompass = net.viper.status.modules.scoreboard.ScoreboardModule.playerCompass.get(cUuid); + if (oldCompass != null && !oldCompass.equals(compassStr.trim()) + && Boolean.TRUE.equals(playerAfk.get(cUuid))) { + net.viper.status.modules.afk.AfkModule.unAfkByMovement(cUuid); + } + net.viper.status.modules.afk.AfkModule.recordActivity(cUuid); net.viper.status.modules.scoreboard.ScoreboardModule.playerCompass.put(cUuid, compassStr.trim()); } catch (Exception ignored) {} } @@ -812,6 +823,22 @@ public class StatusAPI extends Plugin implements Runnable { return; } + // POST /player/afk – AFK-Status eines Spielers setzen (von StatusAPIBridge) + if ("POST".equalsIgnoreCase(method) && "/player/afk".equalsIgnoreCase(pathOnly)) { + String body = readBody(in, headers); + String uuidStr = extractJsonString(body, "uuid"); + String afkStr = extractJsonString(body, "afk"); + if (uuidStr != null && !uuidStr.isEmpty() && afkStr != null) { + try { + UUID uid = UUID.fromString(uuidStr.trim()); + boolean isAfk = Boolean.parseBoolean(afkStr.trim()); + net.viper.status.modules.afk.AfkModule.setBridgeAfk(uid, isAfk); + } catch (IllegalArgumentException ignored) {} + } + sendHttpResponse(out, "{\"success\":true}", 200); + return; + } + // POST /ticket/update – TicketSystem Daten (von StatusAPIBridge) if ("POST".equalsIgnoreCase(method) && "/ticket/update".equalsIgnoreCase(pathOnly)) { String body = readBody(in, headers); @@ -853,6 +880,23 @@ public class StatusAPI extends Plugin implements Runnable { String fdS = extractJsonString(body, "food"); String spS = extractJsonString(body, "speed"); String wld = extractJsonString(body, "world"); + // Bewegungserkennung: Koordinaten mit letzten Werten vergleichen + if (xS != null && yS != null && zS != null) { + int newX = (int) Double.parseDouble(xS); + int newY = (int) Double.parseDouble(yS); + int newZ = (int) Double.parseDouble(zS); + Integer oldX = net.viper.status.modules.scoreboard.ScoreboardModule.playerX.get(uid); + Integer oldY = net.viper.status.modules.scoreboard.ScoreboardModule.playerY.get(uid); + Integer oldZ = net.viper.status.modules.scoreboard.ScoreboardModule.playerZ.get(uid); + if (oldX != null && (newX != oldX || newY != oldY || newZ != oldZ)) { + net.viper.status.modules.afk.AfkModule.recordActivity(uid); + // AFK aufheben wenn Spieler sich bewegt hat + if (Boolean.TRUE.equals(playerAfk.get(uid))) { + ProxiedPlayer mover = ProxyServer.getInstance().getPlayer(uid); + if (mover != null) net.viper.status.modules.afk.AfkModule.unAfkByMovement(uid); + } + } + } if (xS != null) net.viper.status.modules.scoreboard.ScoreboardModule.playerX.put(uid, (int)Double.parseDouble(xS)); if (yS != null) net.viper.status.modules.scoreboard.ScoreboardModule.playerY.put(uid, (int)Double.parseDouble(yS)); if (zS != null) net.viper.status.modules.scoreboard.ScoreboardModule.playerZ.put(uid, (int)Double.parseDouble(zS)); @@ -873,7 +917,7 @@ public class StatusAPI extends Plugin implements Runnable { return; } - // POST /player/papi – empfängt von StatusAPIBridge aufgelöste PAPI-Werte + // POST /player/papi – empf\u00e4ngt von StatusAPIBridge aufgel\u00f6ste PAPI-Werte if ("POST".equalsIgnoreCase(method) && "/player/papi".equalsIgnoreCase(pathOnly)) { String body = readBody(in, headers); String uuidStr = extractJsonString(body, "uuid"); @@ -1011,7 +1055,7 @@ public class StatusAPI extends Plugin implements Runnable { playerInfo.put("deaths", ps.deaths); playerInfo.put("first_seen", ps.firstSeen); playerInfo.put("last_seen", ps.lastSeen); - // Balance direkt aus MySQL (serverübergreifend) + // Balance direkt aus MySQL (server\u00fcbergreifend) double statusBalance = playerBalances.getOrDefault(p.getUniqueId(), 0.0); Map eco = new LinkedHashMap<>(); eco.put("balance", statusBalance); @@ -1298,7 +1342,7 @@ public class StatusAPI extends Plugin implements Runnable { // ── PAPI-Token-Erkennung ────────────────────────────────────────────────── - /** Alle Tokens die StatusAPI selbst auflöst – werden nicht an PAPI weitergegeben */ + /** Alle Tokens die StatusAPI selbst aufl\u00f6st – werden nicht an PAPI weitergegeben */ private static final Set NATIVE_TOKENS = new HashSet<>(Arrays.asList( "player", "rank", "money", "server", "compass", "health", "hearts", "ping", "online", "maxplayers", "tps", "ram", "time", "playtime", "x", "y", "z", @@ -1310,8 +1354,8 @@ public class StatusAPI extends Plugin implements Runnable { /** * Scannt alle .properties-Dateien im Plugin-Ordner nach %token%-Mustern, - * filtert nativ unterstützte Tokens heraus und veröffentlicht den Rest - * als JSON-Array unter GET /papi/tokens für StatusAPIBridge. + * filtert nativ unterst\u00fctzte Tokens heraus und ver\u00f6ffentlicht den Rest + * als JSON-Array unter GET /papi/tokens f\u00fcr StatusAPIBridge. */ public void scanAndPublishPapiTokens() { Set tokens = new LinkedHashSet<>(); @@ -1378,8 +1422,8 @@ public class StatusAPI extends Plugin implements Runnable { // ── Reload ──────────────────────────────────────────────────────────────── /** - * Lädt Scoreboard und Tablist neu (Config + Tasks), ohne den HTTP-Server zu berühren. - * Alle anderen Module (Chat, AntiBot, etc.) bleiben unberührt. + * L\u00e4dt Scoreboard und Tablist neu (Config + Tasks), ohne den HTTP-Server zu ber\u00fchren. + * Alle anderen Module (Chat, AntiBot, etc.) bleiben unber\u00fchrt. */ public void reloadModules() { getLogger().info("[StatusAPI] Reload von Scoreboard und Tablist..."); @@ -1432,7 +1476,7 @@ public class StatusAPI extends Plugin implements Runnable { if (isAdmin) { send(sender, "&e/statusapi reload &7– Scoreboard & Tablist neu laden"); } else { - send(sender, "&7Keine weiteren Unterbefehle verfügbar."); + send(sender, "&7Keine weiteren Unterbefehle verf\u00fcgbar."); } send(sender, "&8&m──────────────────────────────────────────"); return; diff --git a/StatusAPI/src/main/java/net/viper/status/UpdateChecker.java b/StatusAPI/src/main/java/net/viper/status/UpdateChecker.java index 9827dbc..00c6c6f 100644 --- a/StatusAPI/src/main/java/net/viper/status/UpdateChecker.java +++ b/StatusAPI/src/main/java/net/viper/status/UpdateChecker.java @@ -16,7 +16,7 @@ public class UpdateChecker { private final String currentVersion; private final int intervalHours; - // Neue Domain und korrekter API-Pfad für Releases + // Neue Domain und korrekter API-Pfad f\u00fcr Releases private final String apiUrl = "https://git.viper.ipv64.net/api/v1/repos/M_Viper/StatusAPI/releases"; private volatile String latestVersion = ""; @@ -55,7 +55,7 @@ public class UpdateChecker { String body = sb.toString(); - // Neu: Da die API ein JSON-Array von Releases zurückgibt, nehmen wir das erste (neueste) Release + // Neu: Da die API ein JSON-Array von Releases zur\u00fcckgibt, nehmen wir das erste (neueste) Release // Wir suchen den ersten Block mit tag_name String foundVersion = null; Matcher tagM = TAG_NAME_PATTERN.matcher(body); diff --git a/StatusAPI/src/main/java/net/viper/status/module/Module.java b/StatusAPI/src/main/java/net/viper/status/module/Module.java index 371b1c7..108b0d9 100644 --- a/StatusAPI/src/main/java/net/viper/status/module/Module.java +++ b/StatusAPI/src/main/java/net/viper/status/module/Module.java @@ -3,7 +3,7 @@ package net.viper.status.module; import net.md_5.bungee.api.plugin.Plugin; /** - * Interface für alle zukünftigen Erweiterungen. + * Interface f\u00fcr alle zuk\u00fcnftigen Erweiterungen. */ public interface Module { diff --git a/StatusAPI/src/main/java/net/viper/status/module/ModuleManager.java b/StatusAPI/src/main/java/net/viper/status/module/ModuleManager.java index 6d62d7a..45f6dcb 100644 --- a/StatusAPI/src/main/java/net/viper/status/module/ModuleManager.java +++ b/StatusAPI/src/main/java/net/viper/status/module/ModuleManager.java @@ -8,7 +8,7 @@ import java.util.Map; /** * Verwaltet alle geladenen Module. * Verwendet LinkedHashMap um die Registrierungsreihenfolge zu erhalten, - * damit Abhängigkeiten (z.B. VanishModule → ChatModule) korrekt aufgelöst werden. + * damit Abh\u00e4ngigkeiten (z.B. VanishModule → ChatModule) korrekt aufgel\u00f6st werden. */ public class ModuleManager { @@ -41,14 +41,14 @@ public class ModuleManager { } /** - * Ermöglicht anderen Komponenten (wie dem WebServer) Zugriff auf spezifische Module. + * Erm\u00f6glicht anderen Komponenten (wie dem WebServer) Zugriff auf spezifische Module. */ public Module getModule(String name) { return modules.get(name.toLowerCase()); } /** - * Ersetzt ein bestehendes Modul durch eine neue Instanz (für Reload). + * Ersetzt ein bestehendes Modul durch eine neue Instanz (f\u00fcr Reload). * Das alte Modul muss bereits deaktiviert worden sein. */ public void replaceModule(String name, Module newModule) { diff --git a/StatusAPI/src/main/java/net/viper/status/modules/AutoMessage/AutoMessageModule.java b/StatusAPI/src/main/java/net/viper/status/modules/AutoMessage/AutoMessageModule.java index 7c2a3f4..29b9636 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/AutoMessage/AutoMessageModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/AutoMessage/AutoMessageModule.java @@ -23,10 +23,10 @@ import java.util.concurrent.atomic.AtomicInteger; * * Fix #5: * - Nachrichten werden bei jedem Zyklus frisch aus der Datei gelesen, - * damit Änderungen an messages.txt sofort wirken ohne Neustart. + * damit \u00c4nderungen an messages.txt sofort wirken ohne Neustart. * - Neuer Befehl /automessage reload (Permission: statusapi.automessage) - * lädt die Konfiguration neu und setzt den Zähler zurück. - * - TextComponent.fromLegacy() → ChatColor.translateAlternateColorCodes für §-Codes. + * l\u00e4dt die Konfiguration neu und setzt den Z\u00e4hler zur\u00fcck. + * - TextComponent.fromLegacy() → ChatColor.translateAlternateColorCodes f\u00fcr §-Codes. */ public class AutoMessageModule implements Module { @@ -34,7 +34,7 @@ public class AutoMessageModule implements Module { private StatusAPI api; private final AtomicInteger currentIndex = new AtomicInteger(0); - // Konfiguration (für Reload zugänglich) + // Konfiguration (f\u00fcr Reload zug\u00e4nglich) private volatile boolean enabled = false; private volatile int intervalSeconds = 300; private volatile String fileName = "messages.txt"; @@ -82,7 +82,7 @@ public class AutoMessageModule implements Module { try { Files.write(target.toPath(), ("# AutoMessage – eine Nachricht pro Zeile\n" + - "# Farben mit & oder §-Codes, z.B. &aGrüner Text\n" + + "# Farben mit & oder §-Codes, z.B. &aGr\u00fcner Text\n" + "# Kommentarzeilen (# ...) und Leerzeilen werden ignoriert\n").getBytes(StandardCharsets.UTF_8)); api.getLogger().info("[AutoMessage] " + fileName + " wurde als leere Vorlage erstellt."); } catch (IOException e) { @@ -95,7 +95,7 @@ public class AutoMessageModule implements Module { enabled = Boolean.parseBoolean(props.getProperty("automessage.enabled", "false")); String rawInterval = props.getProperty("automessage.interval", "300"); try { intervalSeconds = Integer.parseInt(rawInterval); } - catch (NumberFormatException e) { api.getLogger().warning("Ungültiges Intervall für AutoMessage! Nutze Standard (300s)."); intervalSeconds = 300; } + catch (NumberFormatException e) { api.getLogger().warning("Ung\u00fcltiges Intervall f\u00fcr AutoMessage! Nutze Standard (300s)."); intervalSeconds = 300; } fileName = props.getProperty("automessage.file", "messages.txt"); prefix = props.getProperty("automessage.prefix", ""); } @@ -129,7 +129,7 @@ public class AutoMessageModule implements Module { return; } - // Fix #5: Datei bei jedem Tick neu einlesen → Änderungen wirken sofort + // Fix #5: Datei bei jedem Tick neu einlesen → \u00c4nderungen wirken sofort List messages; try { messages = Files.readAllLines(messageFile.toPath(), StandardCharsets.UTF_8); @@ -146,7 +146,7 @@ public class AutoMessageModule implements Module { String raw = messages.get(idx); String prefixPart = prefix.isEmpty() ? "" : ChatColor.translateAlternateColorCodes('&', prefix) + " "; - // Fix: §-Codes direkt übersetzen (messages.txt nutzt §-Codes) + // Fix: §-Codes direkt \u00fcbersetzen (messages.txt nutzt §-Codes) String text = prefixPart + ChatColor.translateAlternateColorCodes('&', raw.replace("\u00a7", "&").replace("§", "&")); diff --git a/StatusAPI/src/main/java/net/viper/status/modules/afk/AfkModule.java b/StatusAPI/src/main/java/net/viper/status/modules/afk/AfkModule.java new file mode 100644 index 0000000..504458e --- /dev/null +++ b/StatusAPI/src/main/java/net/viper/status/modules/afk/AfkModule.java @@ -0,0 +1,621 @@ +package net.viper.status.modules.afk; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.Title; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.ChatEvent; +import net.md_5.bungee.api.event.PlayerDisconnectEvent; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.scheduler.ScheduledTask; +import net.md_5.bungee.event.EventHandler; +import net.viper.status.StatusAPI; +import net.viper.status.module.Module; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +/** + * AfkModule – /afk Befehl + automatische AFK-Erkennung nach Inaktivit\u00e4t. + * + * Unterst\u00fctzt %gradient:FARBE1:FARBE2:...:TEXT% in allen Title-Eintr\u00e4gen, + * genau wie Scoreboard und Tablist. + * + * Config: afk.properties (wird beim ersten Start automatisch erstellt) + * + * Title-Format: "Title-Zeile|Subtitle-Zeile" (Subtitle optional) + * Beispiel mit Gradient: + * afk.title.set.1=%gradient:&b:&f:&b:&l [AFK] %|&8Bewege dich um zur\u00fcckzukehren + */ +public class AfkModule implements Module, Listener { + + private static final String CONFIG_FILE = "afk.properties"; + + private Plugin plugin; + private ScheduledTask idleCheckTask; + private static AfkModule INSTANCE; + private java.lang.reflect.Method sendPkt; + + public static final ConcurrentHashMap lastActivity = new ConcurrentHashMap<>(); + + // Laufender Title-Repeat-Task pro AFK-Spieler + private final ConcurrentHashMap titleTasks = new ConcurrentHashMap<>(); + + // Welcher Title-Eintrag diesem Spieler gerade angezeigt wird (bleibt gleich solange AFK) + private final ConcurrentHashMap activeTitleEntry = new ConcurrentHashMap<>(); + + // Config + private boolean enabled = true; + private boolean idleEnabled = true; + private int idleSeconds = 300; + private String bypassPerm = "statusapi.afk.bypass"; + private int titleFadeIn = 10; + private int titleStay = 60; + private int titleFadeOut = 20; + + // Titel-Paar: set-Nachricht + passende unset-Nachricht + private static class TitlePair { + final String[] set; // { title, subtitle } + final String[] unset; // { title, subtitle } + TitlePair(String[] set, String[] unset) { this.set = set; this.unset = unset; } + } + + private final List titlePairs = new ArrayList<>(); + + // Welches Paar diesem Spieler gerade zugewiesen ist (bleibt f\u00fcr die gesamte AFK-Zeit gleich) + private final ConcurrentHashMap activePair = new ConcurrentHashMap<>(); + + private final Random random = new Random(); + + @Override public String getName() { return "AfkModule"; } + + @Override + public void onEnable(Plugin plugin) { + this.plugin = plugin; + INSTANCE = this; + try { + Class uc = Class.forName("net.md_5.bungee.UserConnection"); + sendPkt = uc.getMethod("sendPacketQueued", net.md_5.bungee.protocol.DefinedPacket.class); + sendPkt.setAccessible(true); + } catch (Exception e) { + plugin.getLogger().severe("[AfkModule] sendPacketQueued nicht gefunden: " + e.getMessage()); + } + ensureConfigExists(); + loadConfig(); + if (!enabled) { plugin.getLogger().info("[AfkModule] Deaktiviert."); return; } + + ProxyServer.getInstance().getPluginManager().registerListener(plugin, this); + ProxyServer.getInstance().getPluginManager().registerCommand(plugin, + new Command("afk") { + @Override + public void execute(CommandSender sender, String[] args) { + if (!(sender instanceof ProxiedPlayer)) { + sender.sendMessage(plain("&cNur f\u00fcr Spieler.")); + return; + } + toggleAfk((ProxiedPlayer) sender); + } + }); + + if (idleEnabled) { + idleCheckTask = ProxyServer.getInstance().getScheduler().schedule( + plugin, this::checkIdle, 10L, 10L, TimeUnit.SECONDS); + } + plugin.getLogger().info("[AfkModule] Aktiviert. idle=" + idleEnabled + + " idleSeconds=" + idleSeconds + + " pairs=" + titlePairs.size()); + } + + @Override + public void onDisable(Plugin plugin) { + if (idleCheckTask != null) { idleCheckTask.cancel(); idleCheckTask = null; } + titleTasks.values().forEach(ScheduledTask::cancel); + titleTasks.clear(); + activeTitleEntry.clear(); + activePair.clear(); + StatusAPI.playerAfk.clear(); + lastActivity.clear(); + INSTANCE = null; + } + + // ── Events ─────────────────────────────────────────────────────────────── + + @EventHandler + public void onChat(ChatEvent e) { + if (!(e.getSender() instanceof ProxiedPlayer)) return; + ProxiedPlayer p = (ProxiedPlayer) e.getSender(); + recordActivity(p.getUniqueId()); + if (!e.getMessage().toLowerCase().startsWith("/afk") && isAfk(p.getUniqueId())) + setAfk(p, false); + } + + @EventHandler + public void onDisconnect(PlayerDisconnectEvent e) { + UUID id = e.getPlayer().getUniqueId(); + StatusAPI.playerAfk.remove(id); + lastActivity.remove(id); + stopTitleTask(id); + activePair.remove(id); + } + + // ── Public API ─────────────────────────────────────────────────────────── + + public static void setBridgeAfk(UUID uuid, boolean afk) { + if (afk) { + StatusAPI.playerAfk.put(uuid, true); + } else { + StatusAPI.playerAfk.remove(uuid); + lastActivity.put(uuid, System.currentTimeMillis()); + } + } + + /** + * Wird von StatusAPI aufgerufen wenn die Bridge eine Koordinaten\u00e4nderung meldet. + * Hebt AFK sofort auf und zeigt den Unset-Title an. + */ + public static void unAfkByMovement(UUID uuid) { + AfkModule inst = INSTANCE; + if (inst == null) return; + if (!Boolean.TRUE.equals(StatusAPI.playerAfk.get(uuid))) return; + ProxiedPlayer p = ProxyServer.getInstance().getPlayer(uuid); + if (p == null) return; + inst.setAfk(p, false); + } + + public static void recordActivity(UUID uuid) { + lastActivity.put(uuid, System.currentTimeMillis()); + } + + // ── Interne Logik ──────────────────────────────────────────────────────── + + private boolean isAfk(UUID uuid) { + return Boolean.TRUE.equals(StatusAPI.playerAfk.get(uuid)); + } + + private void toggleAfk(ProxiedPlayer p) { + setAfk(p, !isAfk(p.getUniqueId())); + } + + private void setAfk(ProxiedPlayer p, boolean afk) { + UUID id = p.getUniqueId(); + if (isAfk(id) == afk) return; + if (afk) { + StatusAPI.playerAfk.put(id, true); + startTitleTask(p); + } else { + StatusAPI.playerAfk.remove(id); + lastActivity.put(id, System.currentTimeMillis()); + TitlePair pair = activePair.get(id); + stopTitleTask(id); + clearTitle(p); + if (pair != null) sendTitleEntry(p, pair.unset); + } + } + + private void checkIdle() { + long now = System.currentTimeMillis(); + long thresholdMs = idleSeconds * 1000L; + for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) { + if (!p.isConnected()) continue; + UUID id = p.getUniqueId(); + if (p.hasPermission(bypassPerm) || isAfk(id)) continue; + Long last = lastActivity.get(id); + if (last == null) { lastActivity.put(id, now); continue; } + if (now - last >= thresholdMs) setAfk(p, true); + } + } + + /** + * W\u00e4hlt einen zuf\u00e4lligen AFK-Title und wiederholt ihn dauerhaft, + * solange der Spieler AFK ist – kein Ausblenden m\u00f6glich. + */ + private void startTitleTask(ProxiedPlayer p) { + if (titlePairs.isEmpty()) return; + UUID id = p.getUniqueId(); + stopTitleTask(id); + + // Zuf\u00e4lliges Paar w\u00e4hlen – bleibt f\u00fcr die gesamte AFK-Zeit gleich + TitlePair pair = titlePairs.get(random.nextInt(titlePairs.size())); + activePair.put(id, pair); + activeTitleEntry.put(id, pair.set); + sendTitleEntry(p, pair.set); + + long intervalMs = Math.max(500, (titleStay - 20) * 50L); + ScheduledTask task = ProxyServer.getInstance().getScheduler().schedule(plugin, () -> { + ProxiedPlayer online = ProxyServer.getInstance().getPlayer(id); + if (online == null || !online.isConnected() || !isAfk(id)) { + stopTitleTask(id); + return; + } + String[] current = activeTitleEntry.get(id); + if (current != null) sendTitleEntry(online, current); + }, intervalMs, intervalMs, TimeUnit.MILLISECONDS); + titleTasks.put(id, task); + } + + private void stopTitleTask(UUID id) { + ScheduledTask old = titleTasks.remove(id); + if (old != null) old.cancel(); + activeTitleEntry.remove(id); + // activePair bleibt bis setAfk(false) es ausliest, danach: + } + + /** Entfernt den Title sofort vom Bildschirm. */ + /** Entfernt den Title sofort vom Bildschirm via ClearTitles-Packet. */ + private void clearTitle(ProxiedPlayer p) { + try { + net.md_5.bungee.protocol.packet.ClearTitles clear = new net.md_5.bungee.protocol.packet.ClearTitles(); + if (sendPkt != null) sendPkt.invoke(p, clear); + } catch (Exception ignored) {} + } + + /** + * Sendet Title + Subtitle als raw Packets (wie ScoreboardModule) – + * dadurch werden Hex-Farben korrekt \u00fcbertragen, ohne durch TextComponent.fromArray() zu laufen. + */ + private void sendTitleEntry(ProxiedPlayer p, String[] entry) { + String titleRaw = ChatColor.translateAlternateColorCodes('&', applyGradients(entry[0])); + String subtitleRaw = entry.length > 1 ? ChatColor.translateAlternateColorCodes('&', applyGradients(entry[1])) : ""; + try { + if (sendPkt == null) throw new IllegalStateException("sendPkt not initialized"); + + // Times zuerst senden + net.md_5.bungee.protocol.packet.TitleTimes times = new net.md_5.bungee.protocol.packet.TitleTimes(); + times.setFadeIn(titleFadeIn); + times.setStay(titleStay); + times.setFadeOut(titleFadeOut); + sendPkt.invoke(p, times); + + // Title-Packet (Action = TITLE, ordinal 0) + net.md_5.bungee.protocol.packet.Title titlePkt = new net.md_5.bungee.protocol.packet.Title(); + titlePkt.setAction(net.md_5.bungee.protocol.packet.Title.Action.TITLE); + titlePkt.setText(mergeComponents(buildComponents(titleRaw))); + sendPkt.invoke(p, titlePkt); + + // Subtitle-Packet + net.md_5.bungee.protocol.packet.Subtitle subPkt = new net.md_5.bungee.protocol.packet.Subtitle(); + subPkt.setText(mergeComponents(buildComponents(subtitleRaw))); + sendPkt.invoke(p, subPkt); + + } catch (Exception e) { + // Fallback auf Title-API + try { + Title title = ProxyServer.getInstance().createTitle(); + title.title(buildComponents(titleRaw)); + title.subTitle(buildComponents(subtitleRaw)); + title.fadeIn(titleFadeIn); + title.stay(titleStay); + title.fadeOut(titleFadeOut); + p.sendTitle(title); + } catch (Exception ignored) {} + } + } + + /** Fasst BaseComponent[] in eine TextComponent zusammen (f\u00fcr Title/Subtitle setText). */ + private static net.md_5.bungee.api.chat.BaseComponent mergeComponents(BaseComponent[] parts) { + TextComponent root = new TextComponent(""); + for (BaseComponent bc : parts) root.addExtra(bc); + return root; + } + + /** Fasst ein BaseComponent[]-Array in eine einzelne TextComponent zusammen. */ + private static BaseComponent wrap(BaseComponent[] parts) { + return mergeComponents(parts); + } + + // ── Gradient-Verarbeitung ───────────────────────────────────────────────── + // Identische Implementierung wie im ScoreboardModule – verarbeitet + // %gradient:FARBE1:FARBE2:...:TEXT% mit beliebig vielen Farb-Stopps. + // Farben: #RRGGBB, &#RRGGBB oder &b, &f, &c usw. + // Formatcodes (&l, &o, &n, &m) im Text bleiben erhalten. + + private String applyGradients(String input) { + if (input == null || !input.contains("%gradient:")) return input; + StringBuilder result = new StringBuilder(); + int i = 0; + while (i < input.length()) { + int start = input.indexOf("%gradient:", i); + if (start < 0) { result.append(input.substring(i)); break; } + result.append(input, i, start); + int end = input.indexOf("%", start + 10); + if (end < 0) { result.append(input.substring(start)); break; } + String inner = input.substring(start + 10, end); + List stops = new ArrayList<>(); + int colonIdx = 0; + while (colonIdx < inner.length()) { + int nextColon = inner.indexOf(':', colonIdx); + if (nextColon < 0) break; + String candidate = inner.substring(colonIdx, nextColon); + int[] rgb = parseGradientColor(candidate); + if (rgb != null) { stops.add(rgb); colonIdx = nextColon + 1; } + else break; + } + if (stops.size() < 2) { result.append(input, start, end + 1); i = end + 1; continue; } + String text = inner.substring(colonIdx); + String plain = ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', text)); + boolean bold = text.contains("&l") || text.contains("\u00A7l"); + boolean italic = text.contains("&o") || text.contains("\u00A7o"); + boolean underline = text.contains("&n") || text.contains("\u00A7n"); + boolean strike = text.contains("&m") || text.contains("\u00A7m"); + String fmt = (bold ? "\u00A7l" : "") + (italic ? "\u00A7o" : "") + + (underline ? "\u00A7n" : "") + (strike ? "\u00A7m" : ""); + int visLen = 0; + for (char ch : plain.toCharArray()) if (ch != ' ') visLen++; + if (visLen == 0) visLen = 1; + int charIdx = 0; + int[] lastRgb = stops.get(0); // Farbe f\u00fcr f\u00fchrende Leerzeichen + for (char ch : plain.toCharArray()) { + if (ch == ' ') { + // Leerzeichen mit der letzten aktiven Gradient-Farbe einf\u00e4rben + result.append('\u00A7').append('x'); + result.append('\u00A7').append(String.format("%02X", lastRgb[0]).charAt(0)); + result.append('\u00A7').append(String.format("%02X", lastRgb[0]).charAt(1)); + result.append('\u00A7').append(String.format("%02X", lastRgb[1]).charAt(0)); + result.append('\u00A7').append(String.format("%02X", lastRgb[1]).charAt(1)); + result.append('\u00A7').append(String.format("%02X", lastRgb[2]).charAt(0)); + result.append('\u00A7').append(String.format("%02X", lastRgb[2]).charAt(1)); + result.append(fmt).append(ch); + continue; + } + float pos = visLen <= 1 ? 0f : (float) charIdx / (visLen - 1); + lastRgb = interpolateGradient(stops, pos); + result.append('\u00A7').append('x'); + result.append('\u00A7').append(String.format("%02X", lastRgb[0]).charAt(0)); + result.append('\u00A7').append(String.format("%02X", lastRgb[0]).charAt(1)); + result.append('\u00A7').append(String.format("%02X", lastRgb[1]).charAt(0)); + result.append('\u00A7').append(String.format("%02X", lastRgb[1]).charAt(1)); + result.append('\u00A7').append(String.format("%02X", lastRgb[2]).charAt(0)); + result.append('\u00A7').append(String.format("%02X", lastRgb[2]).charAt(1)); + result.append(fmt).append(ch); + charIdx++; + } + i = end + 1; + } + return result.toString(); + } + + private int[] parseGradientColor(String s) { + s = s.trim(); + if (s.startsWith("&#")) s = s.substring(1); + if (s.startsWith("#") && s.length() == 7) { + try { + return new int[]{ + Integer.parseInt(s.substring(1,3),16), + Integer.parseInt(s.substring(3,5),16), + Integer.parseInt(s.substring(5,7),16) + }; + } catch (Exception ignored) {} + } + if (s.startsWith("&") && s.length() == 2) return mcColorToRgb(s.charAt(1)); + return null; + } + + private int[] interpolateGradient(List stops, float pos) { + if (stops.size() == 1) return stops.get(0); + float scaled = pos * (stops.size() - 1); + int i0 = Math.min((int) scaled, stops.size() - 2); + float t = scaled - i0; + return new int[]{ + (int)(stops.get(i0)[0] * (1-t) + stops.get(i0+1)[0] * t), + (int)(stops.get(i0)[1] * (1-t) + stops.get(i0+1)[1] * t), + (int)(stops.get(i0)[2] * (1-t) + stops.get(i0+1)[2] * t) + }; + } + + private static int[] mcColorToRgb(char code) { + switch (Character.toLowerCase(code)) { + case '0': return new int[]{ 0, 0, 0}; + case '1': return new int[]{ 0, 0, 170}; + case '2': return new int[]{ 0, 170, 0}; + case '3': return new int[]{ 0, 170, 170}; + case '4': return new int[]{170, 0, 0}; + case '5': return new int[]{170, 0, 170}; + case '6': return new int[]{255, 170, 0}; + case '7': return new int[]{170, 170, 170}; + case '8': return new int[]{ 85, 85, 85}; + case '9': return new int[]{ 85, 85, 255}; + case 'a': return new int[]{ 85, 255, 85}; + case 'b': return new int[]{ 85, 255, 255}; + case 'c': return new int[]{255, 85, 85}; + case 'd': return new int[]{255, 85, 255}; + case 'e': return new int[]{255, 255, 85}; + case 'f': return new int[]{255, 255, 255}; + default: return null; + } + } + + // ── BaseComponent-Builder ───────────────────────────────────────────────── + // Wandelt einen vorverarbeiteten String (§x§R§R§G§G§B§B + §-Codes) in + // ein BaseComponent[]-Array um – identisch zu ScoreboardModule.buildComponents(). + + private static BaseComponent[] buildComponents(String text) { + if (text == null || text.isEmpty()) + return new BaseComponent[]{ new TextComponent("") }; + + List parts = new ArrayList<>(); + net.md_5.bungee.api.ChatColor currentColor = net.md_5.bungee.api.ChatColor.WHITE; + boolean bold = false, italic = false, underline = false, strike = false, magic = false; + int i = 0; + StringBuilder buf = new StringBuilder(); + + while (i < text.length()) { + char c = text.charAt(i); + // §x§R§R§G§G§B§B – RGB Hex (14 Zeichen) + if (c == '§' && i + 13 < text.length() && text.charAt(i+1) == 'x') { + if (buf.length() > 0) { + parts.add(makeComp(buf.toString(), currentColor, bold, italic, underline, strike, magic)); + buf.setLength(0); + } + try { + String hex = "" + text.charAt(i+3) + text.charAt(i+5) + + text.charAt(i+7) + text.charAt(i+9) + + text.charAt(i+11) + text.charAt(i+13); + currentColor = net.md_5.bungee.api.ChatColor.of("#" + hex); + } catch (Exception ignored) {} + i += 14; + continue; + } + // §X – Farb-/Formatcodes + if (c == '§' && i + 1 < text.length()) { + char code = Character.toLowerCase(text.charAt(i+1)); + if (buf.length() > 0) { + parts.add(makeComp(buf.toString(), currentColor, bold, italic, underline, strike, magic)); + buf.setLength(0); + } + switch (code) { + case 'r': currentColor = net.md_5.bungee.api.ChatColor.WHITE; + bold=false; italic=false; underline=false; strike=false; magic=false; break; + case 'l': bold=true; break; + case 'o': italic=true; break; + case 'n': underline=true; break; + case 'm': strike=true; break; + case 'k': magic=true; break; + default: + net.md_5.bungee.api.ChatColor col = net.md_5.bungee.api.ChatColor.getByChar(code); + if (col != null) { currentColor=col; bold=false; italic=false; underline=false; strike=false; magic=false; } + } + i += 2; + continue; + } + buf.append(c); + i++; + } + if (buf.length() > 0) + parts.add(makeComp(buf.toString(), currentColor, bold, italic, underline, strike, magic)); + if (parts.isEmpty()) + return new BaseComponent[]{ new TextComponent("") }; + return parts.toArray(new BaseComponent[0]); + } + + private static BaseComponent makeComp(String text, + net.md_5.bungee.api.ChatColor color, + boolean bold, boolean italic, boolean underline, boolean strike, boolean magic) { + TextComponent tc = new TextComponent(text); + tc.setColor(color); + tc.setBold(bold); + tc.setItalic(italic); + tc.setUnderlined(underline); + tc.setStrikethrough(strike); + tc.setObfuscated(magic); + return tc; + } + + // ── Config ─────────────────────────────────────────────────────────────── + + private void loadConfig() { + java.io.File file = new java.io.File(plugin.getDataFolder(), CONFIG_FILE); + Map map = new LinkedHashMap<>(); + if (file.exists()) { + try (java.io.BufferedReader br = new java.io.BufferedReader( + new java.io.InputStreamReader( + new java.io.FileInputStream(file), + java.nio.charset.StandardCharsets.UTF_8))) { + String line; + while ((line = br.readLine()) != null) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("#")) continue; + int eq = line.indexOf('='); + if (eq < 1) continue; + map.put(line.substring(0, eq).trim(), line.substring(eq + 1)); + } + } catch (Exception e) { + plugin.getLogger().warning("[AfkModule] Ladefehler: " + e.getMessage()); + } + } + enabled = Boolean.parseBoolean(map.getOrDefault("afk.enabled", "true")); + idleEnabled = Boolean.parseBoolean(map.getOrDefault("afk.idle_enabled", "true")); + idleSeconds = parseInt(map.getOrDefault("afk.idle_seconds", "300"), 300); + bypassPerm = map.getOrDefault("afk.permission.bypass", "statusapi.afk.bypass"); + titleFadeIn = parseInt(map.getOrDefault("afk.title.fade_in", "10"), 10); + titleStay = parseInt(map.getOrDefault("afk.title.stay", "100"), 100); + titleFadeOut = parseInt(map.getOrDefault("afk.title.fade_out", "10"), 10); + + titlePairs.clear(); + for (int i = 1; i <= 20; i++) { + String raw = map.get("afk.title.pair." + i); + if (raw == null || raw.isEmpty()) continue; + // Format: setTitle|setSubtitle||unsetTitle|unsetSubtitle + int sep = raw.indexOf("||"); + if (sep < 0) continue; + String[] set = splitTitleLine(raw.substring(0, sep)); + String[] unset = splitTitleLine(raw.substring(sep + 2)); + titlePairs.add(new TitlePair(set, unset)); + } + if (titlePairs.isEmpty()) { + titlePairs.add(new TitlePair( + new String[]{"%gradient:&b:&f:&b:&l [AFK] %", "&8Bewege dich um zur\u00fcckzukehren"}, + new String[]{"&aWillkommen zur\u00fcck!", ""} + )); + } + } + + private String[] splitTitleLine(String raw) { + int pipe = raw.indexOf('|'); + if (pipe < 0) return new String[]{ raw, "" }; + return new String[]{ raw.substring(0, pipe), raw.substring(pipe + 1) }; + } + + private void ensureConfigExists() { + java.io.File file = new java.io.File(plugin.getDataFolder(), CONFIG_FILE); + if (file.exists()) return; + try (java.io.OutputStreamWriter w = new java.io.OutputStreamWriter( + new java.io.FileOutputStream(file), java.nio.charset.StandardCharsets.UTF_8)) { + w.write( + "# =====================================================\n" + + "# AfkModule – /afk Befehl & automatische AFK-Erkennung\n" + + "# =====================================================\n\n" + + "afk.enabled=true\n\n" + + "# Automatisch AFK setzen nach X Sekunden ohne Aktivitaet\n" + + "afk.idle_enabled=true\n" + + "afk.idle_seconds=300\n\n" + + "# Berechtigung zum Umgehen des auto-AFK\n" + + "afk.permission.bypass=statusapi.afk.bypass\n\n" + + "# ── Title-Anzeigezeiten (in Ticks, 20 Ticks = 1 Sekunde) ──\n" + + "afk.title.fade_in=10\n" + + "afk.title.stay=100\n" + + "afk.title.fade_out=10\n\n" + + "# ── Nachrichten-Paare ─────────────────────────────────────\n" + + "# Format: setTitle|setSubtitle||unsetTitle|unsetSubtitle\n" + + "# | trennt Title und Subtitle innerhalb einer Seite\n" + + "# || trennt AFK-Nachricht (links) von R\u00fcckkehr-Nachricht (rechts)\n" + + "# Gradient: %gradient:FARBE1:FARBE2:...:TEXT%\n" + + "# Farben: &b, &f, &a usw. oder #RRGGBB\n" + + "# Es wird zufaellig ein Paar gewaehlt (max. 20 Paare).\n" + + "# Die R\u00fcckkehr-Nachricht passt thematisch zur AFK-Nachricht.\n\n" + + "afk.title.pair.1=%gradient:&b:&f:&b:&l [AFK] %|&8Wahrscheinlich auf dem Klo...||%gradient:&a:&f:&a:&l Willkommen zur\u00fcck! %|&7War das Klo sauber?\n" + + "afk.title.pair.2=%gradient:&b:&f:&b:&l [AFK] %|&8Hat den Stecker gezogen||%gradient:&a:&f:&a:&l Wieder eingesteckt! %|&7Der Strom ist zur\u00fcck\n" + + "afk.title.pair.3=%gradient:&b:&f:&b:&l [AFK] %|&8Schaut seit 10min die Decke an||%gradient:&a:&f:&a:&l Na endlich! %|&7Die Decke hat dich freigegeben\n" + + "afk.title.pair.4=%gradient:&b:&f:&b:&l [AFK] %|&8Vom K\u00fchlschrank verschluckt worden||%gradient:&a:&f:&a:&l Er lebt! %|&7Der Snack war es wert, oder?\n" + + "afk.title.pair.5=%gradient:&b:&f:&b:&l [AFK] %|&8Loading... Spieler nicht gefunden||%gradient:&a:&f:&a:&l Error behoben! %|&7Spieler erfolgreich neugestartet\n" + + "afk.title.pair.6=%gradient:&b:&f:&b:&l [AFK] %|&8Vermutlich beim Snackholen||%gradient:&a:&f:&a:&l Snack erfolgreich geholt! %|&7Weiter gehts!\n" + + "afk.title.pair.7=%gradient:&b:&f:&b:&l [AFK] %|&8Eingeschlafen. Bitte nicht wecken.||%gradient:&a:&f:&a:&l Aufgewacht! %|&7Der Wecker hat funktioniert\n" + + "afk.title.pair.8=%gradient:&b:&f:&b:&l [AFK] %|&8Hat die Realit\u00e4t betreten||%gradient:&a:&f:&a:&l Zur\u00fcck aus der Realit\u00e4t! %|&7Und? War's schlimm?\n" + + "afk.title.pair.9=%gradient:&b:&f:&b:&l [AFK] %|&8Gehirn: AFK. K\u00f6rper: noch da.||%gradient:&a:&f:&a:&l Gehirn wieder eingeschaltet! %|&7Willkommen zur\u00fcck\n" + + "afk.title.pair.10=%gradient:&b:&f:&b:&l [AFK] %|&8Spricht mit echten Menschen. Seltsam.||%gradient:&a:&f:&a:&l Zur\u00fcck zu den Pixeln! %|&7Echte Menschen sind \u00fcbersch\u00e4tzt\n" + + "afk.title.pair.11=%gradient:&b:&f:&b:&l [AFK] %|&8Sucht den Einschalter f\u00fcrs Leben||%gradient:&a:&f:&a:&l Einschalter gefunden! %|&7Das Leben l\u00e4uft wieder\n" + + "afk.title.pair.12=%gradient:&b:&f:&b:&l [AFK] %|&8Mom hat gerufen. RIP.||%gradient:&a:&f:&a:&l Mom ist wieder weg. %|&7Puh. Knapp entkommen.\n" + + "afk.title.pair.13=%gradient:&b:&f:&b:&l [AFK] %|&8Error 404: Spieler nicht gefunden||%gradient:&a:&f:&a:&l Spieler wieder online! %|&7404 behoben\n" + + "afk.title.pair.14=%gradient:&b:&f:&b:&l [AFK] %|&8Tut so als w\u00e4re er besch\u00e4ftigt||%gradient:&a:&f:&a:&l Aufgeflogen! %|&7War eh nicht \u00fcberzeugend\n" + + "afk.title.pair.15=%gradient:&b:&f:&b:&l [AFK] %|&8Kaffeepause. Die wichtigste Pause.||%gradient:&a:&f:&a:&l Koffein erfolgreich zugef\u00fchrt! %|&7Jetzt wieder einsatzbereit\n" + ); + } catch (Exception e) { + plugin.getLogger().warning("[AfkModule] Konnte Config nicht erstellen: " + e.getMessage()); + } + } + + // ── Hilfsmethoden ──────────────────────────────────────────────────────── + + private static TextComponent plain(String text) { + return new TextComponent(ChatColor.translateAlternateColorCodes('&', text)); + } + + private static int parseInt(String s, int def) { + try { return Integer.parseInt(s.trim()); } catch (Exception e) { return def; } + } +} diff --git a/StatusAPI/src/main/java/net/viper/status/modules/antibot/AntiBotModule.java b/StatusAPI/src/main/java/net/viper/status/modules/antibot/AntiBotModule.java index 1a15bd6..0a1ce97 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/antibot/AntiBotModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/antibot/AntiBotModule.java @@ -46,7 +46,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; /** - * Eigenständiger AntiBot/Attack-Guard. + * Eigenst\u00e4ndiger AntiBot/Attack-Guard. * * Fixes: * - cleanupExpired() nutzt jetzt removeIf() statt Iteration + remove() (Bug #3) @@ -330,7 +330,7 @@ public class AntiBotModule implements Module, Listener { } /** - * Sperrt eine IP für die konfigurierte Block-Dauer (antibot.ip.block_seconds). + * Sperrt eine IP f\u00fcr die konfigurierte Block-Dauer (antibot.ip.block_seconds). * Kann von anderen Modulen aufgerufen werden (z. B. MultiAccountGuard). * @param ip Die zu sperrende IP-Adresse * @param durationSeconds Sperrdauer in Sekunden (0 = antibot-Standard verwenden) diff --git a/StatusAPI/src/main/java/net/viper/status/modules/broadcast/BroadcastModule.java b/StatusAPI/src/main/java/net/viper/status/modules/broadcast/BroadcastModule.java index 1ff0112..8c64d8a 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/broadcast/BroadcastModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/broadcast/BroadcastModule.java @@ -31,7 +31,7 @@ import java.util.concurrent.TimeUnit; * Fixes: * - loadSchedules(): ID-Split nutzt jetzt indexOf/lastIndexOf statt split("\\.") mit length==2-Check. * Damit werden auch clientScheduleIds die Punkte enthalten korrekt geladen. (Bug #2) - * - handleBroadcast(): &-Farbcodes werden jetzt auch in der Nachricht selbst übersetzt. (Bug #3) + * - handleBroadcast(): &-Farbcodes werden jetzt auch in der Nachricht selbst \u00fcbersetzt. (Bug #3) * - handleBroadcast(): Literal \n in der Nachricht wird als echter Zeilenumbruch gerendert. (Bug #4) * - handleBroadcast(): URLs (http/https) werden als anklickbare TextComponents eingebettet. (Bug #5) */ @@ -129,7 +129,7 @@ public class BroadcastModule implements Module, Listener { finalPrefix = prefixColorCode + usedPrefix + ChatColor.RESET; } - // FIX #1: &-Farbcodes auch in der Nachricht selbst übersetzen + // FIX #1: &-Farbcodes auch in der Nachricht selbst \u00fcbersetzen String translatedMessage = ChatColor.translateAlternateColorCodes('&', message); String coloredMessage = (messageColorCode.isEmpty() ? "" : messageColorCode) + translatedMessage; @@ -150,17 +150,17 @@ public class BroadcastModule implements Module, Listener { for (ProxiedPlayer p : plugin.getProxy().getPlayers()) { try { p.sendMessage(components); sent++; } catch (Throwable ignored) {} } - StatusAPI.debugLog(plugin, "[BroadcastModule] Broadcast gesendet (Empfänger=" + sent + "): " + message); + StatusAPI.debugLog(plugin, "[BroadcastModule] Broadcast gesendet (Empf\u00e4nger=" + sent + "): " + message); return true; } /** * Baut ein BaseComponent-Array aus einem formatierten String. * URLs (http/https) werden als anklickbare TextComponents eingebettet. - * Unterstützt auch echte Newlines (\n) als Zeilenumbruch. + * Unterst\u00fctzt auch echte Newlines (\n) als Zeilenumbruch. */ private BaseComponent[] buildClickableComponents(String text) { - // Regex für URLs + // Regex f\u00fcr URLs java.util.regex.Pattern urlPattern = java.util.regex.Pattern.compile( "(https?://[^\\s\\n]+)", java.util.regex.Pattern.CASE_INSENSITIVE); diff --git a/StatusAPI/src/main/java/net/viper/status/modules/chat/AccountLinkManager.java b/StatusAPI/src/main/java/net/viper/status/modules/chat/AccountLinkManager.java index 7b92fc7..86deaa8 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/chat/AccountLinkManager.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/chat/AccountLinkManager.java @@ -6,12 +6,12 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; /** - * Verwaltet die Verknüpfung von Minecraft-Accounts mit Discord/Telegram. + * Verwaltet die Verkn\u00fcpfung von Minecraft-Accounts mit Discord/Telegram. * * Ablauf: * 1. Spieler tippt /linkdiscord oder /linktelegram → Token wird generiert * 2. Spieler schickt Token an den Bot - * 3. Bot-Polling erkennt Token → Verknüpfung wird gespeichert + * 3. Bot-Polling erkennt Token → Verkn\u00fcpfung wird gespeichert * * Speicherformat (chat_links.dat): * minecraft:|name:|discord:|telegram: @@ -21,10 +21,10 @@ public class AccountLinkManager { private final File file; private final Logger logger; - // UUID → verknüpfte Accounts + // UUID → verkn\u00fcpfte Accounts private final ConcurrentHashMap links = new ConcurrentHashMap<>(); - // Ausstehende Token: token → UUID (läuft nach 10 Min ab) + // Ausstehende Token: token → UUID (l\u00e4uft nach 10 Min ab) private final ConcurrentHashMap pendingTokens = new ConcurrentHashMap<>(); public AccountLinkManager(File dataFolder, Logger logger) { @@ -37,10 +37,10 @@ public class AccountLinkManager { public static class LinkedAccount { public UUID minecraftUUID; public String minecraftName; - public String discordUserId = ""; // leer = nicht verknüpft - public String telegramUserId = ""; // leer = nicht verknüpft - public String telegramUsername = ""; // @username für Anzeige - public String discordUsername = ""; // für Anzeige + public String discordUserId = ""; // leer = nicht verkn\u00fcpft + public String telegramUserId = ""; // leer = nicht verkn\u00fcpft + public String telegramUsername = ""; // @username f\u00fcr Anzeige + public String discordUsername = ""; // f\u00fcr Anzeige } private static class PendingToken { @@ -64,8 +64,8 @@ public class AccountLinkManager { // ===== Token-Generierung ===== /** - * Generiert einen neuen Verknüpfungs-Token für einen Spieler. - * Bestehende Token für denselben Spieler+Typ werden überschrieben. + * Generiert einen neuen Verkn\u00fcpfungs-Token f\u00fcr einen Spieler. + * Bestehende Token f\u00fcr denselben Spieler+Typ werden \u00fcberschrieben. * * @param uuid UUID des Spielers * @param playerName Anzeigename @@ -73,7 +73,7 @@ public class AccountLinkManager { * @return 6-stelliger alphanumerischer Token (z.B. "A3F9K2") */ public String generateToken(UUID uuid, String playerName, String type) { - // Alte Token für diesen Spieler+Typ entfernen + // Alte Token f\u00fcr diesen Spieler+Typ entfernen pendingTokens.entrySet().removeIf(e -> e.getValue().uuid.equals(uuid) && e.getValue().type.equals(type)); @@ -97,15 +97,15 @@ public class AccountLinkManager { return sb.toString(); } - // ===== Token einlösen ===== + // ===== Token einl\u00f6sen ===== /** - * Versucht einen Token einzulösen (aufgerufen wenn Bot eine Nachricht empfängt). + * Versucht einen Token einzul\u00f6sen (aufgerufen wenn Bot eine Nachricht empf\u00e4ngt). * * @param token Der eingesendete Token * @param externalId Discord User-ID oder Telegram User-ID (als String) * @param externalName Discord-Username oder Telegram-@username - * @return LinkedAccount wenn erfolgreich, null wenn Token ungültig/abgelaufen + * @return LinkedAccount wenn erfolgreich, null wenn Token ung\u00fcltig/abgelaufen */ public LinkedAccount redeemToken(String token, String externalId, String externalName, String type) { token = token.trim().toUpperCase(); @@ -115,7 +115,7 @@ public class AccountLinkManager { pendingTokens.remove(token); return null; } - // Typ muss übereinstimmen + // Typ muss \u00fcbereinstimmen if (!pending.type.equals(type)) return null; pendingTokens.remove(token); @@ -161,19 +161,19 @@ public class AccountLinkManager { return null; } - /** Gibt den Minecraft-Namen für eine Discord-User-ID zurück, oder null. */ + /** Gibt den Minecraft-Namen f\u00fcr eine Discord-User-ID zur\u00fcck, oder null. */ public String getMinecraftNameByDiscordId(String discordUserId) { LinkedAccount a = getByDiscordId(discordUserId); return a != null ? a.minecraftName : null; } - /** Gibt den Minecraft-Namen für eine Telegram-User-ID zurück, oder null. */ + /** Gibt den Minecraft-Namen f\u00fcr eine Telegram-User-ID zur\u00fcck, oder null. */ public String getMinecraftNameByTelegramId(String telegramUserId) { LinkedAccount a = getByTelegramId(telegramUserId); return a != null ? a.minecraftName : null; } - /** Prüft ob ein Token gerade aussteht (für Tab-Complete etc.). */ + /** Pr\u00fcft ob ein Token gerade aussteht (f\u00fcr Tab-Complete etc.). */ public boolean hasPendingToken(UUID uuid, String type) { for (PendingToken t : pendingTokens.values()) { if (t.uuid.equals(uuid) && t.type.equals(type) && !t.isExpired()) return true; @@ -181,7 +181,7 @@ public class AccountLinkManager { return false; } - // ===== Verknüpfung aufheben ===== + // ===== Verkn\u00fcpfung aufheben ===== public boolean unlinkDiscord(UUID uuid) { LinkedAccount a = links.get(uuid); @@ -210,27 +210,27 @@ public class AccountLinkManager { } } - // ===== Convenience-Methoden für Bridges ===== + // ===== Convenience-Methoden f\u00fcr Bridges ===== /** - * Löst einen Telegram-Token ein. - * Wrapper für redeemToken mit type="telegram". + * L\u00f6st einen Telegram-Token ein. + * Wrapper f\u00fcr redeemToken mit type="telegram". */ public LinkedAccount redeemTelegram(String token, String telegramUserId, String telegramUsername) { return redeemToken(token, telegramUserId, telegramUsername, "telegram"); } /** - * Löst einen Discord-Token ein. - * Wrapper für redeemToken mit type="discord". + * L\u00f6st einen Discord-Token ein. + * Wrapper f\u00fcr redeemToken mit type="discord". */ public LinkedAccount redeemDiscord(String token, String discordUserId, String discordUsername) { return redeemToken(token, discordUserId, discordUsername, "discord"); } /** - * Gibt den Anzeigenamen für einen Telegram-Nutzer zurück. - * Wenn verknüpft: "MinecraftName (@telegram)", sonst: "@telegram" + * Gibt den Anzeigenamen f\u00fcr einen Telegram-Nutzer zur\u00fcck. + * Wenn verkn\u00fcpft: "MinecraftName (@telegram)", sonst: "@telegram" */ public String resolveTelegramName(String telegramUserId, String fallbackName) { String mc = getMinecraftNameByTelegramId(telegramUserId); @@ -238,8 +238,8 @@ public class AccountLinkManager { } /** - * Gibt den Anzeigenamen für einen Discord-Nutzer zurück. - * Wenn verknüpft: Minecraft-Name, sonst: Discord-Username + * Gibt den Anzeigenamen f\u00fcr einen Discord-Nutzer zur\u00fcck. + * Wenn verkn\u00fcpft: Minecraft-Name, sonst: Discord-Username */ public String resolveDiscordName(String discordUserId, String fallbackName) { String mc = getMinecraftNameByDiscordId(discordUserId); @@ -247,7 +247,7 @@ public class AccountLinkManager { } /** - * Gibt das korrekte Format-String zurück abhängig ob Account verknüpft. + * Gibt das korrekte Format-String zur\u00fcck abh\u00e4ngig ob Account verkn\u00fcpft. * linked=true → linkedFormat (mit {player}), false → unlinkedFormat (mit {user}) */ public boolean isLinkedTelegram(String telegramUserId) { diff --git a/StatusAPI/src/main/java/net/viper/status/modules/chat/BlockManager.java b/StatusAPI/src/main/java/net/viper/status/modules/chat/BlockManager.java index 5639480..706e357 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/chat/BlockManager.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/chat/BlockManager.java @@ -35,7 +35,7 @@ public class BlockManager { save(); } - /** Spieler `blocker` hebt den Block für `target` auf. */ + /** Spieler `blocker` hebt den Block f\u00fcr `target` auf. */ public void unblock(UUID blocker, UUID target) { Set set = blocked.get(blocker); if (set != null) { @@ -46,7 +46,7 @@ public class BlockManager { } /** - * Prüft ob `blocker` den Spieler `target` blockiert hat. + * Pr\u00fcft ob `blocker` den Spieler `target` blockiert hat. * Admins (isAdmin=true) sind niemals blockiert. */ public boolean isBlocked(UUID blocker, UUID target) { @@ -55,8 +55,8 @@ public class BlockManager { } /** - * Prüft ob eine Nachricht von `sender` an `receiver` zugestellt werden soll. - * Gibt false zurück, wenn einer der beiden den anderen blockiert. + * Pr\u00fcft ob eine Nachricht von `sender` an `receiver` zugestellt werden soll. + * Gibt false zur\u00fcck, wenn einer der beiden den anderen blockiert. */ public boolean canReceive(UUID sender, UUID receiver) { // receiver hat sender blockiert → keine Nachricht @@ -66,7 +66,7 @@ public class BlockManager { return true; } - /** Gibt alle UUIDs zurück, die `blocker` blockiert hat. */ + /** Gibt alle UUIDs zur\u00fcck, die `blocker` blockiert hat. */ public Set getBlockedBy(UUID blocker) { Set set = blocked.get(blocker); if (set == null) return Collections.emptySet(); diff --git a/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatChannel.java b/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatChannel.java index 0893c68..e13bd80 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatChannel.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatChannel.java @@ -1,7 +1,7 @@ package net.viper.status.modules.chat; /** - * Repräsentiert einen Chat-Kanal mit allen zugehörigen Einstellungen. + * Repr\u00e4sentiert einen Chat-Kanal mit allen zugeh\u00f6rigen Einstellungen. */ public class ChatChannel { @@ -51,12 +51,12 @@ public class ChatChannel { public int getTelegramThreadId() { return telegramThreadId; } public boolean isUseAdminBridge() { return useAdminBridge; } - /** Prüft ob der Kanal eine Permission erfordert. */ + /** Pr\u00fcft ob der Kanal eine Permission erfordert. */ public boolean hasPermission() { return permission != null && !permission.isEmpty(); } - /** Gibt das formatierte Kanalprefix zurück, z.B. §a[G] */ + /** Gibt das formatierte Kanalprefix zur\u00fcck, z.B. §a[G] */ public String getFormattedTag() { return net.md_5.bungee.api.ChatColor.translateAlternateColorCodes('&', color + "[" + symbol + "]"); diff --git a/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatConfig.java b/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatConfig.java index aec5131..0d4f6da 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatConfig.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatConfig.java @@ -10,12 +10,12 @@ import java.nio.file.Files; import java.util.*; /** - * Lädt und verwaltet die chat.yml Konfiguration. + * L\u00e4dt und verwaltet die chat.yml Konfiguration. * * Fix #8: Rate-Limit-Werte aus anti-spam werden nicht mehr durch nachfolgende - * Berechnungen überschrieben. Der rate-limit.chat-Block hat jetzt Vorrang. + * Berechnungen \u00fcberschrieben. Der rate-limit.chat-Block hat jetzt Vorrang. * Die Reihenfolge ist: erst rate-limit.chat einlesen, dann ggf. durch anti-spam - * als Fallback ergänzen, nicht umgekehrt. + * als Fallback erg\u00e4nzen, nicht umgekehrt. */ public class ChatConfig { @@ -103,13 +103,13 @@ public class ChatConfig { config = new Configuration(); } parseConfig(); - plugin.getLogger().fine("[ChatModule] " + channels.size() + " Kanäle geladen."); + plugin.getLogger().fine("[ChatModule] " + channels.size() + " Kan\u00e4le geladen."); } private void parseConfig() { defaultChannel = config.getString("default-channel", "global"); - // --- Kanäle --- + // --- Kan\u00e4le --- channels.clear(); Configuration chSection = config.getSection("channels"); if (chSection != null) { @@ -209,10 +209,10 @@ public class ChatConfig { linkingEnabled = al == null || al.getBoolean("enabled", true); linkDiscordMessage = al != null ? al.getString("discord-link-message", "&aCode: &f{token}") : "&aCode: &f{token}"; linkTelegramMessage = al != null ? al.getString("telegram-link-message", "&aCode: &f{token}") : "&aCode: &f{token}"; - linkSuccessDiscord = al != null ? al.getString("success-discord", "&aDiscord verknüpft!") : "&aDiscord verknüpft!"; - linkSuccessTelegram = al != null ? al.getString("success-telegram", "&aTelegram verknüpft!") : "&aTelegram verknüpft!"; - linkBotSuccessDiscord = al != null ? al.getString("bot-success-discord", "✅ Verknüpft: {player}") : "✅ Verknüpft: {player}"; - linkBotSuccessTelegram = al != null ? al.getString("bot-success-telegram", "✅ Verknüpft: {player}") : "✅ Verknüpft: {player}"; + linkSuccessDiscord = al != null ? al.getString("success-discord", "&aDiscord verkn\u00fcpft!") : "&aDiscord verkn\u00fcpft!"; + linkSuccessTelegram = al != null ? al.getString("success-telegram", "&aTelegram verkn\u00fcpft!") : "&aTelegram verkn\u00fcpft!"; + linkBotSuccessDiscord = al != null ? al.getString("bot-success-discord", "✅ Verkn\u00fcpft: {player}") : "✅ Verkn\u00fcpft: {player}"; + linkBotSuccessTelegram = al != null ? al.getString("bot-success-telegram", "✅ Verkn\u00fcpft: {player}") : "✅ Verkn\u00fcpft: {player}"; linkedDiscordFormat = al != null ? al.getString("linked-discord-format", "&9[&bDiscord&9] &f{player} &8(&7{user}&8)&8: &f{message}") : "&9[&bDiscord&9] &f{player} &8(&7{user}&8)&8: &f{message}"; linkedTelegramFormat = al != null ? al.getString("linked-telegram-format", "&3[&bTelegram&3] &f{player} &8(&7{user}&8)&8: &f{message}") : "&3[&bTelegram&3] &f{player} &8(&7{user}&8)&8: &f{message}"; @@ -227,7 +227,7 @@ public class ChatConfig { filterConfig.spamMaxMessages = spam.getInt("max-messages", 3); filterConfig.spamMessage = spam.getString("message", "&cNicht so schnell!"); // FIX #8: Fallback-Werte aus anti-spam werden NUR gesetzt wenn rate-limit.chat nicht - // konfiguriert ist. Wir setzen die Werte hier als Vorbelegung und überschreiben sie + // konfiguriert ist. Wir setzen die Werte hier als Vorbelegung und \u00fcberschreiben sie // unten mit dem rate-limit.chat-Block wenn vorhanden. filterConfig.globalRateLimitWindowMs = Math.max(500L, filterConfig.spamCooldownMs); filterConfig.globalRateLimitMaxActions = Math.max(1, filterConfig.spamMaxMessages); @@ -272,7 +272,7 @@ public class ChatConfig { } } - // --- Rate-Limit (FIX #8: dieser Block setzt die endgültigen Werte, hat Vorrang) --- + // --- Rate-Limit (FIX #8: dieser Block setzt die endg\u00fcltigen Werte, hat Vorrang) --- pmRateLimitEnabled = true; pmRateLimitWindowMs = 5000L; pmRateLimitMaxActions = 4; @@ -283,7 +283,7 @@ public class ChatConfig { if (rl != null) { Configuration rlChat = rl.getSection("chat"); if (rlChat != null) { - // FIX #8: rate-limit.chat überschreibt die anti-spam-Fallbacks vollständig + // FIX #8: rate-limit.chat \u00fcberschreibt die anti-spam-Fallbacks vollst\u00e4ndig filterConfig.globalRateLimitEnabled = rlChat.getBoolean("enabled", true); filterConfig.globalRateLimitWindowMs = rlChat.getLong("window-ms", filterConfig.globalRateLimitWindowMs); filterConfig.globalRateLimitMaxActions = rlChat.getInt("max-actions", filterConfig.globalRateLimitMaxActions); diff --git a/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatFilter.java b/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatFilter.java index b110f0a..e308aac 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatFilter.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatFilter.java @@ -9,11 +9,11 @@ import java.util.regex.Pattern; /** * Chat-Filter: Anti-Spam, Caps-Filter, Wort-Blacklist, Farbcode-Filter. * - * Reihenfolge der Prüfungen in processChat(): + * Reihenfolge der Pr\u00fcfungen in processChat(): * 1. Spam-Cooldown (zu schnell geschrieben?) * 2. Gleiche Nachricht wiederholt? - * 3. Zu viele Großbuchstaben? - * 4. Verbotene Wörter → ersetzen durch **** + * 3. Zu viele Gro\u00dfbuchstaben? + * 4. Verbotene W\u00f6rter → ersetzen durch **** * 5. Farbcodes (& Codes) → nur mit Permission erlaubt */ public class ChatFilter { @@ -21,10 +21,10 @@ public class ChatFilter { private final ChatFilterConfig cfg; private final GlobalRateLimitFramework rateLimiter = GlobalRateLimitFramework.getInstance(); - // UUID → letzte Nachricht (für Duplikat-Check) + // UUID → letzte Nachricht (f\u00fcr Duplikat-Check) private final Map lastMessageText = new ConcurrentHashMap<>(); - // Kompilierte Regex-Pattern für Blacklist-Wörter + // Kompilierte Regex-Pattern f\u00fcr Blacklist-W\u00f6rter private final List blacklistPatterns = new ArrayList<>(); public ChatFilter(ChatFilterConfig cfg) { @@ -46,7 +46,7 @@ public class ChatFilter { public enum FilterResult { ALLOWED, // Nachricht darf durch BLOCKED, // Nachricht blockiert (Spam/Flood) - MODIFIED // Nachricht wurde verändert (Wörter ersetzt / Caps reduziert) + MODIFIED // Nachricht wurde ver\u00e4ndert (W\u00f6rter ersetzt / Caps reduziert) } public static class FilterResponse { @@ -68,7 +68,7 @@ public class ChatFilter { * * @param uuid UUID des sendenden Spielers * @param message Originalnachricht - * @param isAdmin true → Farbcodes und Caps-Filter überspringen + * @param isAdmin true → Farbcodes und Caps-Filter \u00fcberspringen * @param hasColorPerm true → &-Farbcodes erlaubt * @param hasFormatPerm true → &l, &o etc. erlaubt * @return FilterResponse mit Ergebnis und ggf. modifizierter Nachricht @@ -160,7 +160,7 @@ public class ChatFilter { } private String applyCapsFilter(String message) { - // Zähle Großbuchstaben + // Z\u00e4hle Gro\u00dfbuchstaben int total = 0, upper = 0; for (char c : message.toCharArray()) { if (Character.isLetter(c)) { total++; if (Character.isUpperCase(c)) upper++; } @@ -192,9 +192,9 @@ public class ChatFilter { if (isFormat && hasFormatPerm) { sb.append(c); continue; } if (isHex && hasColorPerm) { sb.append(c); continue; } - // Kein Recht → & und nächstes Zeichen überspringen + // Kein Recht → & und n\u00e4chstes Zeichen \u00fcberspringen if (isColor || isFormat) { i++; continue; } - // Hex: &# + 6 Zeichen überspringen (i zeigt auf &, +1 = #, +2..+7 = RRGGBB) + // Hex: &# + 6 Zeichen \u00fcberspringen (i zeigt auf &, +1 = #, +2..+7 = RRGGBB) if (isHex && i + 7 <= message.length()) { i += 7; continue; } } sb.append(c); @@ -215,7 +215,7 @@ public class ChatFilter { Pattern.compile("(?i)(https?://|www\\.)\\S+"); /** - * Prüft ob die Nachricht Werbung enthält (IP, URL, fremde Domain). + * Pr\u00fcft ob die Nachricht Werbung enth\u00e4lt (IP, URL, fremde Domain). * Domains auf der Whitelist werden ignoriert. * * Erkennt: @@ -314,15 +314,15 @@ public class ChatFilter { // Caps public boolean capsFilterEnabled = true; - public int capsMinLength = 6; // Mindestlänge für Caps-Check - public int capsMaxPercent = 70; // Max. % Großbuchstaben + public int capsMinLength = 6; // Mindestl\u00e4nge f\u00fcr Caps-Check + public int capsMaxPercent = 70; // Max. % Gro\u00dfbuchstaben // Anti-Werbung public boolean antiAdEnabled = true; public String antiAdMessage = "&cWerbung ist in diesem Chat nicht erlaubt!"; // Domains/Substrings die NICHT geblockt werden (z.B. eigene Serveradresse) public List antiAdWhitelist = new ArrayList<>(); - // TLDs die als Werbung gewertet werden (leer = alle TLDs prüfen) + // TLDs die als Werbung gewertet werden (leer = alle TLDs pr\u00fcfen) public List antiAdBlockedTlds = new ArrayList<>(Arrays.asList( "net", "com", "de", "org", "gg", "io", "eu", "tv", "xyz", "info", "me", "cc", "co", "app", "online", "site", "fun" diff --git a/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatLogger.java b/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatLogger.java index 8d679e7..70e2e6f 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatLogger.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatLogger.java @@ -12,7 +12,7 @@ import java.util.logging.Logger; * Verzeichnis: plugins/StatusAPI/chatlogs/chatlog_YYYY-MM-DD.log * Format: [HH:mm:ss] [MSG-XXXXXX] [SERVER] [CHANNEL] Spieler: Nachricht * - * Alte Logs werden beim Start und täglich automatisch bereinigt. + * Alte Logs werden beim Start und t\u00e4glich automatisch bereinigt. * Die Aufbewahrungsdauer ist in der chat.yml konfigurierbar (7 oder 14 Tage). */ public class ChatLogger { @@ -37,7 +37,7 @@ public class ChatLogger { /** * Generiert eine eindeutige Nachrichten-ID (z.B. MSG-A3F2B1). - * Kombiniert Zeitstempel + inkrementellen Zähler für Eindeutigkeit. + * Kombiniert Zeitstempel + inkrementellen Z\u00e4hler f\u00fcr Eindeutigkeit. */ public String generateMessageId() { int seq = counter.incrementAndGet(); @@ -49,7 +49,7 @@ public class ChatLogger { // ===== Logging ===== /** - * Loggt eine Nachricht und gibt die generierte Nachrichten-ID zurück. + * Loggt eine Nachricht und gibt die generierte Nachrichten-ID zur\u00fcck. * * @param msgId Vorher generierte ID (aus generateMessageId()) * @param server Servername des Absenders @@ -80,7 +80,7 @@ public class ChatLogger { // ===== Cleanup ===== /** - * Löscht Log-Dateien, die älter als retentionDays Tage sind. + * L\u00f6scht Log-Dateien, die \u00e4lter als retentionDays Tage sind. * Wird beim Start und kann manuell aufgerufen werden. */ public void cleanup() { @@ -92,7 +92,7 @@ public class ChatLogger { for (File f : files) { if (f.lastModified() < cutoff) { if (f.delete()) { - logger.info("[ChatLogger] Altes Log gelöscht: " + f.getName()); + logger.info("[ChatLogger] Altes Log gel\u00f6scht: " + f.getName()); } } } @@ -112,11 +112,11 @@ public class ChatLogger { /** * Liest die letzten `maxLines` Zeilen aus dem heutigen Chatlog. - * Wenn ein Spielername angegeben ist, werden nur seine Zeilen zurückgegeben. + * Wenn ein Spielername angegeben ist, werden nur seine Zeilen zur\u00fcckgegeben. * - * @param playerFilter Spielername (case-insensitiv) oder null für alle - * @param maxLines Maximale Anzahl zurückgegebener Zeilen - * @return Liste der Logzeilen (älteste zuerst) + * @param playerFilter Spielername (case-insensitiv) oder null f\u00fcr alle + * @param maxLines Maximale Anzahl zur\u00fcckgegebener Zeilen + * @return Liste der Logzeilen (\u00e4lteste zuerst) */ public List readLastLines(String playerFilter, int maxLines) { String date = DATE_FMT.format(new Date()); @@ -145,7 +145,7 @@ public class ChatLogger { logger.warning("[ChatLogger] Fehler beim Lesen: " + e.getMessage()); } - // Letzte maxLines zurückgeben + // Letzte maxLines zur\u00fcckgeben if (allLines.size() <= maxLines) return allLines; return allLines.subList(allLines.size() - maxLines, allLines.size()); } diff --git a/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatModule.java b/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatModule.java index e89ab71..3c0eef0 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/chat/ChatModule.java @@ -23,25 +23,25 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Logger; /** - * ChatModule für StatusAPI (BungeeCord) + * ChatModule f\u00fcr StatusAPI (BungeeCord) * * Features: - * ✅ Mehrere Kanäle mit Permissions + * ✅ Mehrere Kan\u00e4le mit Permissions * ✅ Server-Erkennung im Chat-Format - * ✅ /helpop für Spieler - * ✅ Emoji-Unterstützung (:smile: → 😊) + * ✅ /helpop f\u00fcr Spieler + * ✅ Emoji-Unterst\u00fctzung (:smile: → 😊) * ✅ Admin-Mute / Spieler-eigener Chat-Mute * ✅ CMI & Plugin-kompatibel (kein Eingriff in SubServer-Befehle) * ✅ Privat-Nachrichten (/msg, /r) * ✅ Spieler-Blocking (/ignore, /unignore) * ✅ Discord & Telegram Integration * ✅ Admin-Bypass (kann nicht gemutet/geblockt werden) - * ✅ /broadcast für Admins + * ✅ /broadcast f\u00fcr Admins * ✅ Secure-Chat kompatibel (1.19+ & Bedrock/Geyser) * ✅ Chat-Log mit konfigurierbarer Aufbewahrung (7 / 14 Tage) * ✅ Nachrichten-IDs (klickbar → Zwischenablage) * ✅ Report-System (/report, /reports, /reportclose) - * ✅ Admin-Benachrichtigung bei Reports (sofort oder verzögert nach Login) + * ✅ Admin-Benachrichtigung bei Reports (sofort oder verz\u00f6gert nach Login) * ✅ Server-Farben pro Server (&-Codes und &#RRGGBB HEX) * ✅ Join / Leave Nachrichten (mit Vanish-Support) * ✅ BungeeCord-Vanish-Integration via VanishProvider @@ -69,7 +69,7 @@ public class ChatModule implements Module, Listener { // UUIDs die ihren eigenen Chat-Empfang deaktiviert haben private final Set selfChatMuted = Collections.newSetFromMap(new ConcurrentHashMap<>()); - // UUIDs die Mentions für sich deaktiviert haben + // UUIDs die Mentions f\u00fcr sich deaktiviert haben private final Set mentionsDisabled = Collections.newSetFromMap(new ConcurrentHashMap<>()); // HelpOp Cooldown: UUID → letzter Zeitstempel (Sekunden) @@ -78,13 +78,13 @@ public class ChatModule implements Module, Listener { // Report-Cooldown: UUID → letzter Report-Zeitstempel (Sekunden) private final Map reportCooldowns = new ConcurrentHashMap<>(); - // Letzte Chatnachricht pro Spieler (für Report-Kontext): name.toLowerCase() → message + // Letzte Chatnachricht pro Spieler (f\u00fcr Report-Kontext): name.toLowerCase() → message private final Map lastChatMessages = new ConcurrentHashMap<>(); // UUIDs die gerade auf Plugin-Chat-Eingabe warten private final Set awaitingInput = Collections.newSetFromMap(new ConcurrentHashMap<>()); - // Geyser-Präfix für Bedrock-Spieler (Standard: ".") + // Geyser-Pr\u00e4fix f\u00fcr Bedrock-Spieler (Standard: ".") private static final String GEYSER_PREFIX = "."; @Override @@ -126,7 +126,7 @@ public class ChatModule implements Module, Listener { linkManager = new AccountLinkManager(plugin.getDataFolder(), logger); linkManager.load(); - // Externe Brücken + // Externe Br\u00fccken if (config.isDiscordEnabled() || config.isReportWebhookEnabled()) { discordBridge = new DiscordBridge(plugin, config); discordBridge.setLinkManager(linkManager); @@ -144,7 +144,7 @@ public class ChatModule implements Module, Listener { ProxyServer.getInstance().getPluginManager().registerListener(plugin, this); registerCommands(); - logger.fine("[ChatModule] Aktiviert – " + config.getChannels().size() + " Kanäle geladen."); + logger.fine("[ChatModule] Aktiviert – " + config.getChannels().size() + " Kan\u00e4le geladen."); } @Override @@ -166,17 +166,17 @@ public class ChatModule implements Module, Listener { // CHAT-EVENTS (BungeeCord original, 1.20+) // // Das Bypass-Problem mit Paper: - // Wenn BungeeCord eine signierte Nachricht unverändert durchlässt - // (kein setCancelled), prüft Paper die Signatur → ungültig → Fehler. + // Wenn BungeeCord eine signierte Nachricht unver\u00e4ndert durchl\u00e4sst + // (kein setCancelled), pr\u00fcft Paper die Signatur → ung\u00fcltig → Fehler. // Wenn wir setCancelled(true) setzen und die Nachricht selbst senden, // fehlt die Signatur → Paper lehnt sie ebenfalls ab. // - // Lösung: In der paper-global.yml auf JEDEM Sub-Server: + // L\u00f6sung: In der paper-global.yml auf JEDEM Sub-Server: // messages: // reject-chat-unsigned: false // Das erlaubt unsignierte Nachrichten vom Proxy durch. - // Alternativ: In spigot.yml → settings: bungeecord: true (bereits nötig) - // kombiniert mit BungeeCord IP-Forwarding deaktiviert Paper die Prüfung. + // Alternativ: In spigot.yml → settings: bungeecord: true (bereits n\u00f6tig) + // kombiniert mit BungeeCord IP-Forwarding deaktiviert Paper die Pr\u00fcfung. // ========================================================= @EventHandler(priority = EventPriority.HIGHEST) @@ -215,7 +215,7 @@ public class ChatModule implements Module, Listener { if (channel == null) channel = config.getDefaultChannel(); if (channel.hasPermission() && !player.hasPermission(channel.getPermission())) { - player.sendMessage(color("&cDu hast keine Berechtigung für den Kanal &f" + channel.getName() + "&c.")); + player.sendMessage(color("&cDu hast keine Berechtigung f\u00fcr den Kanal &f" + channel.getName() + "&c.")); channelId = config.getDefaultChannelId(); channel = config.getDefaultChannel(); playerChannels.put(uuid, channelId); @@ -281,7 +281,7 @@ public class ChatModule implements Module, Listener { msgId = null; } - // Letzte Nachricht des Spielers speichern (für Report-Kontext) + // Letzte Nachricht des Spielers speichern (f\u00fcr Report-Kontext) lastChatMessages.put(player.getName().toLowerCase(), finalMessage); final ChatChannel finalChannel = channel; @@ -314,7 +314,7 @@ public class ChatModule implements Module, Listener { if (isMentioned) { recipient.sendMessage(color(config.getMentionsNotifyPrefix() - + "&7" + finalSenderName + " &7hat dich erwähnt!")); + + "&7" + finalSenderName + " &7hat dich erw\u00e4hnt!")); sendMentionSound(recipient, config.getMentionsSound()); } @@ -353,7 +353,7 @@ public class ChatModule implements Module, Listener { broadcastJoinLeave(player, true); } - // ── Offene Reports für Admins anzeigen ── + // ── Offene Reports f\u00fcr Admins anzeigen ── if (reportManager != null && (player.hasPermission(config.getAdminNotifyPermission()) || player.hasPermission(config.getAdminBypassPermission()))) { @@ -361,7 +361,7 @@ public class ChatModule implements Module, Listener { if (count > 0) { player.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬")); player.sendMessage(color("&c&l⚑ OFFENE REPORTS &8| &f" + count + " ausstehend")); - player.sendMessage(color("&7Tippe &f/reports &7für eine Übersicht.")); + player.sendMessage(color("&7Tippe &f/reports &7f\u00fcr eine \u00dcbersicht.")); player.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬")); } } @@ -457,7 +457,7 @@ public class ChatModule implements Module, Listener { ProxyServer.getInstance().getConsole().sendMessage(color( isVanished ? "&8" + logMsg : "&7" + logMsg)); - // Brücken (nur für nicht-vanished Spieler) + // Br\u00fccken (nur f\u00fcr nicht-vanished Spieler) if (!isVanished) { String cleanMsg = ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', normalMsg)); if (discordBridge != null && !config.getJoinLeaveDiscordWebhook().isEmpty()) { @@ -484,7 +484,7 @@ public class ChatModule implements Module, Listener { if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(color("&cNur Spieler!")); return; } ProxiedPlayer p = (ProxiedPlayer) sender; if (args.length == 0) { - p.sendMessage(color("&8▸ &eVerfügbare Kanäle:")); + p.sendMessage(color("&8▸ &eVerf\u00fcgbare Kan\u00e4le:")); for (ChatChannel ch : config.getChannels().values()) { boolean hasPerm = !ch.hasPermission() || p.hasPermission(ch.getPermission()); String active = ch.getId().equals(playerChannels.getOrDefault(p.getUniqueId(), config.getDefaultChannelId())) ? " &a✔" : ""; @@ -499,7 +499,7 @@ public class ChatModule implements Module, Listener { ChatChannel ch = config.getChannel(target); if (ch == null) { p.sendMessage(color("&cKanal &f" + args[0] + " &cnicht gefunden.")); return; } if (ch.hasPermission() && !p.hasPermission(ch.getPermission())) { - p.sendMessage(color("&cDu hast keine Berechtigung für diesen Kanal.")); return; + p.sendMessage(color("&cDu hast keine Berechtigung f\u00fcr diesen Kanal.")); return; } playerChannels.put(p.getUniqueId(), ch.getId()); p.sendMessage(color("&aKanal gewechselt: " + ch.getFormattedTag() + " &a" + ch.getName())); @@ -519,7 +519,7 @@ public class ChatModule implements Module, Listener { Long last = helpopCooldowns.get(p.getUniqueId()); if (last != null && (now - last) < config.getHelpopCooldown()) { long wait = config.getHelpopCooldown() - (now - last); - p.sendMessage(color("&cBitte warte noch &f" + wait + "s &cvor dem nächsten HelpOp.")); + p.sendMessage(color("&cBitte warte noch &f" + wait + "s &cvor dem n\u00e4chsten HelpOp.")); return; } helpopCooldowns.put(p.getUniqueId(), now); @@ -550,8 +550,8 @@ public class ChatModule implements Module, Listener { ProxyServer.getInstance().getPluginManager().registerCommand(plugin, helpop); // /msg - // Vanish: Vanished Spieler sind für normale Spieler nicht erreichbar. - // Admins können vanished Spieler per PM kontaktieren. + // Vanish: Vanished Spieler sind f\u00fcr normale Spieler nicht erreichbar. + // Admins k\u00f6nnen vanished Spieler per PM kontaktieren. Command msgCmd = new Command("msg", null, "tell", "w", "whisper") { @Override public void execute(CommandSender sender, String[] args) { @@ -562,7 +562,7 @@ public class ChatModule implements Module, Listener { ProxiedPlayer from = (ProxiedPlayer) sender; boolean fromIsAdmin = from.hasPermission(config.getAdminBypassPermission()); - // Ziel suchen – vanished Spieler sind für Nicht-Admins "nicht gefunden" + // Ziel suchen – vanished Spieler sind f\u00fcr Nicht-Admins "nicht gefunden" ProxiedPlayer to = findVisiblePlayer(args[0], fromIsAdmin); if (to == null || !to.isConnected()) { from.sendMessage(color("&cSpieler &f" + args[0] + " &cnicht gefunden.")); return; @@ -597,7 +597,7 @@ public class ChatModule implements Module, Listener { ProxiedPlayer target = ProxyServer.getInstance().getPlayer(args[0]); if (target == null) { p.sendMessage(color("&cSpieler nicht gefunden.")); return; } if (target.hasPermission(config.getAdminBypassPermission())) { - p.sendMessage(color("&cAdmins können nicht ignoriert werden.")); return; + p.sendMessage(color("&cAdmins k\u00f6nnen nicht ignoriert werden.")); return; } if (blockManager.isBlocked(p.getUniqueId(), target.getUniqueId())) { p.sendMessage(color("&cDu hast &f" + target.getName() + " &cbereits ignoriert.")); return; @@ -623,7 +623,7 @@ public class ChatModule implements Module, Listener { p.sendMessage(color("&cDu hast diesen Spieler nicht ignoriert.")); return; } blockManager.unblock(p.getUniqueId(), target.getUniqueId()); - p.sendMessage(color("&aIgnore für &f" + args[0] + " &aaufgehoben.")); + p.sendMessage(color("&aIgnore f\u00fcr &f" + args[0] + " &aaufgehoben.")); } }; ProxyServer.getInstance().getPluginManager().registerCommand(plugin, unignoreCmd); @@ -641,14 +641,14 @@ public class ChatModule implements Module, Listener { int duration = config.getDefaultMuteDuration(); if (args.length >= 2) { try { duration = Integer.parseInt(args[1]); } - catch (NumberFormatException ex) { sender.sendMessage(color("&cUngültige Dauer. Bitte Zahl eingeben.")); return; } + catch (NumberFormatException ex) { sender.sendMessage(color("&cUng\u00fcltige Dauer. Bitte Zahl eingeben.")); return; } } muteManager.mute(target.getUniqueId(), duration); String durationStr = duration <= 0 ? "permanent" : duration + " Minuten"; - target.sendMessage(color("&cDu wurdest für " + durationStr + " stummgeschaltet.")); - sender.sendMessage(color("&a" + target.getName() + " wurde für " + durationStr + " gemutet.")); + target.sendMessage(color("&cDu wurdest f\u00fcr " + durationStr + " stummgeschaltet.")); + sender.sendMessage(color("&a" + target.getName() + " wurde f\u00fcr " + durationStr + " gemutet.")); notifyAdmins("&8[&cMute&8] &f" + (sender instanceof ProxiedPlayer ? sender.getName() : "Konsole") - + " &7hat &f" + target.getName() + " &7für &f" + durationStr + " &7gemutet."); + + " &7hat &f" + target.getName() + " &7f\u00fcr &f" + durationStr + " &7gemutet."); } }; ProxyServer.getInstance().getPluginManager().registerCommand(plugin, muteCmd); @@ -746,7 +746,7 @@ public class ChatModule implements Module, Listener { }; ProxyServer.getInstance().getPluginManager().registerCommand(plugin, reloadCmd); - // /chatinfo – Admin-Info über einen Spieler + // /chatinfo – Admin-Info \u00fcber einen Spieler Command chatInfoCmd = new Command("chatinfo", "chat.admin.bypass") { @Override public void execute(CommandSender sender, String[] args) { @@ -769,9 +769,9 @@ public class ChatModule implements Module, Listener { AccountLinkManager.LinkedAccount link = linkManager.getByUUID(tUUID); String discordInfo = (link != null && !link.discordUserId.isEmpty()) - ? "&a" + link.discordUsername + " &8(" + link.discordUserId + ")" : "&7Nicht verknüpft"; + ? "&a" + link.discordUsername + " &8(" + link.discordUserId + ")" : "&7Nicht verkn\u00fcpft"; String telegramInfo = (link != null && !link.telegramUserId.isEmpty()) - ? "&a" + link.telegramUsername + " &8(" + link.telegramUserId + ")" : "&7Nicht verknüpft"; + ? "&a" + link.telegramUsername + " &8(" + link.telegramUserId + ")" : "&7Nicht verkn\u00fcpft"; String vanishStatus = VanishProvider.isVanished(target) ? "&eJa" : "&aKein"; @@ -834,7 +834,7 @@ public class ChatModule implements Module, Listener { sender.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬")); if (history.isEmpty()) { - sender.sendMessage(color("&7Keine Einträge gefunden.")); + sender.sendMessage(color("&7Keine Eintr\u00e4ge gefunden.")); } else { for (String line : history) { sender.sendMessage(color("&8" + line)); @@ -866,7 +866,7 @@ public class ChatModule implements Module, Listener { }; ProxyServer.getInstance().getPluginManager().registerCommand(plugin, mentionsCmd); - // /chatbypass – Chat-Verarbeitung für nächste Eingabe(n) überspringen + // /chatbypass – Chat-Verarbeitung f\u00fcr n\u00e4chste Eingabe(n) \u00fcberspringen Command bypassCmd = new Command("chatbypass", null, "cbp") { @Override public void execute(CommandSender sender, String[] args) { @@ -878,14 +878,14 @@ public class ChatModule implements Module, Listener { p.sendMessage(color("&aChatModule &l✔ &aaktiv.")); } else { awaitingInput.add(p.getUniqueId()); - p.sendMessage(color("&eChatModule &l⏸ &eüberbrückt. &7Nächste Nachricht geht direkt an den Server.")); + p.sendMessage(color("&eChatModule &l⏸ &e\u00fcberbr\u00fcckt. &7N\u00e4chste Nachricht geht direkt an den Server.")); p.sendMessage(color("&7Mit &f/chatbypass &7wieder einschalten.")); } } }; ProxyServer.getInstance().getPluginManager().registerCommand(plugin, bypassCmd); - // /discordlink – Discord-Account verknüpfen + // /discordlink – Discord-Account verkn\u00fcpfen Command discordLinkCmd = new Command("discordlink", null, "dlink") { @Override public void execute(CommandSender sender, String[] args) { @@ -896,16 +896,16 @@ public class ChatModule implements Module, Listener { String token = linkManager.generateToken(p.getUniqueId(), p.getName(), "discord"); p.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬")); - p.sendMessage(color("&9&lDiscord-Verknüpfung")); + p.sendMessage(color("&9&lDiscord-Verkn\u00fcpfung")); p.sendMessage(color("&7Schreibe dem Bot auf Discord:")); p.sendMessage(color("&f!link &b" + token)); - p.sendMessage(color("&7Token gültig für &f10 Minuten&7.")); + p.sendMessage(color("&7Token g\u00fcltig f\u00fcr &f10 Minuten&7.")); p.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬")); } }; ProxyServer.getInstance().getPluginManager().registerCommand(plugin, discordLinkCmd); - // /telegramlink – Telegram-Account verknüpfen + // /telegramlink – Telegram-Account verkn\u00fcpfen Command telegramLinkCmd = new Command("telegramlink", null, "tlink") { @Override public void execute(CommandSender sender, String[] args) { @@ -916,16 +916,16 @@ public class ChatModule implements Module, Listener { String token = linkManager.generateToken(p.getUniqueId(), p.getName(), "telegram"); p.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬")); - p.sendMessage(color("&3&lTelegram-Verknüpfung")); + p.sendMessage(color("&3&lTelegram-Verkn\u00fcpfung")); p.sendMessage(color("&7Schreibe dem Bot auf Telegram:")); p.sendMessage(color("&f/link &b" + token)); - p.sendMessage(color("&7Token gültig für &f10 Minuten&7.")); + p.sendMessage(color("&7Token g\u00fcltig f\u00fcr &f10 Minuten&7.")); p.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬")); } }; ProxyServer.getInstance().getPluginManager().registerCommand(plugin, telegramLinkCmd); - // /unlink – Verknüpfung aufheben + // /unlink – Verkn\u00fcpfung aufheben Command unlinkCmd = new Command("unlink") { @Override public void execute(CommandSender sender, String[] args) { @@ -940,21 +940,21 @@ public class ChatModule implements Module, Listener { switch (args[0].toLowerCase()) { case "discord": if (linkManager.unlinkDiscord(p.getUniqueId())) - p.sendMessage(color("&aDiscord-Verknüpfung aufgehoben.")); + p.sendMessage(color("&aDiscord-Verkn\u00fcpfung aufgehoben.")); else - p.sendMessage(color("&cKein Discord-Account verknüpft.")); + p.sendMessage(color("&cKein Discord-Account verkn\u00fcpft.")); break; case "telegram": if (linkManager.unlinkTelegram(p.getUniqueId())) - p.sendMessage(color("&aTelegram-Verknüpfung aufgehoben.")); + p.sendMessage(color("&aTelegram-Verkn\u00fcpfung aufgehoben.")); else - p.sendMessage(color("&cKein Telegram-Account verknüpft.")); + p.sendMessage(color("&cKein Telegram-Account verkn\u00fcpft.")); break; case "all": boolean d = linkManager.unlinkDiscord(p.getUniqueId()); boolean t = linkManager.unlinkTelegram(p.getUniqueId()); - if (d || t) p.sendMessage(color("&aAlle Verknüpfungen aufgehoben.")); - else p.sendMessage(color("&cKeine Verknüpfungen vorhanden.")); + if (d || t) p.sendMessage(color("&aAlle Verkn\u00fcpfungen aufgehoben.")); + else p.sendMessage(color("&cKeine Verkn\u00fcpfungen vorhanden.")); break; default: p.sendMessage(color("&cBenutzung: /unlink ")); @@ -974,7 +974,7 @@ public class ChatModule implements Module, Listener { String reqPerm = config.getReportPermission(); if (reqPerm != null && !reqPerm.isEmpty() && !p.hasPermission(reqPerm)) { - p.sendMessage(color("&cDu hast keine Berechtigung für /report.")); return; + p.sendMessage(color("&cDu hast keine Berechtigung f\u00fcr /report.")); return; } if (args.length < 2) { @@ -986,7 +986,7 @@ public class ChatModule implements Module, Listener { Long last = reportCooldowns.get(p.getUniqueId()); if (last != null && (now - last) < config.getReportCooldown()) { long wait = config.getReportCooldown() - (now - last); - p.sendMessage(color("&cBitte warte noch &f" + wait + "s &cvor dem nächsten Report.")); + p.sendMessage(color("&cBitte warte noch &f" + wait + "s &cvor dem n\u00e4chsten Report.")); return; } @@ -1040,7 +1040,7 @@ public class ChatModule implements Module, Listener { }; ProxyServer.getInstance().getPluginManager().registerCommand(plugin, reportCmd); - // /reports [all] – Admin-Übersicht + // /reports [all] – Admin-\u00dcbersicht Command reportsCmd = new Command("reports", config.getReportViewPermission()) { @Override public void execute(CommandSender sender, String[] args) { @@ -1054,7 +1054,7 @@ public class ChatModule implements Module, Listener { sender.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬")); sender.sendMessage(color("&c&l⚑ REPORTS &8| &f" + list.size() + (showAll ? " gesamt" : " offen") - + " &8| &7/reports all für alle")); + + " &8| &7/reports all f\u00fcr alle")); sender.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬")); if (list.isEmpty()) { @@ -1102,7 +1102,7 @@ public class ChatModule implements Module, Listener { sender.sendMessage(color("&8▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬")); if (!showAll && sender instanceof ProxiedPlayer) { - sender.sendMessage(color("&7Tipp: &f/reportclose &7zum Schließen.")); + sender.sendMessage(color("&7Tipp: &f/reportclose &7zum Schlie\u00dfen.")); } } }; @@ -1150,8 +1150,8 @@ public class ChatModule implements Module, Listener { // ========================================================= /** - * Öffentliche API für Sub-Server-Plugins oder BungeeCord-eigene Plugins. - * Setzt den Bypass-Status für einen Spieler. + * \u00d6ffentliche API f\u00fcr Sub-Server-Plugins oder BungeeCord-eigene Plugins. + * Setzt den Bypass-Status f\u00fcr einen Spieler. * * Beispiel aus einem anderen BungeeCord-Plugin: * ChatModule chatModule = (ChatModule) proxy.getPluginManager() @@ -1172,11 +1172,11 @@ public class ChatModule implements Module, Listener { // ========================================================= /** - * Sucht einen Spieler nach Name und berücksichtigt den Vanish-Status. + * Sucht einen Spieler nach Name und ber\u00fccksichtigt den Vanish-Status. * * @param name Spielername (case-insensitiv) * @param callerIsAdmin true → Vanished Spieler werden ebenfalls gefunden - * @return ProxiedPlayer oder null wenn nicht gefunden / vanished (für Nicht-Admins) + * @return ProxiedPlayer oder null wenn nicht gefunden / vanished (f\u00fcr Nicht-Admins) */ private ProxiedPlayer findVisiblePlayer(String name, boolean callerIsAdmin) { ProxiedPlayer target = ProxyServer.getInstance().getPlayer(name); @@ -1193,8 +1193,8 @@ public class ChatModule implements Module, Listener { String player, String suffix, String message) { String serverColor = config.getServerColor(server); String serverDisplay = config.getServerDisplay(server); - // Nur den Servernamen-Teil vorübersetzen damit &#RRGGBB im Display-Namen - // korrekt sitzt; der Rest wird am Ausgabepunkt via translateColors() übersetzt. + // Nur den Servernamen-Teil vor\u00fcbersetzen damit &#RRGGBB im Display-Namen + // korrekt sitzt; der Rest wird am Ausgabepunkt via translateColors() \u00fcbersetzt. String coloredServer = serverColor + serverDisplay + "&r"; return format @@ -1218,7 +1218,7 @@ public class ChatModule implements Module, Listener { } /** - * Übersetzt sowohl klassische &-Farbcodes als auch HEX-Codes im Format &#RRGGBB. + * \u00dcbersetzt sowohl klassische &-Farbcodes als auch HEX-Codes im Format &#RRGGBB. */ private String translateColors(String text) { if (text == null) return ""; @@ -1238,7 +1238,7 @@ public class ChatModule implements Module, Listener { i += 8; continue; } catch (Exception ignored) { - // Ungültige Farbe → als normalen Text behandeln + // Ung\u00fcltige Farbe → als normalen Text behandeln } } } @@ -1258,7 +1258,7 @@ public class ChatModule implements Module, Listener { } /** - * Benachrichtigt alle online Admins über einen neuen Report. + * Benachrichtigt alle online Admins \u00fcber einen neuen Report. */ private void notifyAdminsReport(String reportId, String reporter, String reported, String server, String reason, String msgContext) { @@ -1366,7 +1366,7 @@ public class ChatModule implements Module, Listener { } // ========================================================= - // EXTERNE BRÜCKEN + // EXTERNE BR\u00dcCKEN // ========================================================= private void bridgeToDiscord(ChatChannel channel, String playerName, String message, String server) { diff --git a/StatusAPI/src/main/java/net/viper/status/modules/chat/EmojiParser.java b/StatusAPI/src/main/java/net/viper/status/modules/chat/EmojiParser.java index 2dd94c3..1758063 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/chat/EmojiParser.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/chat/EmojiParser.java @@ -5,8 +5,8 @@ import java.util.Map; /** * Ersetzt Emoji-Shortcuts (:smile:, :heart:, …) durch Unicode-Zeichen. * - * Bedrock-Spieler (Geyser) unterstützen Unicode-Emojis ebenfalls, - * da sie als reguläre UTF-8 Zeichen in TextComponents übertragen werden. + * Bedrock-Spieler (Geyser) unterst\u00fctzen Unicode-Emojis ebenfalls, + * da sie als regul\u00e4re UTF-8 Zeichen in TextComponents \u00fcbertragen werden. */ public class EmojiParser { @@ -20,7 +20,7 @@ public class EmojiParser { /** * Konvertiert alle bekannten Emoji-Shortcuts in der Nachricht zu Unicode. - * Nicht erkannte Shortcuts bleiben unverändert. + * Nicht erkannte Shortcuts bleiben unver\u00e4ndert. * * @param message Die Originalnachricht des Spielers * @return Nachricht mit ersetzten Emojis @@ -36,12 +36,12 @@ public class EmojiParser { } /** - * Gibt eine lesbare Liste aller Emojis zurück (für /emoji list). + * Gibt eine lesbare Liste aller Emojis zur\u00fcck (f\u00fcr /emoji list). */ public String buildEmojiList() { if (mappings.isEmpty()) return "&cKeine Emojis konfiguriert."; StringBuilder sb = new StringBuilder(); - sb.append("&eVerfügbare Emojis:\n"); + sb.append("&eVerf\u00fcgbare Emojis:\n"); int i = 0; for (Map.Entry entry : mappings.entrySet()) { sb.append("&7").append(entry.getKey()).append(" &f→ ").append(entry.getValue()); diff --git a/StatusAPI/src/main/java/net/viper/status/modules/chat/MuteManager.java b/StatusAPI/src/main/java/net/viper/status/modules/chat/MuteManager.java index 5a0a523..bd2c56e 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/chat/MuteManager.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/chat/MuteManager.java @@ -10,7 +10,7 @@ import java.util.logging.Logger; * Verwaltet Mutes von Spielern. * Speichert: UUID → Ablaufzeitpunkt (Unix-Sekunden, 0 = permanent) * - * Admins/OPs mit dem Bypass-Permission können nicht gemutet werden. + * Admins/OPs mit dem Bypass-Permission k\u00f6nnen nicht gemutet werden. */ public class MuteManager { @@ -28,7 +28,7 @@ public class MuteManager { // ===== Mute-Logik ===== /** - * Mutet einen Spieler für durationMinutes Minuten. + * Mutet einen Spieler f\u00fcr durationMinutes Minuten. * durationMinutes = 0 → permanent */ public void mute(UUID uuid, int durationMinutes) { @@ -45,7 +45,7 @@ public class MuteManager { save(); } - /** Prüft ob ein Spieler aktuell gemutet ist. */ + /** Pr\u00fcft ob ein Spieler aktuell gemutet ist. */ public boolean isMuted(UUID uuid) { Long expiry = mutes.get(uuid); if (expiry == null) return false; @@ -60,8 +60,8 @@ public class MuteManager { } /** - * Gibt die verbleibende Zeit als lesbaren String zurück. - * Gibt "permanent" zurück bei dauerhaftem Mute. + * Gibt die verbleibende Zeit als lesbaren String zur\u00fcck. + * Gibt "permanent" zur\u00fcck bei dauerhaftem Mute. */ public String getRemainingTime(UUID uuid) { Long expiry = mutes.get(uuid); diff --git a/StatusAPI/src/main/java/net/viper/status/modules/chat/PrivateMsgManager.java b/StatusAPI/src/main/java/net/viper/status/modules/chat/PrivateMsgManager.java index 8db961d..131c07c 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/chat/PrivateMsgManager.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/chat/PrivateMsgManager.java @@ -18,7 +18,7 @@ public class PrivateMsgManager { private final BlockManager blockManager; private final GlobalRateLimitFramework rateLimiter = GlobalRateLimitFramework.getInstance(); - // UUID → letzte PM-Gesprächspartner UUID (für /r) + // UUID → letzte PM-Gespr\u00e4chspartner UUID (f\u00fcr /r) private final Map lastPartner = new ConcurrentHashMap<>(); // UUIDs die Social-Spy aktiviert haben @@ -36,7 +36,7 @@ public class PrivateMsgManager { * @param receiver Der empfangende Spieler * @param message Die Nachricht * @param config Chat-Konfiguration (Formate) - * @param bypassPermission Permission für Admin-Bypass (kann nicht geblockt werden) + * @param bypassPermission Permission f\u00fcr Admin-Bypass (kann nicht geblockt werden) * @return true wenn erfolgreich gesendet */ public boolean send(ProxiedPlayer sender, ProxiedPlayer receiver, @@ -84,7 +84,7 @@ public class PrivateMsgManager { sender.sendMessage(color(toSender)); receiver.sendMessage(color(toReceiver)); - // Letzte Partner speichern (für /r) + // Letzte Partner speichern (f\u00fcr /r) lastPartner.put(sender.getUniqueId(), receiver.getUniqueId()); lastPartner.put(receiver.getUniqueId(), sender.getUniqueId()); @@ -97,7 +97,7 @@ public class PrivateMsgManager { /** * Antwort-Funktion (/r). - * Sucht den letzten Gesprächspartner des Senders. + * Sucht den letzten Gespr\u00e4chspartner des Senders. */ public void reply(ProxiedPlayer sender, String message, ChatConfig config, String bypassPermission) { UUID partnerUuid = lastPartner.get(sender.getUniqueId()); @@ -140,10 +140,10 @@ public class PrivateMsgManager { /** * Formatiert eine PM-Nachricht. * {sender} → Name des Absenders - * {receiver} → Name des Empfängers - * {player} → Gesprächspartner aus Sicht des jeweiligen Empfängers: - * Beim Sender: der Empfänger (an wen schreibt er?) - * Beim Empfänger: der Sender (von wem kommt es?) + * {receiver} → Name des Empf\u00e4ngers + * {player} → Gespr\u00e4chspartner aus Sicht des jeweiligen Empf\u00e4ngers: + * Beim Sender: der Empf\u00e4nger (an wen schreibt er?) + * Beim Empf\u00e4nger: der Sender (von wem kommt es?) * * @param viewerIsSender true wenn der aktuelle Betrachter der Absender ist */ @@ -153,7 +153,7 @@ public class PrivateMsgManager { return template .replace("{sender}", sender) .replace("{receiver}", receiver) - .replace("{player}", partner) // Gesprächspartner aus Sicht des Betrachters + .replace("{player}", partner) // Gespr\u00e4chspartner aus Sicht des Betrachters .replace("{message}", message); } diff --git a/StatusAPI/src/main/java/net/viper/status/modules/chat/ReportManager.java b/StatusAPI/src/main/java/net/viper/status/modules/chat/ReportManager.java index accc4fa..73be0e3 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/chat/ReportManager.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/chat/ReportManager.java @@ -11,11 +11,11 @@ import java.util.logging.Logger; * Verwaltet Spieler-Reports (/report). * * Reports werden mit einer eindeutigen ID (z.B. RPT-0001) gespeichert und - * bleiben offen, bis ein Admin sie explizit mit /reportclose schließt. + * bleiben offen, bis ein Admin sie explizit mit /reportclose schlie\u00dft. * * Online-Admins werden sofort benachrichtigt. - * Offline-Admins erhalten eine verzögerte Benachrichtigung beim nächsten Login - * (gesteuert von außen via getPendingNotificationFor()). + * Offline-Admins erhalten eine verz\u00f6gerte Benachrichtigung beim n\u00e4chsten Login + * (gesteuert von au\u00dfen via getPendingNotificationFor()). * * Speicherformat (chat_reports.dat): * id|reporter|reporterUUID|reported|server|messageContext|reason|timestamp|closed|closedBy @@ -28,7 +28,7 @@ public class ReportManager { /** Alle Reports (offen und geschlossen). */ private final ConcurrentHashMap reports = new ConcurrentHashMap<>(); - /** Zähler für Report-IDs. Wird beim Laden synchronisiert. */ + /** Z\u00e4hler f\u00fcr Report-IDs. Wird beim Laden synchronisiert. */ private final AtomicInteger idCounter = new AtomicInteger(0); private static final SimpleDateFormat DATE_FMT = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); @@ -45,7 +45,7 @@ public class ReportManager { public String reason; public long timestamp; public boolean closed; - public String closedBy; // Name des schließenden Admins (oder leer) + public String closedBy; // Name des schlie\u00dfenden Admins (oder leer) public String getFormattedTime() { return DATE_FMT.format(new Date(timestamp)); @@ -68,8 +68,8 @@ public class ReportManager { * @param reporterUUID UUID des meldenden Spielers * @param reportedName Name des gemeldeten Spielers * @param server Server, auf dem sich der Reporter befand - * @param messageContext Letzte bekannte Nachricht des Gemeldeten (für Kontext) - * @param reason Freitext-Begründung + * @param messageContext Letzte bekannte Nachricht des Gemeldeten (f\u00fcr Kontext) + * @param reason Freitext-Begr\u00fcndung * @return die neue Report-ID (z.B. RPT-0001) */ public String createReport(String reporterName, UUID reporterUUID, @@ -95,10 +95,10 @@ public class ReportManager { } /** - * Schließt einen Report. + * Schlie\u00dft einen Report. * * @param id Report-ID (z.B. RPT-0001, case-insensitiv) - * @param adminName Name des Admins, der den Report schließt + * @param adminName Name des Admins, der den Report schlie\u00dft * @return true wenn erfolgreich geschlossen, false wenn nicht gefunden / bereits geschlossen */ public boolean closeReport(String id, String adminName) { @@ -110,13 +110,13 @@ public class ReportManager { return true; } - /** Gibt einen Report nach ID zurück (case-insensitiv). */ + /** Gibt einen Report nach ID zur\u00fcck (case-insensitiv). */ public ChatReport getReport(String id) { if (id == null) return null; return reports.get(id.toUpperCase()); } - /** Gibt alle offenen Reports chronologisch (älteste zuerst) zurück. */ + /** Gibt alle offenen Reports chronologisch (\u00e4lteste zuerst) zur\u00fcck. */ public List getOpenReports() { List list = new ArrayList<>(); for (ChatReport r : reports.values()) { @@ -126,7 +126,7 @@ public class ReportManager { return list; } - /** Gibt alle Reports chronologisch zurück (auch geschlossene). */ + /** Gibt alle Reports chronologisch zur\u00fcck (auch geschlossene). */ public List getAllReports() { List list = new ArrayList<>(reports.values()); list.sort(Comparator.comparingLong(r -> r.timestamp)); @@ -192,7 +192,7 @@ public class ReportManager { r.closedBy = unesc(p[9]); reports.put(r.id.toUpperCase(), r); - // Zähler auf höchste bekannte Nummer synchronisieren + // Z\u00e4hler auf h\u00f6chste bekannte Nummer synchronisieren if (r.id.toUpperCase().startsWith("RPT-")) { try { int num = Integer.parseInt(r.id.substring(4)); @@ -208,7 +208,7 @@ public class ReportManager { } - // ===== Escape-Helfer (Pipe-Zeichen und Zeilenumbrüche escapen) ===== + // ===== Escape-Helfer (Pipe-Zeichen und Zeilenumbr\u00fcche escapen) ===== private static String esc(String s) { if (s == null) return ""; diff --git a/StatusAPI/src/main/java/net/viper/status/modules/chat/VanishProvider.java b/StatusAPI/src/main/java/net/viper/status/modules/chat/VanishProvider.java index 73599ef..e386180 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/chat/VanishProvider.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/chat/VanishProvider.java @@ -11,9 +11,9 @@ import java.util.concurrent.ConcurrentHashMap; * Zentrale Schnittstelle zwischen dem VanishModule und dem ChatModule. * * Das VanishModule (oder jedes andere Modul) ruft {@link #setVanished} auf - * um Spieler als unsichtbar zu markieren. Das ChatModule prüft via + * um Spieler als unsichtbar zu markieren. Das ChatModule pr\u00fcft via * {@link #isVanished} bevor es Join-/Leave-Nachrichten sendet oder - * Privat-Nachrichten zulässt. + * Privat-Nachrichten zul\u00e4sst. * * Verwendung im VanishModule: * VanishProvider.setVanished(player.getUniqueId(), true); // beim Verschwinden @@ -64,7 +64,7 @@ public final class VanishProvider { return uuid != null && vanishedPlayers.contains(uuid); } - /** Snapshot der aktuell unsichtbaren Spieler (für Debugging / Logs). */ + /** Snapshot der aktuell unsichtbaren Spieler (f\u00fcr Debugging / Logs). */ public static Set getVanishedPlayers() { return Collections.unmodifiableSet(vanishedPlayers); } diff --git a/StatusAPI/src/main/java/net/viper/status/modules/chat/bridge/DiscordBridge.java b/StatusAPI/src/main/java/net/viper/status/modules/chat/bridge/DiscordBridge.java index 6d42903..dd77d74 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/chat/bridge/DiscordBridge.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/chat/bridge/DiscordBridge.java @@ -16,10 +16,10 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Logger; /** - * Discord-Brücke für bidirektionale Kommunikation. + * Discord-Br\u00fccke f\u00fcr bidirektionale Kommunikation. * * Fix #12: extractJsonString() behandelt Escape-Sequenzen jetzt korrekt. - * Statt Zeichenvergleich mit dem Vorgänger-Char wird ein expliziter Escape-Flag verwendet. + * Statt Zeichenvergleich mit dem Vorg\u00e4nger-Char wird ein expliziter Escape-Flag verwendet. */ public class DiscordBridge { @@ -49,7 +49,7 @@ public class DiscordBridge { running = true; int interval = Math.max(2, config.getDiscordPollInterval()); plugin.getProxy().getScheduler().schedule(plugin, this::pollAllChannels, interval, interval, TimeUnit.SECONDS); - logger.info("[ChatModule-Discord] Brücke gestartet (Poll-Intervall: " + interval + "s)."); + logger.info("[ChatModule-Discord] Br\u00fccke gestartet (Poll-Intervall: " + interval + "s)."); } public void stop() { running = false; } @@ -141,8 +141,8 @@ public class DiscordBridge { String token = msg.content.substring(6).trim().toUpperCase(); if (linkManager != null) { AccountLinkManager.LinkedAccount acc = linkManager.redeemDiscord(token, msg.authorId, msg.authorName); - if (acc != null) sendToChannel(channelId, "✅ Verknüpfung erfolgreich! Minecraft-Account: **" + acc.minecraftName + "**"); - else sendToChannel(channelId, "❌ Ungültiger oder abgelaufener Token. Bitte `/discordlink` im Spiel erneut ausführen."); + if (acc != null) sendToChannel(channelId, "✅ Verkn\u00fcpfung erfolgreich! Minecraft-Account: **" + acc.minecraftName + "**"); + else sendToChannel(channelId, "❌ Ung\u00fcltiger oder abgelaufener Token. Bitte `/discordlink` im Spiel erneut ausf\u00fchren."); } continue; } @@ -158,7 +158,7 @@ public class DiscordBridge { () -> ProxyServer.getInstance().broadcast(new TextComponent(formatted))); } } catch (Exception e) { - logger.fine("[ChatModule-Discord] Poll-Fehler für Kanal " + channelId + ": " + e.getMessage()); + logger.fine("[ChatModule-Discord] Poll-Fehler f\u00fcr Kanal " + channelId + ": " + e.getMessage()); } } @@ -264,8 +264,8 @@ public class DiscordBridge { /** * FIX #12: Escape-Sequenzen werden korrekt mit einem Escape-Flag behandelt - * statt den Vorgänger-Char zu vergleichen (der bei '\\' + '"' versagt). - * Gibt immer einen leeren String zurück wenn der Key nicht gefunden wird (nie null). + * statt den Vorg\u00e4nger-Char zu vergleichen (der bei '\\' + '"' versagt). + * Gibt immer einen leeren String zur\u00fcck wenn der Key nicht gefunden wird (nie null). */ private String extractJsonString(String json, String key) { if (json == null || key == null) return ""; @@ -279,7 +279,7 @@ public class DiscordBridge { if (valStart >= json.length()) return ""; char first = json.charAt(valStart); if (first == '"') { - // FIX: Expliziter Escape-Flag statt Vorgänger-Char-Vergleich + // FIX: Expliziter Escape-Flag statt Vorg\u00e4nger-Char-Vergleich int end = valStart + 1; boolean escaped = false; while (end < json.length()) { diff --git a/StatusAPI/src/main/java/net/viper/status/modules/chat/bridge/TelegramBridge.java b/StatusAPI/src/main/java/net/viper/status/modules/chat/bridge/TelegramBridge.java index 856ef95..21996cd 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/chat/bridge/TelegramBridge.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/chat/bridge/TelegramBridge.java @@ -17,7 +17,7 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Logger; /** - * Telegram-Brücke für bidirektionale Kommunikation. + * Telegram-Br\u00fccke f\u00fcr bidirektionale Kommunikation. * * Minecraft → Telegram: Via Bot API (sendMessage) * Telegram → Minecraft: Via Long-Polling (getUpdates) @@ -25,8 +25,8 @@ import java.util.logging.Logger; * Voraussetzungen: * - Telegram Bot via @BotFather erstellen * - Bot-Token in chat.yml eintragen - * - Bot in die gewünschten Gruppen/Kanäle einladen - * - Bot zu Admin machen (für Gruppen-Nachrichten empfangen) + * - Bot in die gew\u00fcnschten Gruppen/Kan\u00e4le einladen + * - Bot zu Admin machen (f\u00fcr Gruppen-Nachrichten empfangen) */ public class TelegramBridge { @@ -37,7 +37,7 @@ public class TelegramBridge { private final Logger logger; private AccountLinkManager linkManager; // wird nach dem Start gesetzt - // Letztes verarbeitetes Update-ID (für getUpdates Offset) + // Letztes verarbeitetes Update-ID (f\u00fcr getUpdates Offset) private final AtomicLong lastUpdateId = new AtomicLong(0L); private volatile boolean running = false; @@ -67,7 +67,7 @@ public class TelegramBridge { plugin.getProxy().getScheduler().schedule(plugin, this::pollUpdates, interval, interval, TimeUnit.SECONDS); - logger.info("[ChatModule-Telegram] Brücke gestartet (Poll-Intervall: " + interval + "s)."); + logger.info("[ChatModule-Telegram] Br\u00fccke gestartet (Poll-Intervall: " + interval + "s)."); } public void stop() { @@ -78,7 +78,7 @@ public class TelegramBridge { /** * Sendet eine Nachricht an eine Telegram-Chat-ID. - * Unterstützt Themen-Gruppen via message_thread_id. + * Unterst\u00fctzt Themen-Gruppen via message_thread_id. */ public void sendToTelegram(String chatId, String message) { sendToTelegram(chatId, 0, message); @@ -107,7 +107,7 @@ public class TelegramBridge { /** * Sendet eine formatierte HelpOp/Broadcast-Nachricht an Telegram. - * Unterstützt Themen-Gruppen via message_thread_id. + * Unterst\u00fctzt Themen-Gruppen via message_thread_id. */ public void sendFormattedToTelegram(String chatId, String header, String content) { sendFormattedToTelegram(chatId, 0, header, content); @@ -167,7 +167,7 @@ public class TelegramBridge { if (update.text == null || update.text.isEmpty()) continue; if (update.isBot) continue; - // ── Token-Einlösung: /link ── + // ── Token-Einl\u00f6sung: /link ── if (update.text.startsWith("/link ") || update.text.startsWith("/link@")) { String[] parts = update.text.split("\\s+", 2); if (parts.length == 2 && linkManager != null) { @@ -176,11 +176,11 @@ public class TelegramBridge { linkManager.redeemTelegram(token, update.fromId, update.fromName); if (acc != null) { sendToTelegram(update.chatId, update.threadId, - "✅ Verknüpfung erfolgreich! Minecraft-Account: " + "✅ Verkn\u00fcpfung erfolgreich! Minecraft-Account: " + escapeHtml(acc.minecraftName) + ""); } else { sendToTelegram(update.chatId, update.threadId, - "❌ Ungültiger oder abgelaufener Token. Bitte /telegramlink im Spiel erneut ausführen."); + "❌ Ung\u00fcltiger oder abgelaufener Token. Bitte /telegramlink im Spiel erneut ausf\u00fchren."); } } continue; // Nicht als Chat-Nachricht weiterleiten @@ -189,17 +189,17 @@ public class TelegramBridge { // Bot-Befehle ignorieren if (update.text.startsWith("/")) continue; - // ── Account-Name auflösen ── + // ── Account-Name aufl\u00f6sen ── String displayName = (linkManager != null) ? linkManager.resolveTelegramName(update.fromId, update.fromName) : update.fromName; - // Welchem Minecraft-Kanal gehört diese Telegram-Chat-ID + Thread? + // Welchem Minecraft-Kanal geh\u00f6rt diese Telegram-Chat-ID + Thread? final boolean isAdminChat = update.chatId.equals(config.getTelegramAdminChatId()) && (config.getTelegramAdminTopicId() == 0 || config.getTelegramAdminTopicId() == update.threadId); - // Prüfen ob die Nachricht zu einem konfigurierten Kanal-Thema gehört + // Pr\u00fcfen ob die Nachricht zu einem konfigurierten Kanal-Thema geh\u00f6rt final boolean matchesChannel = isAdminChat || matchesTelegramChannel(update); if (!matchesChannel && !isAdminChat) continue; @@ -257,11 +257,11 @@ public class TelegramBridge { private static class TelegramUpdate { long updateId; String chatId = ""; - String fromId = ""; // Telegram User-ID (für Account-Link) + String fromId = ""; // Telegram User-ID (f\u00fcr Account-Link) String fromName = ""; String text = ""; boolean isBot = false; - int threadId = 0; // message_thread_id für Themen-Gruppen (0 = kein Thema) + int threadId = 0; // message_thread_id f\u00fcr Themen-Gruppen (0 = kein Thema) } private java.util.List parseUpdates(String json) { @@ -289,11 +289,11 @@ public class TelegramBridge { return result; } - /** Prüft ob ein Update zu einem konfigurierten Kanal-Thema gehört. */ + /** Pr\u00fcft ob ein Update zu einem konfigurierten Kanal-Thema geh\u00f6rt. */ private boolean matchesTelegramChannel(TelegramUpdate update) { for (net.viper.status.modules.chat.ChatChannel ch : config.getChannels().values()) { if (!ch.getTelegramChatId().equals(update.chatId)) continue; - // Thema konfiguriert? → Thread-ID muss übereinstimmen + // Thema konfiguriert? → Thread-ID muss \u00fcbereinstimmen if (ch.getTelegramThreadId() > 0 && ch.getTelegramThreadId() != update.threadId) continue; return true; } diff --git a/StatusAPI/src/main/java/net/viper/status/modules/commandblocker/CommandBlockerModule.java b/StatusAPI/src/main/java/net/viper/status/modules/commandblocker/CommandBlockerModule.java index 00bf3fe..ec1976c 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/commandblocker/CommandBlockerModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/commandblocker/CommandBlockerModule.java @@ -23,7 +23,7 @@ import org.yaml.snakeyaml.Yaml; public class CommandBlockerModule implements Module, Listener { private StatusAPI plugin; - private boolean enabled = true; // Standardmäßig aktiv + private boolean enabled = true; // Standardm\u00e4\u00dfig aktiv private String bypassPermission = "commandblocker.bypass"; // Standard Permission private File file; diff --git a/StatusAPI/src/main/java/net/viper/status/modules/customcommands/CustomCommandModule.java b/StatusAPI/src/main/java/net/viper/status/modules/customcommands/CustomCommandModule.java index 083a16a..52b5bc6 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/customcommands/CustomCommandModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/customcommands/CustomCommandModule.java @@ -20,7 +20,7 @@ import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.event.ChatEvent; import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.Listener; -import net.md_5.bungee.api.plugin.Plugin; // Import für das Interface Argument +import net.md_5.bungee.api.plugin.Plugin; // Import f\u00fcr das Interface Argument import net.md_5.bungee.config.Configuration; import net.md_5.bungee.config.ConfigurationProvider; import net.md_5.bungee.config.YamlConfiguration; @@ -109,8 +109,8 @@ public class CustomCommandModule implements Module, Listener { @Override public void onDisable(Plugin plugin) { - // Optional: Cleanup logic, falls nötig. - // Wir nutzen hier das übergebene 'plugin' Argument (oder this.plugin, ist egal) + // Optional: Cleanup logic, falls n\u00f6tig. + // Wir nutzen hier das \u00fcbergebene 'plugin' Argument (oder this.plugin, ist egal) // Listener und Commands werden automatisch entfernt, wenn das Plugin stoppt. } diff --git a/StatusAPI/src/main/java/net/viper/status/modules/economy/EcoAdminCommand.java b/StatusAPI/src/main/java/net/viper/status/modules/economy/EcoAdminCommand.java index 158629f..3b77926 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/economy/EcoAdminCommand.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/economy/EcoAdminCommand.java @@ -6,7 +6,7 @@ import net.md_5.bungee.api.plugin.Plugin; /** * /ecoadmin – wird NICHT mehr auf BungeeCord registriert. - * NexEco /eco auf dem Spigot-Server übernimmt Admin-Befehle. + * NexEco /eco auf dem Spigot-Server \u00fcbernimmt Admin-Befehle. */ public class EcoAdminCommand extends Command { diff --git a/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyDatabase.java b/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyDatabase.java index 6ce2ada..be31a1a 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyDatabase.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyDatabase.java @@ -14,8 +14,8 @@ import java.util.logging.Logger; * * Fixes: * - balance-Spalte als DOUBLE(30,2) statt VARCHAR → kompatibel mit NexEco & SurvivalPlus - * - atomare Transaktion für withdraw+deposit → kein Geldverlust bei Absturz - * - FOR UPDATE Lock → kein Race-Condition-Bug bei gleichzeitigen Überweisungen + * - atomare Transaktion f\u00fcr withdraw+deposit → kein Geldverlust bei Absturz + * - FOR UPDATE Lock → kein Race-Condition-Bug bei gleichzeitigen \u00dcberweisungen */ public class EconomyDatabase { @@ -67,7 +67,7 @@ public class EconomyDatabase { "MODIFY COLUMN `balance` DOUBLE(30,2) NOT NULL DEFAULT 0.00" ); } catch (SQLException e) { - // ALTER schlägt fehl wenn Typ bereits korrekt ist – kein Problem + // ALTER schl\u00e4gt fehl wenn Typ bereits korrekt ist – kein Problem if (!e.getMessage().contains("Duplicate") && !e.getMessage().contains("doesn't exist")) { log.warning("[Economy] Tabellen-Setup bc_accounts: " + e.getMessage()); } @@ -98,7 +98,7 @@ public class EconomyDatabase { // ── Kontostand ──────────────────────────────────────────────────────────── - /** Lädt den Kontostand direkt aus der DB. Gibt -1 zurück wenn kein Eintrag. */ + /** L\u00e4dt den Kontostand direkt aus der DB. Gibt -1 zur\u00fcck wenn kein Eintrag. */ public double load(UUID uuid) { if (!isConnected()) return -1; try (Connection con = dataSource.getConnection(); @@ -109,7 +109,7 @@ public class EconomyDatabase { if (rs.next()) return rs.getDouble("balance"); } } catch (SQLException e) { - log.warning("[Economy] Load fehlgeschlagen für " + uuid + ": " + e.getMessage()); + log.warning("[Economy] Load fehlgeschlagen f\u00fcr " + uuid + ": " + e.getMessage()); } return -1; } @@ -125,14 +125,14 @@ public class EconomyDatabase { ps.setDouble(2, balance); ps.executeUpdate(); } catch (SQLException e) { - log.warning("[Economy] Save fehlgeschlagen für " + uuid + ": " + e.getMessage()); + log.warning("[Economy] Save fehlgeschlagen f\u00fcr " + uuid + ": " + e.getMessage()); } } /** - * Atomare Überweisung von → to. + * Atomare \u00dcberweisung von → to. * Nutzt eine SQL-Transaktion mit FOR UPDATE Lock – race-condition-sicher. - * Gibt false zurück wenn Sender nicht genug Guthaben hat. + * Gibt false zur\u00fcck wenn Sender nicht genug Guthaben hat. */ public boolean transfer(UUID from, UUID to, double amount, double startBalance) { if (!isConnected()) return false; @@ -162,7 +162,7 @@ public class EconomyDatabase { ps.executeUpdate(); } - // Empfänger gutschreiben (Konto anlegen falls nötig) + // Empf\u00e4nger gutschreiben (Konto anlegen falls n\u00f6tig) try (PreparedStatement ps = con.prepareStatement( "INSERT INTO `" + TABLE + "` (`player_name`, `balance`) VALUES (?, ?) " + "ON DUPLICATE KEY UPDATE `balance` = `balance` + ?")) { diff --git a/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyListener.java b/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyListener.java index 0672fd5..baf892e 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyListener.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyListener.java @@ -9,20 +9,20 @@ import net.md_5.bungee.event.EventHandler; import net.viper.status.StatusAPI; /** - * EconomyListener – nur noch Aufräumen der playerBalances Map. + * EconomyListener – nur noch Aufr\u00e4umen der playerBalances Map. * - * Das Befüllen der Map geschieht ausschließlich durch die StatusAPIBridge - * (Spigot) die über Vault/NexEco den Kontostand per HTTP an die StatusAPI sendet. + * Das Bef\u00fcllen der Map geschieht ausschlie\u00dflich durch die StatusAPIBridge + * (Spigot) die \u00fcber Vault/NexEco den Kontostand per HTTP an die StatusAPI sendet. */ public class EconomyListener implements Listener { public EconomyListener(Plugin plugin, EconomyManager manager) { - // EconomyManager wird nicht mehr benötigt + // EconomyManager wird nicht mehr ben\u00f6tigt } @EventHandler public void onLogin(PostLoginEvent event) { - // Wird von StatusAPIBridge befüllt – nichts zu tun beim Login + // Wird von StatusAPIBridge bef\u00fcllt – nichts zu tun beim Login } @EventHandler @@ -32,6 +32,6 @@ public class EconomyListener implements Listener { } public void cancelTasks() { - // Kein periodischer Task mehr nötig + // Kein periodischer Task mehr n\u00f6tig } } diff --git a/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyManager.java b/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyManager.java index bf565de..58ca3ae 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyManager.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyManager.java @@ -5,7 +5,7 @@ import java.util.UUID; /** * EconomyManager – Stub, nicht mehr aktiv. - * Economy wird ausschließlich über NexEco (Spigot) verwaltet. + * Economy wird ausschlie\u00dflich \u00fcber NexEco (Spigot) verwaltet. */ public class EconomyManager { diff --git a/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyModule.java b/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyModule.java index 0f3cf35..87a0f7b 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/economy/EconomyModule.java @@ -6,13 +6,13 @@ import net.viper.status.module.Module; /** * EconomyModule – DEAKTIVIERT. * - * Die Economy wird ausschließlich über NexEco (Spigot) verwaltet. - * Die StatusAPIBridge (Spigot-Plugin) liest den Kontostand über Vault/NexEco + * Die Economy wird ausschlie\u00dflich \u00fcber NexEco (Spigot) verwaltet. + * Die StatusAPIBridge (Spigot-Plugin) liest den Kontostand \u00fcber Vault/NexEco * und pushed ihn per HTTP an die StatusAPI → playerBalances Map. * - * Damit gibt es nur EINE Datenquelle für Kontostände: NexEco / money_accounts. - * Das alte EconomyModule schrieb in bc_accounts – das führte zu doppelten, - * inkonsistenten Kontoständen. + * Damit gibt es nur EINE Datenquelle f\u00fcr Kontost\u00e4nde: NexEco / money_accounts. + * Das alte EconomyModule schrieb in bc_accounts – das f\u00fchrte zu doppelten, + * inkonsistenten Kontost\u00e4nden. */ public class EconomyModule implements Module { @@ -21,8 +21,8 @@ public class EconomyModule implements Module { @Override public void onEnable(Plugin plugin) { - plugin.getLogger().info("[Economy] EconomyModule ist deaktiviert – NexEco ist zuständig."); - plugin.getLogger().info("[Economy] Kontostände kommen via StatusAPIBridge (Vault → NexEco → HTTP)."); + plugin.getLogger().info("[Economy] EconomyModule ist deaktiviert – NexEco ist zust\u00e4ndig."); + plugin.getLogger().info("[Economy] Kontost\u00e4nde kommen via StatusAPIBridge (Vault → NexEco → HTTP)."); } @Override diff --git a/StatusAPI/src/main/java/net/viper/status/modules/economy/PayCommand.java b/StatusAPI/src/main/java/net/viper/status/modules/economy/PayCommand.java index 9c48eb7..0ea250a 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/economy/PayCommand.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/economy/PayCommand.java @@ -6,8 +6,8 @@ import net.md_5.bungee.api.plugin.Plugin; /** * /pay – wird NICHT mehr auf BungeeCord registriert. - * NexEco auf dem Spigot-Server übernimmt /pay direkt. - * Diese Klasse existiert nur noch für Kompilier-Kompatibilität. + * NexEco auf dem Spigot-Server \u00fcbernimmt /pay direkt. + * Diese Klasse existiert nur noch f\u00fcr Kompilier-Kompatibilit\u00e4t. */ public class PayCommand extends Command { diff --git a/StatusAPI/src/main/java/net/viper/status/modules/forum/ForumBridgeModule.java b/StatusAPI/src/main/java/net/viper/status/modules/forum/ForumBridgeModule.java index 47aa8b1..24723aa 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/forum/ForumBridgeModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/forum/ForumBridgeModule.java @@ -39,8 +39,8 @@ import java.util.concurrent.TimeUnit; /** * ForumBridgeModule: Verbindet das WP Business Forum mit dem Minecraft-Server. * - * Fix #13: extractJsonString() gibt jetzt immer einen leeren String statt null zurück. - * Alle Aufrufer müssen nicht mehr auf null prüfen, was NullPointerExceptions verhindert. + * Fix #13: extractJsonString() gibt jetzt immer einen leeren String statt null zur\u00fcck. + * Alle Aufrufer m\u00fcssen nicht mehr auf null pr\u00fcfen, was NullPointerExceptions verhindert. */ public class ForumBridgeModule implements Module, Listener { @@ -106,7 +106,7 @@ public class ForumBridgeModule implements Module, Listener { return "{\"success\":false,\"error\":\"unauthorized\"}"; } - // FIX #13: extractJsonString gibt "" statt null → kein NullPointerException möglich + // FIX #13: extractJsonString gibt "" statt null → kein NullPointerException m\u00f6glich String playerUuid = extractJsonString(body, "player_uuid"); String type = extractJsonString(body, "type"); String title = extractJsonString(body, "title"); @@ -122,7 +122,7 @@ public class ForumBridgeModule implements Module, Listener { if ("thread".equalsIgnoreCase(type) && title.toLowerCase().contains("umfrage")) type = "poll"; if (type.isEmpty()) type = "reply"; - // Alle Werte sind garantiert nicht null (extractJsonString gibt "" zurück) + // Alle Werte sind garantiert nicht null (extractJsonString gibt "" zur\u00fcck) ForumNotification notification = new ForumNotification(uuid, type, title, author, url); ProxiedPlayer online = ProxyServer.getInstance().getPlayer(uuid); @@ -160,7 +160,7 @@ public class ForumBridgeModule implements Module, Listener { TextComponent link = new TextComponent("§a ➜ Im Forum ansehen"); link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, notif.getUrl())); link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, - new ComponentBuilder("§7Klicke um den Beitrag im Forum zu öffnen").create())); + new ComponentBuilder("§7Klicke um den Beitrag im Forum zu \u00f6ffnen").create())); player.sendMessage(link); } player.sendMessage(new TextComponent("§8§m ")); @@ -197,16 +197,16 @@ public class ForumBridgeModule implements Module, Listener { @Override public void execute(CommandSender sender, String[] args) { - if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(new TextComponent("§cNur Spieler können diesen Befehl benutzen.")); return; } + if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(new TextComponent("§cNur Spieler k\u00f6nnen diesen Befehl benutzen.")); return; } ProxiedPlayer p = (ProxiedPlayer) sender; if (args.length != 1) { p.sendMessage(new TextComponent("§eBenutzung: §f/forumlink ")); - p.sendMessage(new TextComponent("§7Den Token erhältst du in deinem Forum-Profil unter §fMinecraft-Verknüpfung§7.")); + p.sendMessage(new TextComponent("§7Den Token erh\u00e4ltst du in deinem Forum-Profil unter §fMinecraft-Verkn\u00fcpfung§7.")); return; } String token = args[0].trim().toUpperCase(); - if (wpBaseUrl.isEmpty()) { p.sendMessage(new TextComponent("§cForum-Verknüpfung ist nicht konfiguriert.")); return; } - p.sendMessage(new TextComponent("§7Überprüfe Token...")); + if (wpBaseUrl.isEmpty()) { p.sendMessage(new TextComponent("§cForum-Verkn\u00fcpfung ist nicht konfiguriert.")); return; } + p.sendMessage(new TextComponent("§7\u00dcberpr\u00fcfe Token...")); plugin.getProxy().getScheduler().runAsync(plugin, () -> { try { @@ -236,17 +236,17 @@ public class ForumBridgeModule implements Module, Listener { String username = extractJsonString(resp, "username"); String show = !displayName.isEmpty() ? displayName : username; p.sendMessage(new TextComponent("§8§m ")); - p.sendMessage(new TextComponent("§a§l✓ §fForum-Account erfolgreich verknüpft!")); + p.sendMessage(new TextComponent("§a§l✓ §fForum-Account erfolgreich verkn\u00fcpft!")); if (!show.isEmpty()) p.sendMessage(new TextComponent("§7 Forum-User: §f" + show)); - p.sendMessage(new TextComponent("§7 Du erhältst jetzt Ingame-Benachrichtigungen.")); + p.sendMessage(new TextComponent("§7 Du erh\u00e4ltst jetzt Ingame-Benachrichtigungen.")); p.sendMessage(new TextComponent("§8§m ")); } else { String error = extractJsonString(resp, "error"); String message = extractJsonString(resp, "message"); if ("token_expired".equals(error)) p.sendMessage(new TextComponent("§c✗ Der Token ist abgelaufen.")); - else if ("uuid_already_linked".equals(error)) p.sendMessage(new TextComponent("§c✗ " + (!message.isEmpty() ? message : "Diese UUID ist bereits verknüpft."))); - else if ("invalid_token".equals(error)) p.sendMessage(new TextComponent("§c✗ Ungültiger Token.")); - else p.sendMessage(new TextComponent("§c✗ Verknüpfung fehlgeschlagen: " + (!error.isEmpty() ? error : "Unbekannter Fehler"))); + else if ("uuid_already_linked".equals(error)) p.sendMessage(new TextComponent("§c✗ " + (!message.isEmpty() ? message : "Diese UUID ist bereits verkn\u00fcpft."))); + else if ("invalid_token".equals(error)) p.sendMessage(new TextComponent("§c✗ Ung\u00fcltiger Token.")); + else p.sendMessage(new TextComponent("§c✗ Verkn\u00fcpfung fehlgeschlagen: " + (!error.isEmpty() ? error : "Unbekannter Fehler"))); } } catch (Exception ex) { p.sendMessage(new TextComponent("§c✗ Fehler bei der Verbindung zum Forum.")); @@ -261,16 +261,16 @@ public class ForumBridgeModule implements Module, Listener { @Override public void execute(CommandSender sender, String[] args) { - if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(new TextComponent("§cNur Spieler können diesen Befehl benutzen.")); return; } + if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(new TextComponent("§cNur Spieler k\u00f6nnen diesen Befehl benutzen.")); return; } ProxiedPlayer p = (ProxiedPlayer) sender; List pending = storage.getPending(p.getUniqueId()); if (pending.isEmpty()) { p.sendMessage(new TextComponent("§7Keine neuen Forum-Benachrichtigungen.")); if (!wpBaseUrl.isEmpty()) { - TextComponent link = new TextComponent("§a➜ Forum öffnen"); + TextComponent link = new TextComponent("§a➜ Forum \u00f6ffnen"); link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, wpBaseUrl)); - link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("§7Klicke um das Forum zu öffnen").create())); + link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("§7Klicke um das Forum zu \u00f6ffnen").create())); p.sendMessage(link); } return; @@ -287,7 +287,7 @@ public class ForumBridgeModule implements Module, Listener { TextComponent detail = new TextComponent(!n.getTitle().isEmpty() ? "§f" + n.getTitle() : "§fvon " + n.getAuthor()); if (!n.getUrl().isEmpty()) { detail.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, n.getUrl())); - detail.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("§7Klicke zum Öffnen").create())); + detail.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("§7Klicke zum \u00d6ffnen").create())); } line.addExtra(detail); p.sendMessage(line); @@ -305,7 +305,7 @@ public class ForumBridgeModule implements Module, Listener { public ForumNotifStorage getStorage() { return storage; } /** - * FIX #13: Gibt immer einen leeren String zurück, niemals null. + * FIX #13: Gibt immer einen leeren String zur\u00fcck, niemals null. * Verhindert NullPointerExceptions in allen Aufrufern. */ private static String extractJsonString(String json, String key) { diff --git a/StatusAPI/src/main/java/net/viper/status/modules/forum/ForumNotifStorage.java b/StatusAPI/src/main/java/net/viper/status/modules/forum/ForumNotifStorage.java index 8a283ca..e1210c1 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/forum/ForumNotifStorage.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/forum/ForumNotifStorage.java @@ -9,7 +9,7 @@ import java.util.logging.Logger; /** * Speichert ausstehende Forum-Benachrichtigungen (Datei-basiert). * Benachrichtigungen die nicht sofort zugestellt werden konnten (Spieler offline) - * werden hier gespeichert und beim nächsten Login zugestellt. + * werden hier gespeichert und beim n\u00e4chsten Login zugestellt. */ public class ForumNotifStorage { @@ -26,7 +26,7 @@ public class ForumNotifStorage { } /** - * Fügt eine Benachrichtigung hinzu. + * F\u00fcgt eine Benachrichtigung hinzu. */ public void add(ForumNotification notification) { pending.computeIfAbsent(notification.getPlayerUuid(), k -> new CopyOnWriteArrayList<>()) @@ -34,7 +34,7 @@ public class ForumNotifStorage { } /** - * Gibt alle ausstehenden (nicht zugestellten) Benachrichtigungen eines Spielers zurück. + * Gibt alle ausstehenden (nicht zugestellten) Benachrichtigungen eines Spielers zur\u00fcck. */ public List getPending(UUID playerUuid) { CopyOnWriteArrayList list = pending.get(playerUuid); @@ -81,7 +81,7 @@ public class ForumNotifStorage { } /** - * Entfernt Benachrichtigungen die älter als maxDays Tage sind. + * Entfernt Benachrichtigungen die \u00e4lter als maxDays Tage sind. */ public void purgeOld(int maxDays) { long cutoff = System.currentTimeMillis() - ((long) maxDays * 24 * 60 * 60 * 1000); diff --git a/StatusAPI/src/main/java/net/viper/status/modules/forum/ForumNotification.java b/StatusAPI/src/main/java/net/viper/status/modules/forum/ForumNotification.java index 9a9d2fa..ecc5233 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/forum/ForumNotification.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/forum/ForumNotification.java @@ -25,7 +25,7 @@ public class ForumNotification { this.delivered = false; } - /** Interner Konstruktor für Deserialisierung */ + /** Interner Konstruktor f\u00fcr Deserialisierung */ ForumNotification(UUID playerUuid, String type, String title, String author, String url, long timestamp, boolean delivered) { this.playerUuid = playerUuid; this.type = type; @@ -48,12 +48,12 @@ public class ForumNotification { public void setDelivered(boolean d) { this.delivered = d; } /** - * Deutsches Label für den Benachrichtigungstyp. + * Deutsches Label f\u00fcr den Benachrichtigungstyp. */ public String getTypeLabel() { switch (type) { case "reply": return "Neue Antwort"; - case "mention": return "Erwähnung"; + case "mention": return "Erw\u00e4hnung"; case "message": return "Neue PN"; case "thread": return "Neuer Thread"; case "poll": return "Neue Umfrage"; @@ -70,15 +70,15 @@ public class ForumNotification { case "reply": return "§b"; // Aqua case "mention": return "§e"; // Gelb case "message": return "§d"; // Rosa - case "thread": return "§a"; // Grün + case "thread": return "§a"; // Gr\u00fcn case "poll": return "§3"; // Dunkel-Aqua - case "answer": return "§2"; // Dunkel-Grün - default: return "§f"; // Weiß + case "answer": return "§2"; // Dunkel-Gr\u00fcn + default: return "§f"; // Wei\u00df } } /** - * Serialisierung für Datei-Speicherung. + * Serialisierung f\u00fcr Datei-Speicherung. * Format: uuid|type|title|author|url|timestamp|delivered */ public String toLine() { diff --git a/StatusAPI/src/main/java/net/viper/status/modules/help/HelpModule.java b/StatusAPI/src/main/java/net/viper/status/modules/help/HelpModule.java index 0edb0ea..aae52d3 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/help/HelpModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/help/HelpModule.java @@ -65,7 +65,7 @@ public class HelpModule implements Module { @Override public void execute(CommandSender sender, String[] args) { if (args.length == 0) { - send(sender, "&7Nutze &e/" + getName() + " help &7für eine Befehlsübersicht."); + send(sender, "&7Nutze &e/" + getName() + " help &7f\u00fcr eine Befehls\u00fcbersicht."); return; } if (!args[0].equalsIgnoreCase("help")) { @@ -87,13 +87,13 @@ public class HelpModule implements Module { try { page = Integer.parseInt(args[1].trim()); } catch (NumberFormatException e) { - send(sender, "&cUngültige Seitenzahl. Nutze &e/" + getName() + " help <1-" + totalPages + ">&c."); + send(sender, "&cUng\u00fcltige Seitenzahl. Nutze &e/" + getName() + " help <1-" + totalPages + ">&c."); return; } } if (page < 1 || page > totalPages) { - send(sender, "&cSeite &e" + page + " &cexistiert nicht. Verfügbar: &e1&c-&e" + totalPages + "&c."); + send(sender, "&cSeite &e" + page + " &cexistiert nicht. Verf\u00fcgbar: &e1&c-&e" + totalPages + "&c."); return; } @@ -116,7 +116,7 @@ public class HelpModule implements Module { send(sender, ""); } - /** Baut alle Seiten zusammen. Admins bekommen zusätzliche Seiten. */ + /** Baut alle Seiten zusammen. Admins bekommen zus\u00e4tzliche Seiten. */ private List> buildPages(boolean isAdmin) { List> pages = new ArrayList<>(); @@ -124,7 +124,7 @@ public class HelpModule implements Module { List p1 = new ArrayList<>(); p1.add(" &e&lAllgemein"); p1.add(" &a/verify &8– &7Account verifizieren"); - p1.add(" &a/forumlink &8(&7/fl&8) &8– &7Forum-Account verknüpfen"); + p1.add(" &a/forumlink &8(&7/fl&8) &8– &7Forum-Account verkn\u00fcpfen"); p1.add(" &a/forum &8– &7Forum-Benachrichtigungen"); p1.add(" &a/go [server] &8(&7/wechsel, /switch&8) &8– &7Serverwechsel"); p1.add(" &a/scoreboard &8(&7/sb&8) [hide|show] &8– &7Scoreboard umschalten"); @@ -138,22 +138,22 @@ public class HelpModule implements Module { p1.add(" &a/chataus &8(&7/togglechat&8) &8– &7Chat-Empfang umschalten"); pages.add(p1); - // ── Seite 2: Chat (weiter) & Account-Verknüpfungen ─────────────── + // ── Seite 2: Chat (weiter) & Account-Verkn\u00fcpfungen ─────────────── List p2 = new ArrayList<>(); p2.add(" &e&lChat (Fortsetzung)"); p2.add(" &a/emoji &8(&7/emojis&8) &8– &7Alle Emojis anzeigen"); p2.add(" &a/mentions &8(&7/mention&8) &8– &7Mention-Benachrichtigungen"); p2.add(" &a/helpop &8– &7Team um Hilfe bitten"); p2.add(" &a/report &8– &7Spieler melden"); - p2.add(" &a/chatbypass &8(&7/cbp&8) &8– &7ChatModule überspringen"); + p2.add(" &a/chatbypass &8(&7/cbp&8) &8– &7ChatModule \u00fcberspringen"); p2.add(""); - p2.add(" &e&lAccount-Verknüpfungen"); - p2.add(" &a/discordlink &8(&7/dlink&8) &8– &7Discord verknüpfen"); - p2.add(" &a/telegramlink &8(&7/tlink&8) &8– &7Telegram verknüpfen"); - p2.add(" &a/unlink &8– &7Verknüpfung aufheben"); + p2.add(" &e&lAccount-Verkn\u00fcpfungen"); + p2.add(" &a/discordlink &8(&7/dlink&8) &8– &7Discord verkn\u00fcpfen"); + p2.add(" &a/telegramlink &8(&7/tlink&8) &8– &7Telegram verkn\u00fcpfen"); + p2.add(" &a/unlink &8– &7Verkn\u00fcpfung aufheben"); pages.add(p2); - // ── Admin-Seiten nur für Berechtigte ────────────────────────────── + // ── Admin-Seiten nur f\u00fcr Berechtigte ────────────────────────────── if (isAdmin) { // ── Seite 3: StatusAPI, AntiBot, Vanish ─────────────────────── List p3 = new ArrayList<>(); @@ -185,7 +185,7 @@ public class HelpModule implements Module { p4.add(""); p4.add(" &c&lAdmin &8– &eReports, Tools"); p4.add(" &c/reports [all] &8– &7Offene Reports anzeigen"); - p4.add(" &c/reportclose &8– &7Report schließen"); + p4.add(" &c/reportclose &8– &7Report schlie\u00dfen"); p4.add(" &c/automessage reload &8– &7AutoMessage neu laden"); p4.add(" &c/bcmds reload &8– &7Custom-Commands neu laden"); p4.add(" &c/cb &8– &7Command-Blocker verwalten"); @@ -198,7 +198,7 @@ public class HelpModule implements Module { /** Sendet eine klickbare Navigationszeile mit ◀ Seite X/Y ▶ */ private void sendNavigation(CommandSender sender, String cmd, int page, int total) { - // Für Konsole: einfacher Text + // F\u00fcr Konsole: einfacher Text if (!(sender instanceof ProxiedPlayer)) { String nav = " "; if (page > 1) nav += "&7[&e◀&7] "; @@ -208,10 +208,10 @@ public class HelpModule implements Module { return; } - // Für Spieler: klickbare Buttons + // F\u00fcr Spieler: klickbare Buttons TextComponent line = new TextComponent(" "); - // ◀ zurück + // ◀ zur\u00fcck if (page > 1) { TextComponent prev = new TextComponent( ChatColor.translateAlternateColorCodes('&', "&7[&e◀&7] ")); diff --git a/StatusAPI/src/main/java/net/viper/status/modules/network/MultiAccountGuard.java b/StatusAPI/src/main/java/net/viper/status/modules/network/MultiAccountGuard.java index 6464d2c..ae07889 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/network/MultiAccountGuard.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/network/MultiAccountGuard.java @@ -32,10 +32,10 @@ import java.util.logging.Logger; * * Features: * - IP-Check: blockiert zweiten Account von gleicher IP - * - Bypass NUR über LuckPerms (OP zählt nicht) + * - Bypass NUR \u00fcber LuckPerms (OP z\u00e4hlt nicht) * - Persistentes Log in multiaccountguard.log * - Staff-Benachrichtigung ingame (Permission: statusapi.staff.notify) - * - Temporärer IP-Bann nach X Versuchen (Integration mit AntiBotModule) + * - Tempor\u00e4rer IP-Bann nach X Versuchen (Integration mit AntiBotModule) * - Discord-Webhook bei Konflikt */ public class MultiAccountGuard implements Module, Listener { @@ -62,7 +62,7 @@ public class MultiAccountGuard implements Module, Listener { private boolean staffNotifyEnabled = true; private String staffNotifyFormat = "&8[&cMAG&8] &e{blocked} &7wurde blockiert &8(2. Account von &e{existing}&8) &7| IP: &f{ip}"; - // Temporärer IP-Bann + // Tempor\u00e4rer IP-Bann private boolean tempBanEnabled = true; private int tempBanMaxAttempts = 3; private int tempBanDurationSecs = 300; @@ -119,7 +119,7 @@ public class MultiAccountGuard implements Module, Listener { ProxiedPlayer joining = event.getPlayer(); if (hasBypass(joining)) { - log.info("[MultiAccountGuard] " + joining.getName() + " hat Bypass (LuckPerms) – übersprungen."); + log.info("[MultiAccountGuard] " + joining.getName() + " hat Bypass (LuckPerms) – \u00fcbersprungen."); return; } @@ -127,14 +127,14 @@ public class MultiAccountGuard implements Module, Listener { String joiningIp = extractIp(joining.getSocketAddress()); if (joiningIp == null) { - log.warning("[MultiAccountGuard] Konnte IP von " + joining.getName() + " nicht lesen – übersprungen."); + log.warning("[MultiAccountGuard] Konnte IP von " + joining.getName() + " nicht lesen – \u00fcbersprungen."); return; } log.info("[MultiAccountGuard] Login-Check: " + joining.getName() + " | UUID=" + joiningUuid + " | IP=" + joiningIp); - // Alle anderen Spieler (sich selbst per UUID ausschließen) + // Alle anderen Spieler (sich selbst per UUID ausschlie\u00dfen) List others = new ArrayList<>(); for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) { if (p.getUniqueId().equals(joiningUuid)) continue; @@ -192,7 +192,7 @@ public class MultiAccountGuard implements Module, Listener { notifyStaff(blockedName, allowedName, ip); } - // 3. Temporärer IP-Bann + // 3. Tempor\u00e4rer IP-Bann if (tempBanEnabled) { int attempts = attemptsByIp.merge(ip, 1, Integer::sum); log.info("[MultiAccountGuard] IP " + ip + " hat " + attempts + "/" + tempBanMaxAttempts + " Versuche."); @@ -258,7 +258,7 @@ public class MultiAccountGuard implements Module, Listener { } // ------------------------------------------------------------------------- - // 3. Temporärer IP-Bann via AntiBotModule + // 3. Tempor\u00e4rer IP-Bann via AntiBotModule // ------------------------------------------------------------------------- private void banIp(String ip) { @@ -266,20 +266,20 @@ public class MultiAccountGuard implements Module, Listener { StatusAPI statusApi = (StatusAPI) ProxyServer.getInstance() .getPluginManager().getPlugin("StatusAPI"); if (statusApi == null) { - log.warning("[MultiAccountGuard] StatusAPI nicht gefunden – IP-Bann nicht möglich."); + log.warning("[MultiAccountGuard] StatusAPI nicht gefunden – IP-Bann nicht m\u00f6glich."); return; } AntiBotModule antiBot = statusApi.getModuleManager().getModule(AntiBotModule.class); if (antiBot == null) { - log.warning("[MultiAccountGuard] AntiBotModule nicht gefunden – IP-Bann nicht möglich."); + log.warning("[MultiAccountGuard] AntiBotModule nicht gefunden – IP-Bann nicht m\u00f6glich."); return; } antiBot.blockIpExternal(ip, tempBanDurationSecs); - log.warning("[MultiAccountGuard] IP " + ip + " für " + tempBanDurationSecs + "s gebannt (zu viele Multi-Account-Versuche)."); + log.warning("[MultiAccountGuard] IP " + ip + " f\u00fcr " + tempBanDurationSecs + "s gebannt (zu viele Multi-Account-Versuche)."); - // Staff über den Bann informieren + // Staff \u00fcber den Bann informieren String banMsg = ChatColor.translateAlternateColorCodes('&', - "&8[&cMAG&8] &7IP &f" + ip + " &7wurde für &c" + tempBanDurationSecs + "s &7gebannt &8(zu viele Versuche)."); + "&8[&cMAG&8] &7IP &f" + ip + " &7wurde f\u00fcr &c" + tempBanDurationSecs + "s &7gebannt &8(zu viele Versuche)."); for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) { if (p.hasPermission(STAFF_PERM)) { p.sendMessage(new TextComponent(banMsg)); @@ -384,7 +384,7 @@ public class MultiAccountGuard implements Module, Listener { } return false; } catch (Exception e) { - log.warning("[MultiAccountGuard] LuckPerms-Check fehlgeschlagen für " + player.getName() + ": " + e.getMessage()); + log.warning("[MultiAccountGuard] LuckPerms-Check fehlgeschlagen f\u00fcr " + player.getName() + ": " + e.getMessage()); return false; } } diff --git a/StatusAPI/src/main/java/net/viper/status/modules/network/NetworkInfoModule.java b/StatusAPI/src/main/java/net/viper/status/modules/network/NetworkInfoModule.java index d95a8da..5695d03 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/network/NetworkInfoModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/network/NetworkInfoModule.java @@ -33,7 +33,7 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; /** - * Liefert erweiterte Proxy- und Systeminformationen für API und Ingame-Debug. + * Liefert erweiterte Proxy- und Systeminformationen f\u00fcr API und Ingame-Debug. */ public class NetworkInfoModule implements Module { @@ -66,7 +66,7 @@ public class NetworkInfoModule implements Module { private long lastTpsAlertAt = 0L; private volatile double currentProxyTps = 20.0D; - /** FIX: Öffentlicher Getter damit ScoreboardModule als TPS-Fallback darauf zugreifen kann */ + /** FIX: \u00d6ffentlicher Getter damit ScoreboardModule als TPS-Fallback darauf zugreifen kann */ public double getProxyTps() { return currentProxyTps; } private long lastTpsSampleAtMs = 0L; private ScheduledTask alertTask; @@ -139,7 +139,7 @@ public class NetworkInfoModule implements Module { return sendWebhookEmbed( webhookUrl, "✅ NetworkInfo gestartet", - "Proxy: **" + ProxyServer.getInstance().getName() + "**\nÜberwachung und Webhook-Alerts sind jetzt aktiv.", + "Proxy: **" + ProxyServer.getInstance().getName() + "**\n\u00dcberwachung und Webhook-Alerts sind jetzt aktiv.", 0x2ECC71, null, false @@ -153,7 +153,7 @@ public class NetworkInfoModule implements Module { return sendWebhookEmbed( webhookUrl, "✅ NetworkInfo gestartet", - "Überwachung und Webhook-Alerts sind jetzt aktiv.", + "\u00dcberwachung und Webhook-Alerts sind jetzt aktiv.", 0x2ECC71, fields.toString(), false @@ -165,7 +165,7 @@ public class NetworkInfoModule implements Module { return sendWebhookEmbed( webhookUrl, "🛑 NetworkInfo gestoppt", - "Die NetworkInfo-Überwachung wurde gestoppt.\nKeine weiteren Auto-Alerts bis zum nächsten Start.", + "Die NetworkInfo-\u00dcberwachung wurde gestoppt.\nKeine weiteren Auto-Alerts bis zum n\u00e4chsten Start.", 0xE74C3C, null, false @@ -179,7 +179,7 @@ public class NetworkInfoModule implements Module { return sendWebhookEmbed( webhookUrl, "🛑 NetworkInfo gestoppt", - "Die NetworkInfo-Überwachung wurde gestoppt.", + "Die NetworkInfo-\u00dcberwachung wurde gestoppt.", 0xE74C3C, fields.toString(), false @@ -222,7 +222,7 @@ public class NetworkInfoModule implements Module { color = 0x2ECC71; } else { title = "🚨 Attack Detected"; - shortText = "Ungewöhnlich hoher Verbindungs-Traffic erkannt."; + shortText = "Ungew\u00f6hnlich hoher Verbindungs-Traffic erkannt."; color = 0xE74C3C; } @@ -565,7 +565,7 @@ public class NetworkInfoModule implements Module { sendWebhookEmbed( webhookUrl, "⚠️ Hohe RAM-Auslastung", - "Ein Schwellwert wurde überschritten.", + "Ein Schwellwert wurde \u00fcberschritten.", 0xF39C12, fields.toString() ); diff --git a/StatusAPI/src/main/java/net/viper/status/modules/scoreboard/ScoreboardModule.java b/StatusAPI/src/main/java/net/viper/status/modules/scoreboard/ScoreboardModule.java index beed418..ba7552f 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/scoreboard/ScoreboardModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/scoreboard/ScoreboardModule.java @@ -58,20 +58,20 @@ public class ScoreboardModule implements Module, Listener { // ── TicketSystem Placeholder ────────────────────────────────────────────── /** Eigene aktive Tickets des Spielers (OPEN + CLAIMED + FORWARDED) */ public static final ConcurrentHashMap ticketMyOpen = new ConcurrentHashMap<>(); - /** Alle offenen Tickets gesamt (Status: OPEN) – für Supporter & Admin */ + /** Alle offenen Tickets gesamt (Status: OPEN) – f\u00fcr Supporter & Admin */ public static final java.util.concurrent.atomic.AtomicInteger ticketTotalOpen = new java.util.concurrent.atomic.AtomicInteger(0); - /** Alle Tickets in Bearbeitung gesamt (Status: CLAIMED) – für Admin */ + /** Alle Tickets in Bearbeitung gesamt (Status: CLAIMED) – f\u00fcr Admin */ public static final java.util.concurrent.atomic.AtomicInteger ticketTotalClaimed = new java.util.concurrent.atomic.AtomicInteger(0); - /** Positive Bewertungen gesamt – für Admin */ + /** Positive Bewertungen gesamt – f\u00fcr Admin */ public static final java.util.concurrent.atomic.AtomicInteger ticketRatingGood = new java.util.concurrent.atomic.AtomicInteger(0); - /** Negative Bewertungen gesamt – für Admin */ + /** Negative Bewertungen gesamt – f\u00fcr Admin */ public static final java.util.concurrent.atomic.AtomicInteger ticketRatingBad = new java.util.concurrent.atomic.AtomicInteger(0); private final ConcurrentHashMap joinTimes = new ConcurrentHashMap<>(); - /** Aktuell gerenderter Spieler – für PAPI-Auflösung in ph() */ + /** Aktuell gerenderter Spieler – f\u00fcr PAPI-Aufl\u00f6sung in ph() */ private UUID currentPlayerUuid = null; // Spieler, die das Scoreboard ausgeblendet haben - /** FIX: Referenz auf NetworkInfoModule für TPS-Fallback */ + /** FIX: Referenz auf NetworkInfoModule f\u00fcr TPS-Fallback */ private net.viper.status.modules.network.NetworkInfoModule networkInfoModule = null; /** Wird von StatusAPI nach dem Registrieren aller Module aufgerufen */ @@ -88,6 +88,10 @@ public class ScoreboardModule implements Module, Listener { private final Set forceAdminView = ConcurrentHashMap.newKeySet(); private boolean enabled = true; + private boolean nametagEnabled = true; + + // Spieler, f\u00fcr die bereits ein Nametag-Team gesetzt wurde (Teamname = "afk_" + player.getName() abgek\u00fcrzt) + private final Set nametagCreated = ConcurrentHashMap.newKeySet(); private int updateInterval = 500; // Millisekunden private int tickerSpeed = 1; private boolean rainbowEnabled = true; @@ -138,7 +142,7 @@ public class ScoreboardModule implements Module, Listener { "§8","§9","§a","§b","§c","§d","§e", "§f§0","§f§1","§f§2","§f§3","§f§4" }; - private static final int MAX_LINES = 15; // Minecraft Client zeigt max 15 Scoreboard-Einträge + private static final int MAX_LINES = 15; // Minecraft Client zeigt max 15 Scoreboard-Eintr\u00e4ge private final ConcurrentHashMap tickerPos = new ConcurrentHashMap<>(); private final ConcurrentHashMap rainbowIdx = new ConcurrentHashMap<>(); @@ -183,13 +187,13 @@ public class ScoreboardModule implements Module, Listener { } ProxyServer.getInstance().getPluginManager().registerListener(plugin, this); ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new ScoreboardToggleCommand()); - // updateInterval in ms für Daten-Updates (Kompass, Zeilen, etc.) + // updateInterval in ms f\u00fcr Daten-Updates (Kompass, Zeilen, etc.) updateTask = ProxyServer.getInstance().getScheduler().schedule( plugin, this::tickAll, updateInterval, updateInterval, TimeUnit.MILLISECONDS); - // Separater schneller Task nur für den Titel (Wave-Animation, 100ms = 10fps) + // Separater schneller Task nur f\u00fcr den Titel (Wave-Animation, 100ms = 10fps) titleTask = ProxyServer.getInstance().getScheduler().schedule( plugin, this::tickTitle, 100, 100, TimeUnit.MILLISECONDS); - // Separater Task für News-Ticker (100ms = flüssiges Scrollen) + // Separater Task f\u00fcr News-Ticker (100ms = fl\u00fcssiges Scrollen) newsTask = ProxyServer.getInstance().getScheduler().schedule( plugin, this::tickNews, 100, 100, TimeUnit.MILLISECONDS); } @@ -220,6 +224,17 @@ public class ScoreboardModule implements Module, Listener { created.remove(id); createdAdmin.remove(id); createdSupporter.remove(id); + // Nametags: Neuen Spieler nach kurzer Verz\u00f6gerung mit allen bestehenden Nametags versorgen + // UND allen anderen den Nametag des neuen Spielers senden + if (nametagEnabled) { + ProxyServer.getInstance().getScheduler().schedule(plugin, () -> { + // Alle bestehenden Spieler → neuer Spieler bekommt ihre Nametags + nametagCreated.remove(id); // Reset damit CREATE statt UPDATE gesendet wird + for (ProxiedPlayer existing : ProxyServer.getInstance().getPlayers()) { + if (existing.isConnected()) updateNametag(existing); + } + }, 2L, TimeUnit.SECONDS); + } } @EventHandler @@ -227,11 +242,11 @@ public class ScoreboardModule implements Module, Listener { if (!ready) return; ProxiedPlayer p = e.getPlayer(); UUID id = p.getUniqueId(); - // Altes Objective sauber entfernen – tickAll übernimmt den Neuaufbau + // Altes Objective sauber entfernen – tickAll \u00fcbernimmt den Neuaufbau if (created.contains(id)) { removeObjectiveAndTeams(p, OBJ_NAME, "vt"); created.remove(id); } if (createdAdmin.contains(id)) { removeObjectiveAndTeams(p, OBJ_NAME_ADMIN, "vta"); createdAdmin.remove(id); } if (createdSupporter.contains(id)) { removeObjectiveAndTeams(p, OBJ_NAME_SUPP, "vts"); createdSupporter.remove(id); } - // Kein verzögerter sendAll-Call mehr – tickAll baut nach max. 500ms neu auf + // Kein verz\u00f6gerter sendAll-Call mehr – tickAll baut nach max. 500ms neu auf } @EventHandler @@ -244,6 +259,8 @@ public class ScoreboardModule implements Module, Listener { playerWorld.remove(id); playerGamemode.remove(id); playerExp.remove(id); playerFood.remove(id); playerSpeed.remove(id); joinTimes.remove(id); hiddenPlayers.remove(id); forceAdminView.remove(id); forcePlayerView.remove(id); newsPos.remove(id); + // Nametag-Team f\u00fcr diesen Spieler bei allen anderen entfernen + if (nametagEnabled) removeNametag(e.getPlayer()); } /** Schneller Task: aktualisiert News-Position und sendet nur die betroffene Team-Zeile */ @@ -263,7 +280,7 @@ public class ScoreboardModule implements Module, Listener { Set activeCreated = isAdmin ? createdAdmin : isSupporter ? createdSupporter : created; if (!activeCreated.contains(id)) continue; - // Position vorrücken + // Position vorr\u00fccken int nOff = (newsPos.getOrDefault(id, 0) + newsSpeed) % nCycle; newsPos.put(id, nOff); @@ -271,7 +288,7 @@ public class ScoreboardModule implements Module, Listener { try { String activeObjName = isAdmin ? OBJ_NAME_ADMIN : OBJ_NAME; String newsStr = buildNewsTicker(nOff); - // Finde welche Zeilennummer(n) %news% enthält und sende nur diese + // Finde welche Zeilennummer(n) %news% enth\u00e4lt und sende nur diese java.util.Map> lineMap = isAdmin ? adminLineMap : isSupporter ? supporterLineMap : playerLineMap; for (java.util.Map.Entry> entry : lineMap.entrySet()) { @@ -293,7 +310,7 @@ public class ScoreboardModule implements Module, Listener { String lineText = c(tpl.replace("%news%", newsStr)); - // Team-Packet nur für diese Zeile senden + // Team-Packet nur f\u00fcr diese Zeile senden net.md_5.bungee.protocol.packet.Team team = new net.md_5.bungee.protocol.packet.Team(); team.setName((isAdmin ? "vta" : isSupporter ? "vts" : "vt") + lineIdx); team.setMode((byte) 2); // UPDATE @@ -314,7 +331,7 @@ public class ScoreboardModule implements Module, Listener { } } - /** Schneller Task: aktualisiert nur den Objective-Titel für flüssige Wave-Animation */ + /** Schneller Task: aktualisiert nur den Objective-Titel f\u00fcr fl\u00fcssige Wave-Animation */ private void tickTitle() { if (!rainbowEnabled || !"wave".equals(rainbowMode)) return; for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) { @@ -346,7 +363,7 @@ public class ScoreboardModule implements Module, Listener { } private void tickAll() { - // Nametags (Prefix über dem Kopf) periodisch aktualisieren + // Nametags (Prefix \u00fcber dem Kopf) periodisch aktualisieren for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) { if (!p.isConnected()) continue; UUID id = p.getUniqueId(); @@ -357,6 +374,95 @@ public class ScoreboardModule implements Module, Listener { created.add(id); } } + // Nametag-Packets an alle Spieler senden (AFK-Prefix \u00fcber dem Kopf) + if (nametagEnabled) { + for (ProxiedPlayer target : ProxyServer.getInstance().getPlayers()) { + updateNametag(target); + } + } + } + + /** + * Sendet ein Team-Packet an alle online Spieler, das den Prefix + * \u00fcber dem Kopf des 'target'-Spielers setzt. + * AFK-Spieler bekommen §7[AFK] §r als Prefix, alle anderen ihren LuckPerms-Prefix. + */ + private void updateNametag(ProxiedPlayer target) { + if (!ready || !target.isConnected()) return; + try { + boolean isAfk = Boolean.TRUE.equals(net.viper.status.StatusAPI.playerAfk.get(target.getUniqueId())); + String lpPrefix = getLpPrefix(target); + + // Teamname: "nt_" + erste 13 Zeichen des Playernamens (max 16 Zeichen insgesamt) + String teamName = "nt_" + target.getName().substring(0, Math.min(13, target.getName().length())); + + String prefixStr = isAfk ? "§7[AFK] §r" : (lpPrefix.isEmpty() ? "" : lpPrefix + "§r "); + + // Packet an alle Online-Spieler senden (damit alle den ge\u00e4nderten Prefix sehen) + for (ProxiedPlayer viewer : ProxyServer.getInstance().getPlayers()) { + if (!viewer.isConnected()) continue; + try { + Team team = new Team(); + team.setName(teamName); + boolean firstTime = !nametagCreated.contains(target.getUniqueId()); + team.setMode(firstTime ? (byte) 0 : (byte) 2); // 0=CREATE, 2=UPDATE + net.md_5.bungee.api.chat.TextComponent pfxComp = + new net.md_5.bungee.api.chat.TextComponent(""); + for (net.md_5.bungee.api.chat.BaseComponent bc : buildComponents(c(prefixStr))) + pfxComp.addExtra(bc); + team.setPrefix(Either.right(pfxComp)); + team.setSuffix(Either.right(new net.md_5.bungee.api.chat.TextComponent(""))); + team.setDisplayName(Either.right(new net.md_5.bungee.api.chat.TextComponent(""))); + team.setNameTagVisibility(Either.right(NameTagVisibility.ALWAYS)); + team.setCollisionRule(Either.right(CollisionRule.ALWAYS)); + team.setColor(Optional.of(21)); // RESET + team.setFriendlyFire((byte) 3); + if (firstTime) team.setPlayers(new String[]{ target.getName() }); + sendPkt.invoke(viewer, team); + } catch (Exception ignored) {} + } + nametagCreated.add(target.getUniqueId()); + } catch (Exception e) { + plugin.getLogger().warning("[ScoreboardModule] Nametag-Fehler f\u00fcr " + target.getName() + ": " + e.getMessage()); + } + } + + /** + * Entfernt das Nametag-Team beim Disconnect sauber vom Client aller Spieler. + */ + private void removeNametag(ProxiedPlayer target) { + String teamName = "nt_" + target.getName().substring(0, Math.min(13, target.getName().length())); + for (ProxiedPlayer viewer : ProxyServer.getInstance().getPlayers()) { + if (!viewer.isConnected() || viewer.getUniqueId().equals(target.getUniqueId())) continue; + try { + Team team = new Team(); + team.setName(teamName); + team.setMode((byte) 1); // REMOVE + sendPkt.invoke(viewer, team); + } catch (Exception ignored) {} + } + nametagCreated.remove(target.getUniqueId()); + } + + /** + * Liest den LuckPerms-Prefix eines Spielers (gleiche Logik wie getRank, aber roher Prefix). + */ + private String getLpPrefix(ProxiedPlayer player) { + try { + Class prov = Class.forName("net.luckperms.api.LuckPermsProvider"); + Object api = prov.getMethod("get").invoke(null); + Object um = api.getClass().getMethod("getUserManager").invoke(api); + Object usr = um.getClass().getMethod("getUser", UUID.class).invoke(um, player.getUniqueId()); + if (usr != null) { + Class qo = Class.forName("net.luckperms.api.query.QueryOptions"); + Object opts = qo.getMethod("defaultContextualOptions").invoke(null); + Object cache= usr.getClass().getMethod("getCachedData").invoke(usr); + Object meta = cache.getClass().getMethod("getMetaData", qo).invoke(cache, opts); + Object pfx = meta.getClass().getMethod("getPrefix").invoke(meta); + if (pfx != null && !pfx.toString().isEmpty()) return pfx.toString(); + } + } catch (Exception ignored) {} + return ""; } private void sendAll(ProxiedPlayer p) throws Exception { @@ -470,8 +576,8 @@ public class ScoreboardModule implements Module, Listener { List lines = new ArrayList<>(); boolean hasTicker = !tickerText.isEmpty() && !isAdmin && !isSupporter; if (hasTicker) lines.add(ticker(rawTicker, tOff, rIdx)); - // Maximale Inhaltszeilen: MAX_LINES insgesamt (Ticker zählt als eine) - currentPlayerUuid = id; // für PAPI-Auflösung in ph() + // Maximale Inhaltszeilen: MAX_LINES insgesamt (Ticker z\u00e4hlt als eine) + currentPlayerUuid = id; // f\u00fcr PAPI-Aufl\u00f6sung in ph() for (String tpl : srcLines) { if (lines.size() >= MAX_LINES) break; lines.add(c(ph(tpl, pn, rank, money, srv, comp, hp, hpNum, ping, online, maxpl, tps, ram, time, playtime, @@ -479,7 +585,7 @@ public class ScoreboardModule implements Module, Listener { ticketMyOpenStr, ticketTotalOpenStr, ticketTotalClaimedStr, ticketRatingGoodStr, ticketRatingBadStr, ticketRatingPctStr))); } - // Immer genau MAX_LINES Zeilen (Rest mit Leerzeilen auffüllen) + // Immer genau MAX_LINES Zeilen (Rest mit Leerzeilen auff\u00fcllen) if (lines.size() > MAX_LINES) lines = new ArrayList<>(lines.subList(0, MAX_LINES)); while (lines.size() < MAX_LINES) lines.add(" "); @@ -618,14 +724,14 @@ public class ScoreboardModule implements Module, Listener { boolean strike = text.contains("§m") || text.contains("&m"); String fmt = (bold ? "§l" : "") + (italic ? "§o" : "") + (underline ? "§n" : "") + (strike ? "§m" : ""); - // Sichtbare Zeichen zählen + // Sichtbare Zeichen z\u00e4hlen int visLen = 0; for (char c : plain.toCharArray()) if (c != ' ') visLen++; int charIdx = 0; for (int i = 0; i < plain.length(); i++) { char ch = plain.charAt(i); if (ch == ' ') { sb.append(' '); continue; } - // JAWa-Style: Hue gleichmäßig über alle Buchstaben verteilt, wandert pro Tick + // JAWa-Style: Hue gleichm\u00e4\u00dfig \u00fcber alle Buchstaben verteilt, wandert pro Tick float hue = ((float) charIdx / Math.max(visLen, 1) + idx * this.waveSpeed) % 1.0f; if (hue < 0) hue += 1.0f; int[] rgb = waveColors != null @@ -688,12 +794,12 @@ public class ScoreboardModule implements Module, Listener { // Parse: %gradient:C1:C2:...:TEXT% String inner = input.substring(start + 10, end); // Letzter Teil ist der Text, vorherige Teile sind Farben - // Text beginnt nach dem letzten ':' der eine Farbe abschließt + // Text beginnt nach dem letzten ':' der eine Farbe abschlie\u00dft // Strategie: Teile von links lesen solange sie Farben sind java.util.List stops = new java.util.ArrayList<>(); int colonIdx = 0; while (colonIdx < inner.length()) { - // Nächsten ':' suchen + // N\u00e4chsten ':' suchen int nextColon = inner.indexOf(':', colonIdx); if (nextColon < 0) break; String candidate = inner.substring(colonIdx, nextColon); @@ -723,18 +829,30 @@ public class ScoreboardModule implements Module, Listener { // Gradient auf sichtbare Zeichen anwenden int visLen = 0; for (char ch : plain.toCharArray()) if (ch != ' ') visLen++; + if (visLen == 0) visLen = 1; int charIdx = 0; + int[] lastRgb = stops.get(0); for (char ch : plain.toCharArray()) { - if (ch == ' ') { result.append(' '); continue; } + if (ch == ' ') { + result.append('\u00A7').append('x'); + result.append('\u00A7').append(String.format("%02X", lastRgb[0]).charAt(0)); + result.append('\u00A7').append(String.format("%02X", lastRgb[0]).charAt(1)); + result.append('\u00A7').append(String.format("%02X", lastRgb[1]).charAt(0)); + result.append('\u00A7').append(String.format("%02X", lastRgb[1]).charAt(1)); + result.append('\u00A7').append(String.format("%02X", lastRgb[2]).charAt(0)); + result.append('\u00A7').append(String.format("%02X", lastRgb[2]).charAt(1)); + result.append(fmt).append(ch); + continue; + } float pos = visLen <= 1 ? 0f : (float) charIdx / (visLen - 1); - int[] rgb = interpolateGradient(stops, pos); + lastRgb = interpolateGradient(stops, pos); result.append('\u00A7').append('x'); - result.append('\u00A7').append(String.format("%02X", rgb[0]).charAt(0)); - result.append('\u00A7').append(String.format("%02X", rgb[0]).charAt(1)); - result.append('\u00A7').append(String.format("%02X", rgb[1]).charAt(0)); - result.append('\u00A7').append(String.format("%02X", rgb[1]).charAt(1)); - result.append('\u00A7').append(String.format("%02X", rgb[2]).charAt(0)); - result.append('\u00A7').append(String.format("%02X", rgb[2]).charAt(1)); + result.append('\u00A7').append(String.format("%02X", lastRgb[0]).charAt(0)); + result.append('\u00A7').append(String.format("%02X", lastRgb[0]).charAt(1)); + result.append('\u00A7').append(String.format("%02X", lastRgb[1]).charAt(0)); + result.append('\u00A7').append(String.format("%02X", lastRgb[1]).charAt(1)); + result.append('\u00A7').append(String.format("%02X", lastRgb[2]).charAt(0)); + result.append('\u00A7').append(String.format("%02X", lastRgb[2]).charAt(1)); result.append(fmt); result.append(ch); charIdx++; @@ -795,20 +913,20 @@ public class ScoreboardModule implements Module, Listener { switch (Character.toLowerCase(code)) { case '0': return new int[]{ 0, 0, 0}; // §0 Schwarz case '1': return new int[]{ 0, 0, 170}; // §1 Dunkelblau - case '2': return new int[]{ 0, 170, 0}; // §2 Dunkelgrün - case '3': return new int[]{ 0, 170, 170}; // §3 Dunkeltürkis + case '2': return new int[]{ 0, 170, 0}; // §2 Dunkelgr\u00fcn + case '3': return new int[]{ 0, 170, 170}; // §3 Dunkelt\u00fcrkis case '4': return new int[]{170, 0, 0}; // §4 Dunkelrot case '5': return new int[]{170, 0, 170}; // §5 Lila case '6': return new int[]{255, 170, 0}; // §6 Gold case '7': return new int[]{170, 170, 170}; // §7 Grau case '8': return new int[]{ 85, 85, 85}; // §8 Dunkelgrau case '9': return new int[]{ 85, 85, 255}; // §9 Blau - case 'a': return new int[]{ 85, 255, 85}; // §a Hellgrün - case 'b': return new int[]{ 85, 255, 255}; // §b Türkis + case 'a': return new int[]{ 85, 255, 85}; // §a Hellgr\u00fcn + case 'b': return new int[]{ 85, 255, 255}; // §b T\u00fcrkis case 'c': return new int[]{255, 85, 85}; // §c Hellrot case 'd': return new int[]{255, 85, 255}; // §d Hellviolett case 'e': return new int[]{255, 255, 85}; // §e Gelb - case 'f': return new int[]{255, 255, 255}; // §f Weiß + case 'f': return new int[]{255, 255, 255}; // §f Wei\u00df default: return null; } } @@ -841,7 +959,7 @@ public class ScoreboardModule implements Module, Listener { String ticketMyOpen, String ticketTotalOpen, String ticketTotalClaimed, String ticketRatingGood, String ticketRatingBad, String ticketRatingPct) { if (tpl == null) return " "; - // PAPI-Werte zuerst einsetzen; native Tokens überschreiben sie danach + // PAPI-Werte zuerst einsetzen; native Tokens \u00fcberschreiben sie danach String s = resolvePapiPlaceholders(tpl, currentPlayerUuid); s = s .replace("%player%", player) .replace("%rank%", rank) @@ -972,18 +1090,18 @@ public class ScoreboardModule implements Module, Listener { * Beispiel-Output (Blick nach N): "- - - - &c&lN&r&7 - - - -" */ /** - * Baut den Kompass-Balken mit Sub-Grad-Auflösung. + * Baut den Kompass-Balken mit Sub-Grad-Aufl\u00f6sung. * * Das Fenster hat COMPASS_WIN Slots (z.B. 9). Jeder Slot entspricht genau * 1 Grad auf dem Kreis (COMPASS_SLOTS = 360). Dadurch verschiebt sich der - * Balken bei jeder 1°-Änderung um genau eine Position – kein Springen. + * Balken bei jeder 1°-\u00c4nderung um genau eine Position – kein Springen. * * Jeder Slot zeigt: * - 'N' / 'E' / 'S' / 'W' wenn sein Grad-Slot mit einem Himmelsrichtungs- - * Label übereinstimmt (±0°, kein Runden) - * - '|' für den Mittelpunkt (aktuelle Blickrichtung), + * Label \u00fcbereinstimmt (±0°, kein Runden) + * - '|' f\u00fcr den Mittelpunkt (aktuelle Blickrichtung), * falls kein Label genau trifft - * - '·' für alle anderen Slots + * - '·' f\u00fcr alle anderen Slots * * Akzeptierte raw-Formate: * Float-String "normYaw" (0..360): Bridge sendet normYaw = ((yaw%360)+360)%360 @@ -994,11 +1112,11 @@ public class ScoreboardModule implements Module, Listener { * * Zeichen: * '─' normaler Slot (grau, &8) - * N/E/S/W außerhalb Mitte: gelb &e + * N/E/S/W au\u00dferhalb Mitte: gelb &e * Mitte mit Himmelsrichtung: rot+fett &c&l * Mitte ohne Himmelsrichtung: rot+fett &c&l '|' * - * Bridge sendet normYaw 0..360 (0 = Süden/MC-Konvention). + * Bridge sendet normYaw 0..360 (0 = S\u00fcden/MC-Konvention). * Umrechnung: facingDeg = (normYaw + 180) % 360 → 0=N, 90=E, 180=S, 270=W */ private static final int SCOREBOARD_WIDTH = 26; // sichtbare Breite des Scoreboards @@ -1044,10 +1162,10 @@ public class ScoreboardModule implements Module, Listener { char marker = (label != 0) ? label : '|'; sb.append("&c&l").append(marker).append("&r&8"); } else if (label != 0) { - // Himmelsrichtung außerhalb Mitte: gelb, gut sichtbar + // Himmelsrichtung au\u00dferhalb Mitte: gelb, gut sichtbar sb.append("&e").append(label).append("&8"); } else { - sb.append('-'); // ASCII-Strich, sicher für alle MC-Versionen + sb.append('-'); // ASCII-Strich, sicher f\u00fcr alle MC-Versionen } } // Kompass zentrieren: Leerzeichen links = (Scoreboard-Breite - Kompass-Breite) / 2 @@ -1062,8 +1180,8 @@ public class ScoreboardModule implements Module, Listener { * Baut den News-Ticker: Text gleitet von rechts nach links durch ein fixes Fenster. * * Das Fenster ist IMMER exakt newsWidth Zeichen breit – Scoreboard-Breite konstant. - * Text erscheint von rechts, läuft durch, verschwindet links. - * Dann Pause (Leerzeichen) bevor der Text wieder von rechts einläuft. + * Text erscheint von rechts, l\u00e4uft durch, verschwindet links. + * Dann Pause (Leerzeichen) bevor der Text wieder von rechts einl\u00e4uft. * * newsPrefix ist optional – leer lassen in Config zum Deaktivieren. */ @@ -1081,7 +1199,7 @@ public class ScoreboardModule implements Module, Listener { int cycleLen = plain.length() + gap; int pos = offset % cycleLen; - // Virtuelles Band: plain + 4 Leerzeichen, läuft zyklisch + // Virtuelles Band: plain + 4 Leerzeichen, l\u00e4uft zyklisch // Fenster zeigt winWidth Zeichen aus dem Band // Band-Position des ersten Fensterzeichens: pos - winWidth + 1 StringBuilder window = new StringBuilder(); @@ -1104,12 +1222,12 @@ public class ScoreboardModule implements Module, Listener { } private String getTps(UUID id) { - // Primär: TPS vom Backend-Server (per POST /scoreboard/tps gesendet) + // Prim\u00e4r: TPS vom Backend-Server (per POST /scoreboard/tps gesendet) Double t = playerTps.get(id); if (t != null) { return new DecimalFormat("0.0").format(Math.min(20.0, t)); } - // FIX: Fallback auf Proxy-eigene TPS aus NetworkInfoModule (immer verfügbar) + // FIX: Fallback auf Proxy-eigene TPS aus NetworkInfoModule (immer verf\u00fcgbar) if (networkInfoModule != null && networkInfoModule.isEnabled()) { double proxyTps = networkInfoModule.getProxyTps(); return new DecimalFormat("0.0").format(Math.min(20.0, proxyTps)) + " (P)"; @@ -1123,7 +1241,7 @@ public class ScoreboardModule implements Module, Listener { + "MB/" + (m.getHeapMemoryUsage().getMax() / 1048576) + "MB"; } - // ── Component Builder (Hex-Farb-Support für Scoreboard) ───────────────────── + // ── Component Builder (Hex-Farb-Support f\u00fcr Scoreboard) ───────────────────── /** * Wandelt einen bereits mit c() prozessierten String (§-Codes + §x§R§R§G§G§B§B) @@ -1160,7 +1278,7 @@ public class ScoreboardModule implements Module, Listener { + text.charAt(i+7) + text.charAt(i+9) + text.charAt(i+11) + text.charAt(i+13); currentColor = net.md_5.bungee.api.ChatColor.of("#" + hex); - // Formatierungen NICHT zurücksetzen – Bold/Italic bleiben erhalten + // Formatierungen NICHT zur\u00fccksetzen – Bold/Italic bleiben erhalten } catch (Exception ignored) {} i += 14; continue; @@ -1222,7 +1340,7 @@ public class ScoreboardModule implements Module, Listener { // ══════════════════════════════════════════════════════════════════════════ // Farb-Parser: Birdflop-kompatibel - // Unterstützte Formate (alle gleichzeitig nutzbar): + // Unterst\u00fctzte Formate (alle gleichzeitig nutzbar): // // &#RRGGBB → Pro-Zeichen Hex (Birdflop Standard-Output) // {#RRGGBB} → Bracket-Format @@ -1249,7 +1367,7 @@ public class ScoreboardModule implements Module, Listener { private static String parseMiniMessage(String text) { if (text == null || !text.contains("<")) return text == null ? "" : text; - // gradient-Tags als erstes, weil sie anderen Text enthalten können + // gradient-Tags als erstes, weil sie anderen Text enthalten k\u00f6nnen text = parseGradientTags(text); // shadow-Tags text = parseShadowTags(text); @@ -1268,7 +1386,7 @@ public class ScoreboardModule implements Module, Listener { int start = text.indexOf(" suchen (mit Tiefenzähler für verschachtelte <...>) + // Schlie\u00dfendes > suchen (mit Tiefenz\u00e4hler f\u00fcr verschachtelte <...>) int end = findClosingAngle(text, start + 1); if (end < 0) { result.append(text, i, text.length()); break; } String inner = text.substring(start + 1, end); // "gradient:#C1:#C2:TEXT" @@ -1279,8 +1397,8 @@ public class ScoreboardModule implements Module, Listener { } /** - * Parst "gradient:#C1:#C2:#C3:TEXT" → eingefärbten Text. - * TEXT darf selbst wieder §-Codes oder &-Codes enthalten (z.B. &l für Bold). + * Parst "gradient:#C1:#C2:#C3:TEXT" → eingef\u00e4rbten Text. + * TEXT darf selbst wieder §-Codes oder &-Codes enthalten (z.B. &l f\u00fcr Bold). */ private static String applyGradientTag(String inner) { // inner = "gradient:COLOR:COLOR:...:TEXT" @@ -1309,14 +1427,14 @@ public class ScoreboardModule implements Module, Listener { } if (colors.size() < 2) return textSb.toString(); - // Shadow-Tags im Text zuerst auflösen (können im Gradient-Text stecken) + // Shadow-Tags im Text zuerst aufl\u00f6sen (k\u00f6nnen im Gradient-Text stecken) String rawText = parseShadowTags(textSb.toString()); return applyGradient(rawText, colors); } private static String applyGradient(String text, java.util.List colorStops) { if (text == null || text.isEmpty()) return text; - // §-Codes und &-Codes aus Text herausfiltern für Längenberechnung + // §-Codes und &-Codes aus Text herausfiltern f\u00fcr L\u00e4ngenberechnung String plain = text .replaceAll("\u00A7[0-9a-fk-orx]", "") .replaceAll("&[0-9a-fA-Fk-orK-OR]", "") @@ -1334,7 +1452,7 @@ public class ScoreboardModule implements Module, Listener { while (ci < text.length()) { char ch = text.charAt(ci); - // §x§R§R§G§G§B§B durchreichen (bereits aufgelöste Hex-Farbe z.B. von shadow) + // §x§R§R§G§G§B§B durchreichen (bereits aufgel\u00f6ste Hex-Farbe z.B. von shadow) if (ch == '\u00A7' && ci + 1 < text.length() && text.charAt(ci + 1) == 'x') { // Lese die 12 folgenden Zeichen (§x + 6x §digit) if (ci + 13 < text.length() + 1) { @@ -1409,7 +1527,7 @@ public class ScoreboardModule implements Module, Listener { text = text.replace("", "&m").replace("", "&r"); text = text.replace("", "&k").replace("", "&r"); text = text.replace("", "&r").replace("", ""); - // Closing-Tags entfernen (werden nach Verarbeitung nicht mehr benötigt) + // Closing-Tags entfernen (werden nach Verarbeitung nicht mehr ben\u00f6tigt) text = text.replaceAll("", ""); text = text.replaceAll("", ""); text = text.replaceAll("", ""); @@ -1513,8 +1631,8 @@ public class ScoreboardModule implements Module, Listener { private static int clamp(int v) { return Math.max(0, Math.min(255, v)); } /** - * Findet das schließende '>' für ein Tag das bei fromIndex beginnt. - * Berücksichtigt verschachtelte <...>. + * Findet das schlie\u00dfende '>' f\u00fcr ein Tag das bei fromIndex beginnt. + * Ber\u00fccksichtigt verschachtelte <...>. */ private static int findClosingAngle(String text, int fromIndex) { int depth = 0; @@ -1556,7 +1674,7 @@ public class ScoreboardModule implements Module, Listener { "scoreboard.ticker.speed=1\n" + "\n" + "scoreboard.rainbow.enabled=true\n" + - "# wave=fließende Welle, chars=Regenbogen pro Buchstabe, line=eine Farbe\n" + + "# wave=flie\u00dfende Welle, chars=Regenbogen pro Buchstabe, line=eine Farbe\n" + "scoreboard.rainbow.mode=wave\n" + "# Wellengeschwindigkeit: 1=sehr langsam, 10=normal, 50=schnell\n" + "scoreboard.rainbow.speed=10\n" + @@ -1674,6 +1792,7 @@ public class ScoreboardModule implements Module, Listener { } java.util.function.BiFunction g = (k,d) -> map.getOrDefault(k, d); enabled = Boolean.parseBoolean(g.apply("scoreboard.enabled", "true")); + nametagEnabled = Boolean.parseBoolean(g.apply("nametag.enabled", "true")); updateInterval = Math.max(250, pi(g.apply("scoreboard.update_interval", "500"), 500)); title = g.apply("scoreboard.title", "&6&lViper Network"); adminTitle = g.apply("scoreboard.admin_title", "&c&l[Admin] &4&lPanel"); @@ -1810,7 +1929,7 @@ public class ScoreboardModule implements Module, Listener { } /** - * Entfernt ein Scoreboard-Objective und alle zugehörigen Teams sauber vom Client. + * Entfernt ein Scoreboard-Objective und alle zugeh\u00f6rigen Teams sauber vom Client. * Muss aufgerufen werden bevor ein anderes Objective aktiviert wird, * sonst crasht der Client beim erneuten Team-CREATE. * @@ -1819,7 +1938,7 @@ public class ScoreboardModule implements Module, Listener { * @param teamPrefix Team-Prefix (z.B. "vt" oder "vta") */ private void removeObjectiveAndTeams(ProxiedPlayer p, String objName, String teamPrefix) { - // 1. Alle Teams löschen (Mode 1 = REMOVE) + // 1. Alle Teams l\u00f6schen (Mode 1 = REMOVE) for (int i = 0; i < 15; i++) { try { Team team = new Team(); @@ -1849,7 +1968,7 @@ public class ScoreboardModule implements Module, Listener { */ private static final List SB_SUBS = Arrays.asList("hide", "show", "player", "admin", "supporter"); - /** Tab-Completion für /scoreboard via TabCompleteEvent */ + /** Tab-Completion f\u00fcr /scoreboard via TabCompleteEvent */ @EventHandler public void onTabComplete(TabCompleteEvent event) { if (!(event.getSender() instanceof ProxiedPlayer)) return; @@ -1887,7 +2006,7 @@ public class ScoreboardModule implements Module, Listener { public void execute(CommandSender sender, String[] args) { if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(new net.md_5.bungee.api.chat.TextComponent( - ChatColor.RED + "Nur für Spieler.")); + ChatColor.RED + "Nur f\u00fcr Spieler.")); return; } ProxiedPlayer p = (ProxiedPlayer) sender; diff --git a/StatusAPI/src/main/java/net/viper/status/modules/serverswitcher/ServerSwitcherModule.java b/StatusAPI/src/main/java/net/viper/status/modules/serverswitcher/ServerSwitcherModule.java index a0c649e..2443a76 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/serverswitcher/ServerSwitcherModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/serverswitcher/ServerSwitcherModule.java @@ -53,7 +53,7 @@ public class ServerSwitcherModule implements Module { private List aliases = new ArrayList<>(Arrays.asList("wechsel", "switch")); private List serverWhitelist = new ArrayList<>(); - private String colorHeader = "&8&m---&r &6&lServer-Menü &8&m---"; + private String colorHeader = "&8&m---&r &6&lServer-Men\u00fc &8&m---"; private String colorEntry = "&7>> &e"; private String colorOnline = "&a"; private String colorOffline = "&c"; @@ -103,7 +103,7 @@ public class ServerSwitcherModule implements Module { "# Optionale Whitelist (leer = alle BungeeCord-Server)\n" + "# Beispiel: serverswitcher.servers=lobby,citybuild,survival\n" + "serverswitcher.servers=\n\n" + - "serverswitcher.color.header=&8&m---&r &6&lServer-Menü &8&m---\n" + + "serverswitcher.color.header=&8&m---&r &6&lServer-Men\u00fc &8&m---\n" + "serverswitcher.color.entry=&7>> &e\n" + "serverswitcher.color.online=&a\n" + "serverswitcher.color.offline=&c\n" + @@ -178,7 +178,7 @@ public class ServerSwitcherModule implements Module { @Override public void execute(CommandSender sender, String[] args) { if (!(sender instanceof ProxiedPlayer)) { - sender.sendMessage(c("&cDieser Befehl ist nur für Spieler verfügbar.")); + sender.sendMessage(c("&cDieser Befehl ist nur f\u00fcr Spieler verf\u00fcgbar.")); return; } @@ -242,7 +242,7 @@ public class ServerSwitcherModule implements Module { } player.sendMessage(c("&8&m----------------------------")); - player.sendMessage(c("&7Tipp: &e/" + commandName + " &7für direkten Wechsel")); + player.sendMessage(c("&7Tipp: &e/" + commandName + " &7f\u00fcr direkten Wechsel")); } } diff --git a/StatusAPI/src/main/java/net/viper/status/modules/tablist/TablistModule.java b/StatusAPI/src/main/java/net/viper/status/modules/tablist/TablistModule.java index eb53c2b..ed9983f 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/tablist/TablistModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/tablist/TablistModule.java @@ -30,7 +30,7 @@ public class TablistModule implements Module, Listener { private static final String CONFIG_FILE = "tablist.properties"; - // Leerer Skin (grauer Kopf) für Platzhalter-Slots + // Leerer Skin (grauer Kopf) f\u00fcr Platzhalter-Slots private static final net.md_5.bungee.protocol.data.Property[] EMPTY_SKIN = { new net.md_5.bungee.protocol.data.Property( "textures", @@ -62,7 +62,7 @@ public class TablistModule implements Module, Listener { private String footerLine2 = " &7Discord: &ediscord.viper-network.de &8| &7Shop: &eviper-network.de/shop"; private String footerLine3 = "&8&m" + rep('\u2501', 53); - private String compactHeader1 = "&6&lViper Network &8• &2Hallo, &a%player%&7! &6Schön dass du da bist!"; + private String compactHeader1 = "&6&lViper Network &8• &2Hallo, &a%player%&7! &6Sch\u00f6n dass du da bist!"; private String compactHeader2 = ""; private String compactHeader3 = ""; private boolean compactHeader1Spacer = false; @@ -197,7 +197,7 @@ public class TablistModule implements Module, Listener { if (skin != null && skin.length > 0) skinCache.put(p.getUniqueId(), skin); disableBungeeTabHandler(p); // BungeeCord resettet tabListHandler nach PostLoginEvent intern nochmals. - // Deshalb: 0.5s, 1s, 2s nacheinander überschreiben. + // Deshalb: 0.5s, 1s, 2s nacheinander \u00fcberschreiben. long[] delays = {500L, 1000L, 2000L}; for (long delayMs : delays) { ProxyServer.getInstance().getScheduler().schedule(plugin, () -> { @@ -218,8 +218,8 @@ public class TablistModule implements Module, Listener { // Sofort deaktivieren disableBungeeTabHandler(switched); - // BungeeCord setzt den tabListHandler nach ServerSwitch intern mehrfach zurück. - // Deshalb: 0.5s, 1s, 2s, 3s nacheinander überschreiben + tablist neu senden. + // BungeeCord setzt den tabListHandler nach ServerSwitch intern mehrfach zur\u00fcck. + // Deshalb: 0.5s, 1s, 2s, 3s nacheinander \u00fcberschreiben + tablist neu senden. long[] delays = {500L, 1000L, 2000L, 3000L}; for (long delayMs : delays) { ProxyServer.getInstance().getScheduler().schedule(plugin, () -> { @@ -238,6 +238,7 @@ public class TablistModule implements Module, Listener { public void onDisconnect(PlayerDisconnectEvent e) { if (!enabled) return; skinCache.remove(e.getPlayer().getUniqueId()); + net.viper.status.StatusAPI.playerAfk.remove(e.getPlayer().getUniqueId()); ProxyServer.getInstance().getScheduler().schedule(plugin, () -> { for (ProxiedPlayer viewer : ProxyServer.getInstance().getPlayers()) { try { removeFakeSlots(viewer); } catch (Exception ignored) {} @@ -305,8 +306,8 @@ public class TablistModule implements Module, Listener { new net.md_5.bungee.api.chat.TextComponent(hComps), new net.md_5.bungee.api.chat.TextComponent(fComps)); // Erst Slots senden (ADD_PLAYER), DANN echte Spieler verstecken (UPDATE_LISTED=false). - // Umgekehrte Reihenfolge führt zu "Ignoring player info update for unknown player" - // weil der Client UPDATE_LISTED für unbekannte UUIDs ignoriert. + // Umgekehrte Reihenfolge f\u00fchrt zu "Ignoring player info update for unknown player" + // weil der Client UPDATE_LISTED f\u00fcr unbekannte UUIDs ignoriert. sendSlots(viewer, buildItems(viewer)); hideRealPlayers(viewer); } catch (Exception ex) { @@ -365,9 +366,9 @@ public class TablistModule implements Module, Listener { boolean compact = "compact".equalsIgnoreCase(layoutMode); // Ob der Spalten-Header einen Slot belegt: - // "full" = explizit aktiviert, "none"/"small" = früher deaktiviert. + // "full" = explizit aktiviert, "none"/"small" = fr\u00fcher deaktiviert. // FIX: Im Server-Modus immer den Servernamen in Zeile 0 schreiben, - // sonst weiß der Spieler nicht welche Spalte welcher Server ist. + // sonst wei\u00df der Spieler nicht welche Spalte welcher Server ist. boolean useSlotHeader = !"none".equalsIgnoreCase(columnHeaderMode); // Info-Spalte (nur classic) @@ -400,7 +401,7 @@ public class TablistModule implements Module, Listener { if ("custom".equalsIgnoreCase(playerDisplayMode)) { // ── Custom-Modus: alle Spieler zusammen, nach Rang sortiert ────────── // Minecraft Tab-Grid ist spaltenweise aufgebaut (Spalte 1 = Slots 0-19, Spalte 2 = Slots 20-39) - // "Links nach rechts" = Zeile 0 über alle Spalten, dann Zeile 1 usw. + // "Links nach rechts" = Zeile 0 \u00fcber alle Spalten, dann Zeile 1 usw. // Spieler 0 → Spalte 0 Zeile 0, Spieler 1 → Spalte 1 Zeile 0, Spieler 2 → Spalte 2 Zeile 0 // Spieler 3 → Spalte 0 Zeile 1, Spieler 4 → Spalte 1 Zeile 1 usw. List allPlayers = new ArrayList<>(ProxyServer.getInstance().getPlayers()); @@ -409,7 +410,7 @@ public class TablistModule implements Module, Listener { int usedCols = columns - startCol; int maxSlots = usedCols * rows; int playerIdx = 0; - // Zeile für Zeile iterieren, innerhalb jeder Zeile alle Spalten + // Zeile f\u00fcr Zeile iterieren, innerhalb jeder Zeile alle Spalten outer: for (int row = 0; row < rows; row++) { for (int col = startCol; col < columns; col++) { @@ -417,11 +418,16 @@ public class TablistModule implements Module, Listener { ProxiedPlayer p = allPlayers.get(playerIdx++); int base = col * rows; String prefix = getLuckPermsPrefix(p); + boolean isAfk = Boolean.TRUE.equals(net.viper.status.StatusAPI.playerAfk.get(p.getUniqueId())); String symbol = getServerSymbol(p); String nameStr = p.getName() + (symbol.isEmpty() ? "" : " " + symbol); - set(texts, base, row, prefix.isEmpty() - ? c("&7" + nameStr) - : c(prefix + "&r " + nameStr)); + if (isAfk) { + set(texts, base, row, c("&7[AFK] &r" + nameStr)); + } else { + set(texts, base, row, prefix.isEmpty() + ? c("&7" + nameStr) + : c(prefix + "&r " + nameStr)); + } net.md_5.bungee.protocol.data.Property[] skin = skinCache.get(p.getUniqueId()); skins[base + row] = (skin != null && skin.length > 0) ? skin : EMPTY_SKIN; pings[base + row] = p.getPing() < 0 ? 1 : p.getPing(); @@ -443,11 +449,16 @@ public class TablistModule implements Module, Listener { for (ProxiedPlayer p : sortPlayersByRank(new ArrayList<>(si.getPlayers()))) { if (row >= rows) break; String prefix = getLuckPermsPrefix(p); + boolean isAfk = Boolean.TRUE.equals(net.viper.status.StatusAPI.playerAfk.get(p.getUniqueId())); String symbol = getServerSymbol(p); String nameStr = p.getName() + (symbol.isEmpty() ? "" : " " + symbol); - set(texts, base, row, prefix.isEmpty() - ? c("&7" + nameStr) - : c(prefix + "&r " + nameStr)); + if (isAfk) { + set(texts, base, row, c("&7[AFK] &r" + nameStr)); + } else { + set(texts, base, row, prefix.isEmpty() + ? c("&7" + nameStr) + : c(prefix + "&r " + nameStr)); + } net.md_5.bungee.protocol.data.Property[] skin = skinCache.get(p.getUniqueId()); skins[base + row] = (skin != null && skin.length > 0) ? skin : EMPTY_SKIN; pings[base + row] = p.getPing() < 0 ? 1 : p.getPing(); @@ -496,8 +507,8 @@ public class TablistModule implements Module, Listener { sendPacketQueuedMethod.invoke(viewer, playerPkt); } - // Server-UUIDs (BungeeCord schreibt pro Server 2 Einträge in die Tablist): - // Diese per PlayerListItemRemove entfernen – das funktioniert auch für + // Server-UUIDs (BungeeCord schreibt pro Server 2 Eintr\u00e4ge in die Tablist): + // Diese per PlayerListItemRemove entfernen – das funktioniert auch f\u00fcr // UUIDs die der Client noch nicht kennt, ohne Skin-Schaden. List serverUuids = new ArrayList<>(); for (String srvName : ProxyServer.getInstance().getServers().keySet()) { @@ -543,9 +554,9 @@ public class TablistModule implements Module, Listener { pkt.setItems(items); sendPacketQueuedMethod.invoke(viewer, pkt); - // Paket 2: Explizit UPDATE_LISTED=true für alle Fake-Slots nochmal senden. + // Paket 2: Explizit UPDATE_LISTED=true f\u00fcr alle Fake-Slots nochmal senden. // BungeeCord sendet nach ServerSwitch ein eigenes Tab-Paket das einige Slots - // auf listed=false setzt – dieses zweite Paket überschreibt das wieder. + // auf listed=false setzt – dieses zweite Paket \u00fcberschreibt das wieder. PlayerListItemUpdate listedPkt = new PlayerListItemUpdate(); listedPkt.setActions(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LISTED)); listedPkt.setItems(items); // items haben alle listed=true gesetzt @@ -601,15 +612,15 @@ public class TablistModule implements Module, Listener { } /** - * ── ULTIMATE: Gibt das konfigurierte Server-Symbol für den Spieler zurück. - * Leer wenn kein Symbol für den aktuellen Server definiert ist. + * ── ULTIMATE: Gibt das konfigurierte Server-Symbol f\u00fcr den Spieler zur\u00fcck. + * Leer wenn kein Symbol f\u00fcr den aktuellen Server definiert ist. */ private String getServerSymbol(ProxiedPlayer player) { if (serverSymbols.isEmpty() || player.getServer() == null) return ""; String srvKey = player.getServer().getInfo().getName().toLowerCase(); String raw = serverSymbols.get(srvKey); if (raw == null || raw.isEmpty()) return ""; - return c(raw); // Farb-Codes und Hex-Farben auflösen + return c(raw); // Farb-Codes und Hex-Farben aufl\u00f6sen } private net.md_5.bungee.protocol.data.Property[] fetchSkin(ProxiedPlayer player) { @@ -696,7 +707,7 @@ public class TablistModule implements Module, Listener { Object cache = usr.getClass().getMethod("getCachedData").invoke(usr); Object meta = cache.getClass().getMethod("getMetaData", qo).invoke(cache, opts); Object pfx = meta.getClass().getMethod("getPrefix").invoke(meta); - // ── HEX-Farben auch im Prefix auflösen ─────────────────────── + // ── HEX-Farben auch im Prefix aufl\u00f6sen ─────────────────────── if (pfx != null) return c(pfx.toString()); } } catch (Exception ignored) {} @@ -750,11 +761,11 @@ public class TablistModule implements Module, Listener { if (base + row < total) arr[base + row] = text == null ? " " : text; return row + 1; } - // ── Farb-Auflösung ───────────────────────────────────────────────────────── + // ── Farb-Aufl\u00f6sung ───────────────────────────────────────────────────────── // ══════════════════════════════════════════════════════════════════════════ // Farb-Parser: Birdflop-kompatibel - // Unterstützte Formate (alle gleichzeitig nutzbar): + // Unterst\u00fctzte Formate (alle gleichzeitig nutzbar): // // &#RRGGBB → Pro-Zeichen Hex (Birdflop Standard-Output) // {#RRGGBB} → Bracket-Format @@ -781,7 +792,7 @@ public class TablistModule implements Module, Listener { private static String parseMiniMessage(String text) { if (text == null || !text.contains("<")) return text == null ? "" : text; - // gradient-Tags als erstes, weil sie anderen Text enthalten können + // gradient-Tags als erstes, weil sie anderen Text enthalten k\u00f6nnen text = parseGradientTags(text); // shadow-Tags text = parseShadowTags(text); @@ -800,7 +811,7 @@ public class TablistModule implements Module, Listener { int start = text.indexOf(" suchen (mit Tiefenzähler für verschachtelte <...>) + // Schlie\u00dfendes > suchen (mit Tiefenz\u00e4hler f\u00fcr verschachtelte <...>) int end = findClosingAngle(text, start + 1); if (end < 0) { result.append(text, i, text.length()); break; } String inner = text.substring(start + 1, end); // "gradient:#C1:#C2:TEXT" @@ -811,8 +822,8 @@ public class TablistModule implements Module, Listener { } /** - * Parst "gradient:#C1:#C2:#C3:TEXT" → eingefärbten Text. - * TEXT darf selbst wieder §-Codes oder &-Codes enthalten (z.B. &l für Bold). + * Parst "gradient:#C1:#C2:#C3:TEXT" → eingef\u00e4rbten Text. + * TEXT darf selbst wieder §-Codes oder &-Codes enthalten (z.B. &l f\u00fcr Bold). */ private static String applyGradientTag(String inner) { // inner = "gradient:COLOR:COLOR:...:TEXT" @@ -841,14 +852,14 @@ public class TablistModule implements Module, Listener { } if (colors.size() < 2) return textSb.toString(); - // Shadow-Tags im Text zuerst auflösen (können im Gradient-Text stecken) + // Shadow-Tags im Text zuerst aufl\u00f6sen (k\u00f6nnen im Gradient-Text stecken) String rawText = parseShadowTags(textSb.toString()); return applyGradient(rawText, colors); } private static String applyGradient(String text, java.util.List colorStops) { if (text == null || text.isEmpty()) return text; - // §-Codes und &-Codes aus Text herausfiltern für Längenberechnung + // §-Codes und &-Codes aus Text herausfiltern f\u00fcr L\u00e4ngenberechnung String plain = text .replaceAll("\u00A7[0-9a-fk-orx]", "") .replaceAll("&[0-9a-fA-Fk-orK-OR]", "") @@ -866,7 +877,7 @@ public class TablistModule implements Module, Listener { while (ci < text.length()) { char ch = text.charAt(ci); - // §x§R§R§G§G§B§B durchreichen (bereits aufgelöste Hex-Farbe z.B. von shadow) + // §x§R§R§G§G§B§B durchreichen (bereits aufgel\u00f6ste Hex-Farbe z.B. von shadow) if (ch == '\u00A7' && ci + 1 < text.length() && text.charAt(ci + 1) == 'x') { // Lese die 12 folgenden Zeichen (§x + 6x §digit) if (ci + 13 < text.length() + 1) { @@ -941,7 +952,7 @@ public class TablistModule implements Module, Listener { text = text.replace("", "&m").replace("", "&r"); text = text.replace("", "&k").replace("", "&r"); text = text.replace("", "&r").replace("", ""); - // Closing-Tags entfernen (werden nach Verarbeitung nicht mehr benötigt) + // Closing-Tags entfernen (werden nach Verarbeitung nicht mehr ben\u00f6tigt) text = text.replaceAll("", ""); text = text.replaceAll("", ""); text = text.replaceAll("", ""); @@ -1045,8 +1056,8 @@ public class TablistModule implements Module, Listener { private static int clamp(int v) { return Math.max(0, Math.min(255, v)); } /** - * Findet das schließende '>' für ein Tag das bei fromIndex beginnt. - * Berücksichtigt verschachtelte <...>. + * Findet das schlie\u00dfende '>' f\u00fcr ein Tag das bei fromIndex beginnt. + * Ber\u00fccksichtigt verschachtelte <...>. */ private static int findClosingAngle(String text, int fromIndex) { int depth = 0; @@ -1081,8 +1092,8 @@ public class TablistModule implements Module, Listener { "tablist.server_order=\n" + "tablist.hidden_servers=\n" + "tablist.rank_order=owner,mod,primo,vip,scout,bewohner\n\n" + - "# column_header: full = großer Spalten-Header (alte Markierung)\n" + - "# none = kein Header, Zeile 0 ist für Spieler frei (MuckiDEE-Wunsch)\n" + + "# column_header: full = gro\u00dfer Spalten-Header (alte Markierung)\n" + + "# none = kein Header, Zeile 0 ist f\u00fcr Spieler frei (MuckiDEE-Wunsch)\n" + "# small = wie none, aber Server-Namen erscheinen im Tab-Footer\n" + "tablist.column_header=none\n" + "# player_display: server = Server-basiert (default) | custom = alle zusammen nach Rang sortiert\n" + @@ -1095,7 +1106,7 @@ public class TablistModule implements Module, Listener { "tablist.footer.line3=&8&m" + sep + "\n\n" + "# ── Compact Layout ──────────────────────────────────────────────────\n" + "# Platzhalter: %player% %rank% %server% %world% %time% %balance% %ping% %online%\n" + - "# spacer=true: leere Zeile = Abstand | spacer=false: leere Zeile = überspringen\n" + + "# spacer=true: leere Zeile = Abstand | spacer=false: leere Zeile = \u00fcberspringen\n" + "tablist.compact.header.line1=&6&lViper Network &8• &2Hallo, &a%player%&7!\n" + "tablist.compact.header.line2=&dCitybuild &8• &aSurvival &8• &eMinigames\n" + "tablist.compact.header.line2.spacer=false\n" + @@ -1146,7 +1157,7 @@ public class TablistModule implements Module, Listener { "\n# ── Server-Symbole ───────────────────────────────────────────────────\n" + "# Format: tablist.symbol.=&FarbCode Symbol\n" + "# Farben: & + Code (z.B. &6 = Gold) oder &#RRGGBB / {#RRGGBB} / <#RRGGBB>\n" + - "# Emojis und Unicode-Symbole werden unterstützt.\n" + + "# Emojis und Unicode-Symbole werden unterst\u00fctzt.\n" + "# Der Symbol-Text erscheint hinter dem Spielernamen in der Tablist.\n" + "tablist.symbol.lobby=&f\uD83C\uDFE0\n" + "tablist.symbol.sv1=&6\u26CF\uFE0F\n" + diff --git a/StatusAPI/src/main/java/net/viper/status/modules/vanish/VanishModule.java b/StatusAPI/src/main/java/net/viper/status/modules/vanish/VanishModule.java index e18a1af..d26c92b 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/vanish/VanishModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/vanish/VanishModule.java @@ -22,11 +22,11 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; /** - * VanishModule für StatusAPI (BungeeCord) + * VanishModule f\u00fcr StatusAPI (BungeeCord) * * Features: * - /vanish zum Ein-/Ausschalten - * - /vanish für Admin-Vanish anderer Spieler + * - /vanish f\u00fcr Admin-Vanish anderer Spieler * - /vanishlist – zeigt alle aktuell unsichtbaren Spieler * - Vanish-Status wird persistent in vanish.dat gespeichert * - Beim Login wird gespeicherter Status wiederhergestellt @@ -71,7 +71,7 @@ public class VanishModule implements Module, Listener { @Override public void onDisable(Plugin plugin) { save(); - // Alle als sichtbar markieren beim Shutdown (damit beim nächsten Start + // Alle als sichtbar markieren beim Shutdown (damit beim n\u00e4chsten Start // der VanishProvider sauber ist – load() setzt sie beim Login neu) for (UUID uuid : persistentVanished) { VanishProvider.setVanished(uuid, false); @@ -95,7 +95,7 @@ public class VanishModule implements Module, Listener { // den Vanish-Status garantiert vorfindet und keine Join-Nachricht sendet. VanishProvider.setVanished(player.getUniqueId(), true); - // Nur die Bestätigungsnachricht an den Spieler wird verzögert, + // Nur die Best\u00e4tigungsnachricht an den Spieler wird verz\u00f6gert, // damit der Client bereit ist. plugin.getProxy().getScheduler().schedule(plugin, () -> { if (player.isConnected()) { @@ -108,7 +108,7 @@ public class VanishModule implements Module, Listener { @EventHandler public void onDisconnect(PlayerDisconnectEvent e) { // VanishProvider cleanup – der Eintrag in persistentVanished bleibt - // erhalten damit der Status beim nächsten Login wiederhergestellt wird + // erhalten damit der Status beim n\u00e4chsten Login wiederhergestellt wird VanishProvider.cleanup(e.getPlayer().getUniqueId()); } @@ -135,7 +135,7 @@ public class VanishModule implements Module, Listener { } else { // Anderen Spieler vanishen if (!sender.hasPermission(PERMISSION_OTHER)) { - sender.sendMessage(color("&cDu hast keine Berechtigung für /vanish .")); + sender.sendMessage(color("&cDu hast keine Berechtigung f\u00fcr /vanish .")); return; } ProxiedPlayer target = ProxyServer.getInstance().getPlayer(args[0]); @@ -176,7 +176,7 @@ public class VanishModule implements Module, Listener { /** * Schaltet den Vanish-Status eines Spielers um. * - * @param executor Der Befehlsgeber (für Feedback-Nachrichten) + * @param executor Der Befehlsgeber (f\u00fcr Feedback-Nachrichten) * @param target Der betroffene Spieler */ private void toggleVanish(CommandSender executor, ProxiedPlayer target) { @@ -187,7 +187,7 @@ public class VanishModule implements Module, Listener { ? "&8[&7Vanish&8] &f" + target.getName() + " &7ist jetzt &cUnsichtbar&7." : "&8[&7Vanish&8] &f" + target.getName() + " &7ist jetzt &aSichtbar&7."; - // Feedback an den Ausführenden + // Feedback an den Ausf\u00fchrenden executor.sendMessage(color(statusMsg)); // Falls jemand anderes gevanisht wurde, auch dem Ziel Bescheid geben @@ -198,7 +198,7 @@ public class VanishModule implements Module, Listener { target.sendMessage(color(selfMsg)); } - // Admins mit chat.admin.bypass informieren (außer dem Ausführenden) + // Admins mit chat.admin.bypass informieren (au\u00dfer dem Ausf\u00fchrenden) for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) { if (p.equals(executor) || p.equals(target)) continue; if (p.hasPermission("chat.admin.bypass")) { @@ -222,7 +222,7 @@ public class VanishModule implements Module, Listener { } /** - * Öffentliche API für andere Module. + * \u00d6ffentliche API f\u00fcr andere Module. */ public boolean isVanished(ProxiedPlayer player) { return VanishProvider.isVanished(player); diff --git a/StatusAPI/src/main/java/net/viper/status/modules/verify/VerifyModule.java b/StatusAPI/src/main/java/net/viper/status/modules/verify/VerifyModule.java index 5aeb5a6..1648f54 100644 --- a/StatusAPI/src/main/java/net/viper/status/modules/verify/VerifyModule.java +++ b/StatusAPI/src/main/java/net/viper/status/modules/verify/VerifyModule.java @@ -34,7 +34,7 @@ import java.util.Properties; public class VerifyModule implements Module { private String wpVerifyUrl; - // Keys sind lowercase normalisiert für case-insensitiven Vergleich + // Keys sind lowercase normalisiert f\u00fcr case-insensitiven Vergleich private final Map serverConfigs = new HashMap<>(); @Override @@ -84,7 +84,7 @@ public class VerifyModule implements Module { ServerConfig config = serverConfigs.computeIfAbsent(serverName, k -> new ServerConfig()); if ("id".equalsIgnoreCase(type)) { try { config.serverId = Integer.parseInt(props.getProperty(key)); } - catch (NumberFormatException e) { plugin.getLogger().warning("Ungültige Server ID für " + serverName); } + catch (NumberFormatException e) { plugin.getLogger().warning("Ung\u00fcltige Server ID f\u00fcr " + serverName); } } else if ("secret".equalsIgnoreCase(type)) { config.sharedSecret = props.getProperty(key); } @@ -103,11 +103,11 @@ public class VerifyModule implements Module { @Override public void execute(CommandSender sender, String[] args) { - if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(ChatColor.RED + "Nur Spieler können diesen Befehl benutzen."); return; } + if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(ChatColor.RED + "Nur Spieler k\u00f6nnen diesen Befehl benutzen."); return; } ProxiedPlayer p = (ProxiedPlayer) sender; if (args.length != 1) { p.sendMessage(ChatColor.YELLOW + "Benutzung: /verify "); return; } - // FIX #7: Servername lowercase für case-insensitiven Lookup + // FIX #7: Servername lowercase f\u00fcr case-insensitiven Lookup String serverName = p.getServer().getInfo().getName().toLowerCase(); ServerConfig config = serverConfigs.get(serverName); diff --git a/StatusAPI/src/main/java/net/viper/status/stats/PlayerStats.java b/StatusAPI/src/main/java/net/viper/status/stats/PlayerStats.java index 1c9c04f..fb5f7d8 100644 --- a/StatusAPI/src/main/java/net/viper/status/stats/PlayerStats.java +++ b/StatusAPI/src/main/java/net/viper/status/stats/PlayerStats.java @@ -82,8 +82,8 @@ public class PlayerStats { * balance|totalEarned|totalSpent|transactionsCount| * bansCount|mutesCount|warnsCount|lastPunishmentAt|lastPunishmentType|punishmentScore * - * HINWEIS: kills/deaths wurden in Version 1.17.1 als Felder 7 und 8 eingefügt. - * fromLine() ist rückwärtskompatibel (alte Dateien ohne kills/deaths werden 0 gesetzt). + * HINWEIS: kills/deaths wurden in Version 1.17.1 als Felder 7 und 8 eingef\u00fcgt. + * fromLine() ist r\u00fcckw\u00e4rtskompatibel (alte Dateien ohne kills/deaths werden 0 gesetzt). */ public synchronized String toLine() { String safeType = (lastPunishmentType == null ? "" : lastPunishmentType).replace("|", "_"); @@ -123,7 +123,7 @@ public class PlayerStats { // Erkennung ob altes Format (ohne kills/deaths, Economy ab Index 7) // oder neues Format (kills/deaths ab Index 7, Economy ab Index 9). // Altes Format hat 17 Felder (Index 0-16), neues hat 19 (Index 0-18). - // Heuristik: Wenn parts[7] ein gültiger Integer ist UND parts[9] wie eine + // Heuristik: Wenn parts[7] ein g\u00fcltiger Integer ist UND parts[9] wie eine // Gleitkommazahl aussieht → neues Format. Sonst altes Format. boolean newFormat = false; if (parts.length >= 19) { diff --git a/StatusAPI/src/main/java/net/viper/status/stats/StatsModule.java b/StatusAPI/src/main/java/net/viper/status/stats/StatsModule.java index 46baa0a..05328cb 100644 --- a/StatusAPI/src/main/java/net/viper/status/stats/StatsModule.java +++ b/StatusAPI/src/main/java/net/viper/status/stats/StatsModule.java @@ -13,14 +13,14 @@ import java.util.concurrent.TimeUnit; * StatsModule: Tracking von Spielerdaten (Playtime, Joins, Kills, Deaths). * * Fixes: - * - BUG-1: Crash-Recovery für currentSessionStart (verhindert falsche Spielzeit nach Absturz) + * - BUG-1: Crash-Recovery f\u00fcr currentSessionStart (verhindert falsche Spielzeit nach Absturz) * - BUG-2: kills / deaths werden jetzt getrackt und per POST /stats/update aktualisiert */ public class StatsModule implements Module, Listener { /** - * Maximale Sessionlänge nach einem Crash noch gutschreiben (24 Stunden). - * Längere Differenzen sind unrealistisch → werden ignoriert, currentSessionStart = 0 gesetzt. + * Maximale Sessionl\u00e4nge nach einem Crash noch gutschreiben (24 Stunden). + * L\u00e4ngere Differenzen sind unrealistisch → werden ignoriert, currentSessionStart = 0 gesetzt. */ private static final long MAX_SESSION_SECONDS = 86_400L; @@ -47,15 +47,15 @@ public class StatsModule implements Module, Listener { // FIX BUG-1: Crash-Recovery – offene Sessions bereinigen. // // Bei normalem Shutdown setzt onDisable() currentSessionStart = 0 und speichert. - // Bei einem Crash (kill -9, OOM, etc.) passiert das nicht. Beim nächsten Start - // sind alle Spieler offline, aber currentSessionStart enthält noch den alten - // Timestamp. getPlaytimeWithCurrentSession() würde dann fälschlicherweise + // Bei einem Crash (kill -9, OOM, etc.) passiert das nicht. Beim n\u00e4chsten Start + // sind alle Spieler offline, aber currentSessionStart enth\u00e4lt noch den alten + // Timestamp. getPlaytimeWithCurrentSession() w\u00fcrde dann f\u00e4lschlicherweise // (now - alter_crash_timestamp) zur Spielzeit addieren → massiv falscher Wert. // - // Fix: Nach dem Laden jeden Eintrag prüfen. Falls currentSessionStart > 0: + // Fix: Nach dem Laden jeden Eintrag pr\u00fcfen. Falls currentSessionStart > 0: // - Plausible Differenz (≤ MAX_SESSION_SECONDS) → als echte Zeit gutschreiben - // - Unplausibel (> MAX_SESSION_SECONDS) → verwerfen, nur zurücksetzen - // - In beiden Fällen: currentSessionStart = 0 setzen + // - Unplausibel (> MAX_SESSION_SECONDS) → verwerfen, nur zur\u00fccksetzen + // - In beiden F\u00e4llen: currentSessionStart = 0 setzen // ----------------------------------------------------------------------- long now = System.currentTimeMillis() / 1000L; int recovered = 0; @@ -68,9 +68,9 @@ public class StatsModule implements Module, Listener { recovered++; } else if (delta > MAX_SESSION_SECONDS) { plugin.getLogger().warning( - "[StatsModule] Unplausibler currentSessionStart für " + ps.name + "[StatsModule] Unplausibler currentSessionStart f\u00fcr " + ps.name + " (delta=" + delta + "s > " + MAX_SESSION_SECONDS + "s). " - + "Session wird ohne Gutschrift zurückgesetzt." + + "Session wird ohne Gutschrift zur\u00fcckgesetzt." ); } ps.currentSessionStart = 0; diff --git a/StatusAPI/src/main/resources/plugin.yml b/StatusAPI/src/main/resources/plugin.yml index 12ca058..a40b523 100644 --- a/StatusAPI/src/main/resources/plugin.yml +++ b/StatusAPI/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: StatusAPI main: net.viper.status.StatusAPI -version: 4.1.3 +version: 4.1.4 author: M_Viper description: StatusAPI für BungeeCord inkl. Update-Checker, Modul-System und ChatModule # Mindestanforderung: Minecraft 1.20 / BungeeCord mit PlayerChatEvent-Unterstützung @@ -10,6 +10,11 @@ softdepend: - Geyser-BungeeCord commands: + # ── AfkModule ────────────────────────────────────────────── + afk: + description: AFK-Modus ein- oder ausschalten + usage: /afk + # ── HelpModule ──────────────────────────────────────────── help: description: Zeigt alle verfügbaren Befehle (Admin-Befehle nur mit Berechtigung) @@ -205,6 +210,11 @@ commands: aliases: [wechsel, switch] permissions: + # ── AfkModule ────────────────────────────────────────────── + statusapi.afk.bypass: + description: Automatisches AFK nach Inaktivität umgehen + default: op + # ── StatusAPI Core ──────────────────────────────────────── statusapi.admin: description: Zugang zu StatusAPI-Administrationsbefehlen (reload etc.)