Upload folder via GUI - src

This commit is contained in:
Git Manager GUI
2026-05-09 11:23:27 +02:00
parent 2c2dd9c248
commit 2c3050c87a
15 changed files with 481 additions and 436 deletions

View File

@@ -53,6 +53,14 @@ public class StatusAPI extends Plugin implements Runnable {
// Kontostand pro Spieler (UUID -> Balance), wird von StatusAPIBridge gepusht // Kontostand pro Spieler (UUID -> Balance), wird von StatusAPIBridge gepusht
public static final ConcurrentHashMap<UUID, Double> playerBalances = new ConcurrentHashMap<>(); public static final ConcurrentHashMap<UUID, Double> playerBalances = new ConcurrentHashMap<>();
// Debug-Modus (aus verify.properties)
public static boolean DEBUG = false;
/** Gibt eine Info-Meldung nur im Debug-Modus aus */
public static void debugLog(Plugin plugin, String message) {
if (DEBUG) plugin.getLogger().info(message);
}
private volatile Thread thread; private volatile Thread thread;
private volatile ServerSocket serverSocket; private volatile ServerSocket serverSocket;
private volatile boolean shuttingDown = false; private volatile boolean shuttingDown = false;
@@ -83,6 +91,9 @@ public class StatusAPI extends Plugin implements Runnable {
port = 9191; port = 9191;
} }
// Debug-Modus
DEBUG = verifyProperties != null && Boolean.parseBoolean(verifyProperties.getProperty("debug", "false"));
moduleManager = new ModuleManager(); moduleManager = new ModuleManager();
// Module in korrekter Reihenfolge registrieren // Module in korrekter Reihenfolge registrieren
@@ -192,8 +203,9 @@ public class StatusAPI extends Plugin implements Runnable {
File file = new File(getDataFolder(), "verify.properties"); File file = new File(getDataFolder(), "verify.properties");
verifyProperties = new Properties(); verifyProperties = new Properties();
if (file.exists()) { if (file.exists()) {
try (FileInputStream fis = new FileInputStream(file)) { try (java.io.InputStreamReader reader = new java.io.InputStreamReader(
verifyProperties.load(fis); new FileInputStream(file), StandardCharsets.UTF_8)) {
verifyProperties.load(reader);
} }
} else { } else {
getLogger().warning("verify.properties nicht gefunden."); getLogger().warning("verify.properties nicht gefunden.");

View File

@@ -121,7 +121,7 @@ public class AntiBotModule implements Module, Listener {
ensureSecurityLogFile(); ensureSecurityLogFile();
if (!enabled) { if (!enabled) {
this.plugin.getLogger().info("[AntiBotModule] deaktiviert via " + CONFIG_FILE_NAME); StatusAPI.debugLog(this.plugin, "[AntiBotModule] deaktiviert via " + CONFIG_FILE_NAME);
return; return;
} }
@@ -129,7 +129,7 @@ public class AntiBotModule implements Module, Listener {
ProxyServer.getInstance().getPluginManager().registerCommand(this.plugin, new AntiBotCommand()); ProxyServer.getInstance().getPluginManager().registerCommand(this.plugin, new AntiBotCommand());
ProxyServer.getInstance().getScheduler().schedule(this.plugin, this::tick, 1, 1, TimeUnit.SECONDS); ProxyServer.getInstance().getScheduler().schedule(this.plugin, this::tick, 1, 1, TimeUnit.SECONDS);
this.plugin.getLogger().info("[AntiBotModule] aktiviert. maxCps=" + maxCps this.plugin.getLogger().fine("[AntiBotModule] aktiviert. maxCps=" + maxCps
+ ", attackStartCps=" + attackStartCps + ", ip/min=" + ipConnectionsPerMinute); + ", attackStartCps=" + attackStartCps + ", ip/min=" + ipConnectionsPerMinute);
} }

View File

@@ -1,12 +1,20 @@
package net.viper.status.modules.broadcast; package net.viper.status.modules.broadcast;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.BaseComponent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ClickEvent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.ComponentBuilder; import net.md_5.bungee.api.chat.ComponentBuilder;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Listener;
import net.viper.status.module.Module; import net.viper.status.module.Module;
@@ -57,7 +65,7 @@ public class BroadcastModule implements Module, Listener {
loadConfig(); loadConfig();
if (!enabled) return; if (!enabled) return;
try { plugin.getProxy().getPluginManager().registerListener(plugin, this); } catch (Throwable ignored) {} try { plugin.getProxy().getPluginManager().registerListener(plugin, this); } catch (Throwable ignored) {}
plugin.getLogger().info("[BroadcastModule] aktiviert. Format: " + format); plugin.getLogger().fine("[BroadcastModule] aktiviert. Format: " + format);
loadSchedules(); loadSchedules();
plugin.getProxy().getScheduler().schedule(plugin, this::processScheduled, 1, 1, TimeUnit.SECONDS); plugin.getProxy().getScheduler().schedule(plugin, this::processScheduled, 1, 1, TimeUnit.SECONDS);
} }
@@ -142,7 +150,7 @@ public class BroadcastModule implements Module, Listener {
for (ProxiedPlayer p : plugin.getProxy().getPlayers()) { for (ProxiedPlayer p : plugin.getProxy().getPlayers()) {
try { p.sendMessage(components); sent++; } catch (Throwable ignored) {} try { p.sendMessage(components); sent++; } catch (Throwable ignored) {}
} }
plugin.getLogger().info("[BroadcastModule] Broadcast gesendet (Empfänger=" + sent + "): " + message); StatusAPI.debugLog(plugin, "[BroadcastModule] Broadcast gesendet (Empfänger=" + sent + "): " + message);
return true; return true;
} }
@@ -279,7 +287,7 @@ public class BroadcastModule implements Module, Listener {
} }
} }
scheduledByClientId.putAll(loaded); scheduledByClientId.putAll(loaded);
plugin.getLogger().info("[BroadcastModule] " + loaded.size() + " geplante Broadcasts aus Datei wiederhergestellt."); plugin.getLogger().fine("[BroadcastModule] geplante Broadcasts wiederhergestellt.");
} }
public boolean scheduleBroadcast(long timestampMillis, String sourceName, String message, String type, public boolean scheduleBroadcast(long timestampMillis, String sourceName, String message, String type,
@@ -311,7 +319,7 @@ public class BroadcastModule implements Module, Listener {
prefix, prefixColor, bracketColor, messageColor, recur); prefix, prefixColor, bracketColor, messageColor, recur);
scheduledByClientId.put(id, sb); scheduledByClientId.put(id, sb);
saveSchedules(); saveSchedules();
plugin.getLogger().info("[BroadcastModule] Neue geplante Nachricht registriert: " + id StatusAPI.debugLog(plugin, "[BroadcastModule] Neue geplante Nachricht registriert: " + id
+ " @ " + dateFormat.format(new Date(timestampMillis))); + " @ " + dateFormat.format(new Date(timestampMillis)));
return true; return true;
} }
@@ -319,7 +327,7 @@ public class BroadcastModule implements Module, Listener {
public boolean cancelScheduled(String clientScheduleId) { public boolean cancelScheduled(String clientScheduleId) {
if (clientScheduleId == null || clientScheduleId.trim().isEmpty()) return false; if (clientScheduleId == null || clientScheduleId.trim().isEmpty()) return false;
ScheduledBroadcast removed = scheduledByClientId.remove(clientScheduleId); ScheduledBroadcast removed = scheduledByClientId.remove(clientScheduleId);
if (removed != null) { plugin.getLogger().info("[BroadcastModule] Schedule abgebrochen: " + clientScheduleId); saveSchedules(); return true; } if (removed != null) { StatusAPI.debugLog(plugin, "[BroadcastModule] Schedule abgebrochen: " + clientScheduleId); saveSchedules(); return true; }
return false; return false;
} }
@@ -332,7 +340,7 @@ public class BroadcastModule implements Module, Listener {
for (Map.Entry<String, ScheduledBroadcast> entry : scheduledByClientId.entrySet()) { for (Map.Entry<String, ScheduledBroadcast> entry : scheduledByClientId.entrySet()) {
ScheduledBroadcast sb = entry.getValue(); ScheduledBroadcast sb = entry.getValue();
if (sb.nextRunMillis <= now) { if (sb.nextRunMillis <= now) {
plugin.getLogger().info("[BroadcastModule] ⏰ Sende geplante Nachricht (ID: " + entry.getKey() + ")"); StatusAPI.debugLog(plugin, "[BroadcastModule] ⏰ Sende geplante Nachricht (ID: " + entry.getKey() + ")");
handleBroadcast(sb.sourceName, sb.message, sb.type, "", sb.prefix, sb.prefixColor, sb.bracketColor, sb.messageColor); handleBroadcast(sb.sourceName, sb.message, sb.type, "", sb.prefix, sb.prefixColor, sb.bracketColor, sb.messageColor);
if (!"none".equalsIgnoreCase(sb.recur)) { if (!"none".equalsIgnoreCase(sb.recur)) {
long next = computeNextMillis(sb.nextRunMillis, sb.recur); long next = computeNextMillis(sb.nextRunMillis, sb.recur);

View File

@@ -103,7 +103,7 @@ public class ChatConfig {
config = new Configuration(); config = new Configuration();
} }
parseConfig(); parseConfig();
plugin.getLogger().info("[ChatModule] " + channels.size() + " Kanäle geladen."); plugin.getLogger().fine("[ChatModule] " + channels.size() + " Kanäle geladen.");
} }
private void parseConfig() { private void parseConfig() {
@@ -392,7 +392,7 @@ public class ChatConfig {
try (java.io.FileWriter fw = new java.io.FileWriter(filterFile)) { try (java.io.FileWriter fw = new java.io.FileWriter(filterFile)) {
fw.write("# StatusAPI - Wort-Blacklist\n# words:\n# - beispielwort\nwords:\n"); fw.write("# StatusAPI - Wort-Blacklist\n# words:\n# - beispielwort\nwords:\n");
} }
plugin.getLogger().info("[ChatModule] filter.yml erstellt."); plugin.getLogger().fine("[ChatModule] filter.yml erstellt.");
} catch (IOException e) { plugin.getLogger().warning("[ChatModule] Konnte filter.yml nicht erstellen: " + e.getMessage()); } } catch (IOException e) { plugin.getLogger().warning("[ChatModule] Konnte filter.yml nicht erstellen: " + e.getMessage()); }
return; return;
} }

View File

@@ -113,7 +113,7 @@ public class ChatModule implements Module, Listener {
// ChatLogger // ChatLogger
if (config.isChatlogEnabled()) { if (config.isChatlogEnabled()) {
chatLogger = new ChatLogger(plugin.getDataFolder(), logger, config.getChatlogRetentionDays()); chatLogger = new ChatLogger(plugin.getDataFolder(), logger, config.getChatlogRetentionDays());
logger.info("[ChatModule] Chat-Log aktiviert (" + config.getChatlogRetentionDays() + " Tage Aufbewahrung)."); logger.fine("[ChatModule] Chat-Log aktiviert (" + config.getChatlogRetentionDays() + " Tage Aufbewahrung).");
} }
// ReportManager // ReportManager
@@ -144,7 +144,7 @@ public class ChatModule implements Module, Listener {
ProxyServer.getInstance().getPluginManager().registerListener(plugin, this); ProxyServer.getInstance().getPluginManager().registerListener(plugin, this);
registerCommands(); registerCommands();
logger.info("[ChatModule] Aktiviert " + config.getChannels().size() + " Kanäle geladen."); logger.fine("[ChatModule] Aktiviert " + config.getChannels().size() + " Kanäle geladen.");
} }
@Override @Override

View File

@@ -62,7 +62,7 @@ public class CommandBlockerModule implements Module, Listener {
} }
}); });
this.plugin.getLogger().info("[CommandBlocker] aktiviert (" + blocked.size() + " Commands)."); this.plugin.getLogger().fine("[CommandBlocker] aktiviert (" + blocked.size() + " Commands).");
} }

View File

@@ -48,7 +48,7 @@ public class CustomCommandModule implements Module, Listener {
// Hier casten wir 'Plugin' zu 'StatusAPI', da wir wissen, dass es das ist // Hier casten wir 'Plugin' zu 'StatusAPI', da wir wissen, dass es das ist
this.plugin = (StatusAPI) plugin; this.plugin = (StatusAPI) plugin;
this.plugin.getLogger().info("Lade CustomCommandModule..."); this.plugin.getLogger().fine("Lade CustomCommandModule...");
reloadConfig(); reloadConfig();
if (this.config == null) { if (this.config == null) {
this.config = new Configuration(); this.config = new Configuration();

View File

@@ -2,6 +2,7 @@ package net.viper.status.modules.economy;
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import java.sql.*; import java.sql.*;
@@ -23,6 +24,9 @@ public class EconomyDatabase {
this.log = plugin.getLogger(); this.log = plugin.getLogger();
HikariConfig cfg = new HikariConfig(); HikariConfig cfg = new HikariConfig();
// HikariCP Startup-Logs unterdrücken
java.util.logging.Logger.getLogger("com.zaxxer.hikari").setLevel(java.util.logging.Level.WARNING);
java.util.logging.Logger.getLogger("com.zaxxer.hikari.HikariDataSource").setLevel(java.util.logging.Level.WARNING);
cfg.setJdbcUrl("jdbc:mysql://" + host + ":" + port + "/" + database cfg.setJdbcUrl("jdbc:mysql://" + host + ":" + port + "/" + database
+ "?useSSL=false&autoReconnect=true&characterEncoding=UTF-8&useUnicode=true"); + "?useSSL=false&autoReconnect=true&characterEncoding=UTF-8&useUnicode=true");
cfg.setUsername(user); cfg.setUsername(user);
@@ -67,7 +71,7 @@ public class EconomyDatabase {
" `updated` BIGINT NOT NULL" + " `updated` BIGINT NOT NULL" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;")) { ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;")) {
ps.executeUpdate(); ps.executeUpdate();
log.info("[Economy] MySQL verbunden Tabellen bereit."); if (StatusAPI.DEBUG) log.info("[Economy] MySQL verbunden Tabellen bereit.");
} catch (SQLException e) { } catch (SQLException e) {
log.severe("[Economy] Tabellen-Setup (bc_player_names) fehlgeschlagen: " + e.getMessage()); log.severe("[Economy] Tabellen-Setup (bc_player_names) fehlgeschlagen: " + e.getMessage());
} }

View File

@@ -1,5 +1,6 @@
package net.viper.status.modules.economy; package net.viper.status.modules.economy;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import java.util.UUID; import java.util.UUID;
@@ -64,7 +65,7 @@ public class EconomyManager {
UUID uuid = UUID.fromString(formatted); UUID uuid = UUID.fromString(formatted);
// Für künftige Lookups speichern // Für künftige Lookups speichern
db.saveNameMapping(uuid, name); db.saveNameMapping(uuid, name);
plugin.getLogger().info("[Economy] Mojang-Lookup: " + name + "" + uuid); StatusAPI.debugLog(plugin, "[Economy] Mojang-Lookup: " + name + "" + uuid);
return uuid; return uuid;
} catch (Exception e) { } catch (Exception e) {
plugin.getLogger().warning("[Economy] Mojang-Lookup fehlgeschlagen für " + name + ": " + e.getMessage()); plugin.getLogger().warning("[Economy] Mojang-Lookup fehlgeschlagen für " + name + ": " + e.getMessage());

View File

@@ -57,14 +57,14 @@ public class EconomyModule implements Module {
plugin.getProxy().getPluginManager().registerCommand(plugin, new PayCommand(plugin, manager)); plugin.getProxy().getPluginManager().registerCommand(plugin, new PayCommand(plugin, manager));
plugin.getProxy().getPluginManager().registerCommand(plugin, new EcoAdminCommand(plugin, manager)); plugin.getProxy().getPluginManager().registerCommand(plugin, new EcoAdminCommand(plugin, manager));
plugin.getLogger().info("[Economy] EconomyModule aktiviert (start-balance: " + startBal + ")."); StatusAPI.debugLog(plugin, "[Economy] EconomyModule aktiviert (start-balance: " + startBal + ").");
} }
@Override @Override
public void onDisable(Plugin plugin) { public void onDisable(Plugin plugin) {
if (database != null) { if (database != null) {
database.close(); database.close();
plugin.getLogger().info("[Economy] MySQL-Verbindung geschlossen."); StatusAPI.debugLog(plugin, "[Economy] MySQL-Verbindung geschlossen.");
} }
} }

View File

@@ -1,17 +1,30 @@
package net.viper.status.modules.forum; package net.viper.status.modules.forum;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ClickEvent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.ComponentBuilder; import net.md_5.bungee.api.chat.ComponentBuilder;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.HoverEvent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.event.PostLoginEvent; import net.md_5.bungee.api.event.PostLoginEvent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.Command;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Listener;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import net.viper.status.StatusAPI;
import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventHandler;
import net.viper.status.module.Module; import net.viper.status.module.Module;
@@ -46,7 +59,7 @@ public class ForumBridgeModule implements Module, Listener {
public void onEnable(Plugin plugin) { public void onEnable(Plugin plugin) {
this.plugin = plugin; this.plugin = plugin;
loadConfig(plugin); loadConfig(plugin);
if (!enabled) { plugin.getLogger().info("ForumBridgeModule ist deaktiviert."); return; } if (!enabled) { StatusAPI.debugLog(plugin, "ForumBridgeModule ist deaktiviert."); return; }
storage = new ForumNotifStorage(plugin.getDataFolder(), plugin.getLogger()); storage = new ForumNotifStorage(plugin.getDataFolder(), plugin.getLogger());
storage.load(); storage.load();
@@ -60,12 +73,12 @@ public class ForumBridgeModule implements Module, Listener {
}, 10, 10, TimeUnit.MINUTES); }, 10, 10, TimeUnit.MINUTES);
plugin.getProxy().getScheduler().schedule(plugin, () -> storage.purgeOld(30), 1, 24, TimeUnit.HOURS); plugin.getProxy().getScheduler().schedule(plugin, () -> storage.purgeOld(30), 1, 24, TimeUnit.HOURS);
plugin.getLogger().info("ForumBridgeModule aktiviert."); plugin.getLogger().fine("ForumBridgeModule aktiviert.");
} }
@Override @Override
public void onDisable(Plugin plugin) { public void onDisable(Plugin plugin) {
if (storage != null) { storage.save(); plugin.getLogger().info("Forum-Benachrichtigungen gespeichert."); } if (storage != null) { storage.save(); StatusAPI.debugLog(plugin, "Forum-Benachrichtigungen gespeichert."); }
} }
private void loadConfig(Plugin plugin) { private void loadConfig(Plugin plugin) {

View File

@@ -1,18 +1,32 @@
package net.viper.status.modules.serverswitcher; package net.viper.status.modules.serverswitcher;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ClickEvent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.ComponentBuilder; import net.md_5.bungee.api.chat.ComponentBuilder;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.HoverEvent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.event.TabCompleteEvent; import net.md_5.bungee.api.event.TabCompleteEvent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.Command;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Listener;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import net.viper.status.StatusAPI;
import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventHandler;
import net.viper.status.module.Module; import net.viper.status.module.Module;
@@ -57,7 +71,7 @@ public class ServerSwitcherModule implements Module {
loadConfig(); loadConfig();
if (!enabled) { if (!enabled) {
plugin.getLogger().info("[ServerSwitcherModule] Deaktiviert."); StatusAPI.debugLog(plugin, "[ServerSwitcherModule] Deaktiviert.");
return; return;
} }
@@ -67,7 +81,7 @@ public class ServerSwitcherModule implements Module {
ProxyServer.getInstance().getPluginManager().registerListener(plugin, ProxyServer.getInstance().getPluginManager().registerListener(plugin,
new GoTabListener()); new GoTabListener());
plugin.getLogger().info("[ServerSwitcherModule] Aktiviert. Command: /" + commandName plugin.getLogger().fine("[ServerSwitcherModule] Aktiviert. Command: /" + commandName
+ " | Aliases: " + aliases + " | Permission: " + permission); + " | Aliases: " + aliases + " | Permission: " + permission);
} }

View File

@@ -18,29 +18,19 @@ import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import net.md_5.bungee.protocol.packet.PlayerListItemUpdate; import net.md_5.bungee.protocol.packet.PlayerListItemUpdate;
import net.viper.status.module.Module; import net.viper.status.module.Module;
import java.io.File; import java.io.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.*;
import java.util.Date; import java.util.concurrent.ConcurrentHashMap;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class TablistModule implements Module, Listener { public class TablistModule implements Module, Listener {
private static final String CONFIG_FILE = "tablist.properties"; private static final String CONFIG_FILE = "tablist.properties";
// Leerer Skin (grauer Kopf) fuer Platzhalter-Slots selber Skin wie TAB-Plugin // Leerer Skin (grauer Kopf) für Platzhalter-Slots
private static final net.md_5.bungee.protocol.data.Property[] EMPTY_SKIN = { private static final net.md_5.bungee.protocol.data.Property[] EMPTY_SKIN = {
new net.md_5.bungee.protocol.data.Property( new net.md_5.bungee.protocol.data.Property(
"textures", "textures",
@@ -49,16 +39,19 @@ public class TablistModule implements Module, Listener {
) )
}; };
private int rows = 20; // Grid rows ist IMMER 20 (Minecraft-Client-Layout: N Slots → ceil(N/20) Spalten à 20 Zeilen)
private int columns = 4; private static final int ROWS = 20;
private int total = rows * columns; private int rows = ROWS, columns = 3, total = 60, tabSizeMax = 60;
private int tabSizeMax = 80; private int configuredTabSize = 0; // 0 = auto-detect aus BungeeCord
private UUID[] fakeUuids; private UUID[] fakeUuids;
// ── Config ───────────────────────────────────────────────────────────────── // Skin-Cache (pro Spieler)
private final ConcurrentHashMap<UUID, net.md_5.bungee.protocol.data.Property[]> skinCache = new ConcurrentHashMap<>();
// Config
private boolean enabled = true; private boolean enabled = true;
private int updateInterval = 5; private int updateInterval = 5;
private String layoutMode = "compact";
private String headerLine1 = "&8&m" + rep('\u2501', 53); private String headerLine1 = "&8&m" + rep('\u2501', 53);
private String headerLine2 = " &6&lViper Network"; private String headerLine2 = " &6&lViper Network";
@@ -67,12 +60,6 @@ public class TablistModule implements Module, Listener {
private String footerLine2 = " &7Discord: &ediscord.viper-network.de &8| &7Shop: &eviper-network.de/shop"; private String footerLine2 = " &7Discord: &ediscord.viper-network.de &8| &7Shop: &eviper-network.de/shop";
private String footerLine3 = "&8&m" + rep('\u2501', 53); private String footerLine3 = "&8&m" + rep('\u2501', 53);
private String colorSrvHeader = "&6&l";
// Header/Footer Layout-Modus: "classic" oder "compact"
private String layoutMode = "classic";
// Compact-Layout Header/Footer
private String compactHeader1 = "&6&lViper Network &8• &7%online% Spieler online"; private String compactHeader1 = "&6&lViper Network &8• &7%online% Spieler online";
private String compactHeader2 = ""; private String compactHeader2 = "";
private String compactHeader3 = ""; private String compactHeader3 = "";
@@ -85,70 +72,61 @@ public class TablistModule implements Module, Listener {
private boolean compactFooter1Spacer = false; private boolean compactFooter1Spacer = false;
private boolean compactFooter4Spacer = false; private boolean compactFooter4Spacer = false;
// Konfigurierbare Info-Eintraege (Reihenfolge aus Config) private String colorSrvHeader = "&6&l";
private static class InfoEntry {
String label;
String type; // website, name, rank, server, world, time, teamspeak, custom
String value; // fuer custom und statische Werte
boolean enabled;
InfoEntry(String label, String type, String value, boolean enabled) {
this.label = label; this.type = type; this.value = value; this.enabled = enabled;
}
}
private List<InfoEntry> infoEntries = new ArrayList<>();
private String timeFormat = "HH:mm:ss / h:mm a"; private String timeFormat = "HH:mm:ss / h:mm a";
private String timeZone = "Europe/Berlin"; private String timeZone = "Europe/Berlin";
private SimpleDateFormat sdf; private SimpleDateFormat sdf;
private List<String> serverOrder = new ArrayList<>(); private List<String> serverOrder = new ArrayList<>();
private Set<String> hiddenServers = new HashSet<>(); private Set<String> hiddenServers = new HashSet<>();
// Rang-Reihenfolge fuer Spieler-Sortierung (hoechster Rang zuerst)
private List<String> rankOrder = new ArrayList<>(); private List<String> rankOrder = new ArrayList<>();
// ── State ────────────────────────────────────────────────────────────────── // Info-Spalte
private static class InfoEntry {
String label, type, value; boolean enabled;
InfoEntry(String l, String t, String v, boolean e) { label=l; type=t; value=v; enabled=e; }
}
private List<InfoEntry> infoEntries = new ArrayList<>();
// State
private Plugin plugin; private Plugin plugin;
private ScheduledTask updateTask; private ScheduledTask updateTask;
private Method sendPacketQueuedMethod; private Method sendPacketQueuedMethod;
// Spieler die bereits ADD_PLAYER erhalten haben nur noch UPDATE nötig
private final Set<UUID> initializedViewers = new HashSet<>();
// ══════════════════════════════════════════════════════════════════════════
@Override public String getName() { return "TablistModule"; } @Override public String getName() { return "TablistModule"; }
@Override @Override
public void onEnable(Plugin plugin) { public void onEnable(Plugin plugin) {
this.plugin = plugin; this.plugin = plugin;
plugin.getLogger().info("[TablistModule] Starte...");
ensureConfigExists(); ensureConfigExists();
loadConfig(); loadConfig();
plugin.getLogger().info("[TablistModule] Config geladen. Layout=" + layoutMode + " enabled=" + enabled);
if (!enabled) { plugin.getLogger().info("[TablistModule] Deaktiviert."); return; } if (!enabled) { plugin.getLogger().info("[TablistModule] Deaktiviert."); return; }
try {
initGridSize(); initGridSize();
} catch (Exception e) {
fakeUuids = new UUID[total]; plugin.getLogger().warning("[TablistModule] initGridSize Fehler: " + e.getMessage() + " nutze Fallback 3x20");
for (int i = 0; i < total; i++) int fbSize = configuredTabSize > 0 ? configuredTabSize : 60;
fakeUuids[i] = new UUID(0xFFFEDEAD00000000L, (long) i); tabSizeMax = fbSize; rows = ROWS; columns = Math.min(Math.max(3, fbSize / ROWS), 8); total = ROWS * columns;
}
initUuids();
try { try {
Class<?> uc = Class.forName("net.md_5.bungee.UserConnection"); Class<?> uc = Class.forName("net.md_5.bungee.UserConnection");
sendPacketQueuedMethod = uc.getMethod("sendPacketQueued", net.md_5.bungee.protocol.DefinedPacket.class); sendPacketQueuedMethod = uc.getMethod("sendPacketQueued", net.md_5.bungee.protocol.DefinedPacket.class);
sendPacketQueuedMethod.setAccessible(true); sendPacketQueuedMethod.setAccessible(true);
plugin.getLogger().info("[TablistModule] sendPacketQueued gefunden.");
} catch (Exception e) { } catch (Exception e) {
plugin.getLogger().severe("[TablistModule] sendPacketQueued nicht gefunden: " + e.getMessage()); plugin.getLogger().severe("[TablistModule] sendPacketQueued NICHT gefunden: " + e.getMessage());
return; return;
} }
ProxyServer.getInstance().getPluginManager().registerListener(plugin, this); ProxyServer.getInstance().getPluginManager().registerListener(plugin, this);
updateTask = ProxyServer.getInstance().getScheduler().schedule( updateTask = ProxyServer.getInstance().getScheduler().schedule(plugin, this::updateAll, 2L, Math.max(1, updateInterval), TimeUnit.SECONDS);
plugin, this::updateAll, 2L, Math.max(1, updateInterval), TimeUnit.SECONDS);
ProxyServer.getInstance().getScheduler().schedule(plugin, () -> { ProxyServer.getInstance().getScheduler().schedule(plugin, () -> {
List<String> all = new ArrayList<>(ProxyServer.getInstance().getServers().keySet()); plugin.getLogger().info("[TablistModule] Alle BungeeCord-Server: " + new ArrayList<>(ProxyServer.getInstance().getServers().keySet()));
plugin.getLogger().info("[TablistModule] Alle BungeeCord-Server: " + all); plugin.getLogger().info("[TablistModule] Tablist-Spalten: " + getServerOrder());
plugin.getLogger().info("[TablistModule] Tablist-Spalten (" + columns + "x" + rows + "): " + getServerOrder()); recalculateGrid();
}, 3L, TimeUnit.SECONDS); }, 3L, TimeUnit.SECONDS);
plugin.getLogger().info("[TablistModule] Aktiviert. Grid=" + columns + "x" + rows + " layout=" + layoutMode);
plugin.getLogger().info("[TablistModule] Aktiviert. Grid=" + columns + "x" + rows + ", Interval=" + updateInterval + "s");
} }
@Override @Override
@@ -161,73 +139,132 @@ public class TablistModule implements Module, Listener {
} }
private void initGridSize() { private void initGridSize() {
int tabSize = 80; int tabSize = 60;
try { try {
for (ListenerInfo li : ProxyServer.getInstance().getConfig().getListeners()) { for (ListenerInfo li : ProxyServer.getInstance().getConfig().getListeners()) {
try { try { Object v = li.getClass().getMethod("getTabSize").invoke(li);
Object val = li.getClass().getMethod("getTabSize").invoke(li); if (v instanceof Number && ((Number)v).intValue() > 0) { tabSize = ((Number)v).intValue(); break; }
if (val instanceof Number && ((Number) val).intValue() > 0) {
tabSize = ((Number) val).intValue(); break;
}
} catch (Exception ignored) {} } catch (Exception ignored) {}
} }
} catch (Exception ignored) {} } catch (Exception ignored) {}
if (configuredTabSize > 0) tabSize = configuredTabSize; // manuell gesetzt in tablist.properties
tabSizeMax = tabSize; tabSizeMax = tabSize;
rows = 20; rows = ROWS; // immer 20 Minecraft-Client-Pflicht
boolean hasInfo = !"compact".equalsIgnoreCase(layoutMode);
int serverCount = getServerOrder().size(); int serverCount = getServerOrder().size();
// +1 fuer Info-Spalte int needed = (hasInfo ? 1 : 0) + Math.max(1, serverCount);
int needed = 1 + serverCount; // Spalten = benötigte Spalten, aber max was tab-size erlaubt (tab-size/20)
columns = Math.max(2, Math.min(needed, tabSize / rows)); columns = Math.max(hasInfo ? 2 : 1, Math.min(needed, tabSize / ROWS));
total = rows * columns; total = ROWS * columns;
plugin.getLogger().info("[TablistModule] tab_size=" + tabSize + " -> " + columns + "x" + rows + "=" + total); if (needed > tabSize / ROWS) {
plugin.getLogger().warning("[TablistModule] Nicht alle Server passen in die Tablist! "
+ "Erhöhe tab-size in der BungeeCord config.yml auf mindestens " + (needed * ROWS)
+ " (aktuell: " + tabSize + ")");
}
plugin.getLogger().info("[TablistModule] tab_size=" + tabSize + " -> " + columns + "x" + ROWS + "=" + total + " (" + serverCount + " Server)");
}
private void initUuids() {
fakeUuids = new UUID[total];
for (int i = 0; i < total; i++) fakeUuids[i] = new UUID(0xFFFEDEAD00000000L, (long) i);
} }
// ── Events ───────────────────────────────────────────────────────────────── // ── Events ─────────────────────────────────────────────────────────────────
@EventHandler public void onLogin(PostLoginEvent e) { @EventHandler
public void onLogin(PostLoginEvent e) {
if (!enabled) return; if (!enabled) return;
ProxyServer.getInstance().getScheduler().schedule(plugin, ProxiedPlayer p = e.getPlayer();
() -> updateTablist(e.getPlayer()), 3L, TimeUnit.SECONDS); net.md_5.bungee.protocol.data.Property[] skin = fetchSkin(p);
if (skin != null && skin.length > 0) skinCache.put(p.getUniqueId(), skin);
ProxyServer.getInstance().getScheduler().schedule(plugin, () -> {
updateTablist(p);
// Nach 2s nochmals für alle damit der neue Spieler mit Kopf erscheint
ProxyServer.getInstance().getScheduler().schedule(plugin, this::updateAll, 2L, TimeUnit.SECONDS);
}, 2L, TimeUnit.SECONDS);
} }
@EventHandler public void onSwitch(ServerSwitchEvent e) { @EventHandler
public void onSwitch(ServerSwitchEvent e) {
if (!enabled) return; if (!enabled) return;
initializedViewers.remove(e.getPlayer().getUniqueId()); ProxiedPlayer switched = e.getPlayer();
ProxyServer.getInstance().getScheduler().schedule(plugin,
() -> updateTablist(e.getPlayer()), 1L, TimeUnit.SECONDS); // Skin sofort cachen (noch auf dem alten Server, LoginProfile noch verfügbar)
net.md_5.bungee.protocol.data.Property[] skin = fetchSkin(switched);
if (skin != null && skin.length > 0) skinCache.put(switched.getUniqueId(), skin);
// Nach 1s: alle Fake-Slots bei allen Viewern entfernen → erzwingt frisches ADD_PLAYER mit neuem Skin
ProxyServer.getInstance().getScheduler().schedule(plugin, () -> {
// Skin nochmals versuchen (jetzt auf neuem Server)
net.md_5.bungee.protocol.data.Property[] freshSkin = fetchSkin(switched);
if (freshSkin != null && freshSkin.length > 0) skinCache.put(switched.getUniqueId(), freshSkin);
// Alle Slots bei allen Viewern entfernen
for (ProxiedPlayer viewer : ProxyServer.getInstance().getPlayers()) {
try { removeFakeSlots(viewer); } catch (Exception ignored) {}
} }
@EventHandler public void onDisconnect(PlayerDisconnectEvent e) { // Sofort neu aufbauen (kein weiterer Delay nötig da removeFakeSlots synchron ist)
updateAll();
// Nochmal nach 2s als Sicherheit
ProxyServer.getInstance().getScheduler().schedule(plugin, () -> {
net.md_5.bungee.protocol.data.Property[] s2 = fetchSkin(switched);
if (s2 != null && s2.length > 0) skinCache.put(switched.getUniqueId(), s2);
for (ProxiedPlayer viewer : ProxyServer.getInstance().getPlayers()) {
try { removeFakeSlots(viewer); } catch (Exception ignored) {}
}
updateAll();
}, 2L, TimeUnit.SECONDS);
}, 1L, TimeUnit.SECONDS);
}
@EventHandler
public void onDisconnect(PlayerDisconnectEvent e) {
if (!enabled) return; if (!enabled) return;
initializedViewers.remove(e.getPlayer().getUniqueId()); skinCache.remove(e.getPlayer().getUniqueId());
ProxyServer.getInstance().getScheduler().schedule(plugin, this::updateAll, 1L, TimeUnit.SECONDS); // Erst alle Fake-Slots entfernen, dann nach kurzer Pause neu aufbauen
// So verschwindet der Kopf des Spielers zuverlässig
ProxyServer.getInstance().getScheduler().schedule(plugin, () -> {
for (ProxiedPlayer viewer : ProxyServer.getInstance().getPlayers()) {
try { removeFakeSlots(viewer); } catch (Exception ignored) {}
}
ProxyServer.getInstance().getScheduler().schedule(plugin,
this::updateAll, 1L, TimeUnit.SECONDS);
}, 1L, TimeUnit.SECONDS);
} }
// ── Core ─────────────────────────────────────────────────────────────────── // ── Core ───────────────────────────────────────────────────────────────────
private void updateAll() { private void updateAll() {
recalculateGrid(); recalculateGrid();
// Fehlende Skins nachladen
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
if (!skinCache.containsKey(p.getUniqueId())) {
net.md_5.bungee.protocol.data.Property[] skin = fetchSkin(p);
if (skin != null && skin.length > 0) skinCache.put(p.getUniqueId(), skin);
}
}
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) updateTablist(p); for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) updateTablist(p);
} }
private void recalculateGrid() { private void recalculateGrid() {
boolean hasInfo = !"compact".equalsIgnoreCase(layoutMode);
int serverCount = getServerOrder().size(); int serverCount = getServerOrder().size();
// Im compact-Modus keine Info-Spalte, alle Spalten fuer Server int needed = (hasInfo ? 1 : 0) + Math.max(1, serverCount);
boolean hasInfoCol = !"compact".equalsIgnoreCase(layoutMode); // Spalten = benötigte Spalten, aber max was tab-size erlaubt (tab-size/20)
int needed = (hasInfoCol ? 1 : 0) + serverCount; int newColumns = Math.max(hasInfo ? 2 : 1, Math.min(needed, tabSizeMax / ROWS));
int newColumns = Math.max(hasInfoCol ? 2 : 1, Math.min(needed, tabSizeMax / rows)); int newTotal = ROWS * newColumns;
int newTotal = rows * newColumns;
if (newColumns == columns && newTotal == total) return; if (newColumns == columns && newTotal == total) return;
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) { for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
try { removeFakeSlots(p); } catch (Exception ignored) {} try { removeFakeSlots(p); } catch (Exception ignored) {}
} }
initializedViewers.clear(); rows = ROWS;
columns = newColumns; columns = newColumns;
total = newTotal; total = newTotal;
fakeUuids = new UUID[total]; initUuids();
for (int i = 0; i < total; i++) plugin.getLogger().info("[TablistModule] Grid: " + columns + "x" + rows + "=" + total + " (" + serverCount + " Server)");
fakeUuids[i] = new UUID(0xFFFEDEAD00000000L, (long) i);
plugin.getLogger().info("[TablistModule] Grid: " + columns + "x" + rows + "=" + total + " (" + serverCount + " Server, layout=" + layoutMode + ")");
} }
private void updateTablist(ProxiedPlayer viewer) { private void updateTablist(ProxiedPlayer viewer) {
@@ -248,17 +285,17 @@ public class TablistModule implements Module, Listener {
header = c(headerLine1) + "\n" + c(headerLine2) + "\n" + c(headerLine3); header = c(headerLine1) + "\n" + c(headerLine2) + "\n" + c(headerLine3);
footer = c(footerLine1) + "\n" + c(footerLine2) + "\n" + c(footerLine3); footer = c(footerLine1) + "\n" + c(footerLine2) + "\n" + c(footerLine3);
} }
viewer.setTabHeader(new TextComponent(header), new TextComponent(footer)); viewer.setTabHeader(new TextComponent(header), new TextComponent(footer));
hideRealPlayers(viewer); hideRealPlayers(viewer);
sendSlots(viewer, buildItems(viewer)); sendSlots(viewer, buildItems(viewer));
} catch (Exception ex) { } catch (Exception ex) {
plugin.getLogger().warning("[TablistModule] Fehler fuer " + viewer.getName() + ": " + ex.getMessage()); plugin.getLogger().warning("[TablistModule] " + viewer.getName() + ": " + ex.getMessage());
} }
} }
private String buildCompactHeader(ProxiedPlayer viewer, String srv, String world, // ── Header / Footer ────────────────────────────────────────────────────────
String rank, String time, String balance, int online) {
private String buildCompactHeader(ProxiedPlayer viewer, String srv, String world, String rank, String time, String balance, int online) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
appendLine(sb, compactHeader1, false, viewer, srv, world, rank, time, balance, online); appendLine(sb, compactHeader1, false, viewer, srv, world, rank, time, balance, online);
appendLine(sb, compactHeader2, compactHeader2Spacer, viewer, srv, world, rank, time, balance, online); appendLine(sb, compactHeader2, compactHeader2Spacer, viewer, srv, world, rank, time, balance, online);
@@ -266,23 +303,20 @@ public class TablistModule implements Module, Listener {
return sb.toString(); return sb.toString();
} }
private String buildCompactFooter(ProxiedPlayer viewer, String srv, String world, private String buildCompactFooter(ProxiedPlayer viewer, String srv, String world, String rank, String time, String balance, int online) {
String rank, String time, String balance, int online) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
appendLine(sb, compactFooter1, compactFooter1Spacer, viewer, srv, world, rank, time, balance, online); appendLine(sb, compactFooter1, compactFooter1Spacer, viewer, srv, world, rank, time, balance, online);
// Automatische Server-Übersicht
List<String> servers = getServerOrder(); List<String> servers = getServerOrder();
if (!servers.isEmpty()) { if (!servers.isEmpty()) {
StringBuilder serverLine = new StringBuilder(); StringBuilder sLine = new StringBuilder();
for (String sName : servers) { for (String sName : servers) {
ServerInfo info = ProxyServer.getInstance().getServerInfo(sName); ServerInfo si = ProxyServer.getInstance().getServerInfo(sName);
int count = info != null ? info.getPlayers().size() : 0; int cnt = si != null ? si.getPlayers().size() : 0;
if (serverLine.length() > 0) serverLine.append(" &8| "); if (sLine.length() > 0) sLine.append(" &8| ");
serverLine.append(c(colorSrvHeader)).append(capitalize(sName)) sLine.append(c(colorSrvHeader)).append(capitalize(sName)).append(" &8\u25cf &7").append(cnt);
.append(" &8\u25cf &7").append(count);
} }
if (sb.length() > 0) sb.append("\n"); if (sb.length() > 0) sb.append("\n");
sb.append(c(serverLine.toString())); sb.append(c(sLine.toString()));
} }
appendLine(sb, compactFooter2, false, viewer, srv, world, rank, time, balance, online); appendLine(sb, compactFooter2, false, viewer, srv, world, rank, time, balance, online);
appendLine(sb, compactFooter3, false, viewer, srv, world, rank, time, balance, online); appendLine(sb, compactFooter3, false, viewer, srv, world, rank, time, balance, online);
@@ -290,53 +324,34 @@ public class TablistModule implements Module, Listener {
return sb.toString(); return sb.toString();
} }
/** private void appendLine(StringBuilder sb, String line, boolean spacer, ProxiedPlayer viewer, String srv, String world, String rank, String time, String balance, int online) {
* Hängt eine Zeile an: boolean empty = line == null || line.trim().isEmpty();
* - spacer=true + leer → fügt eine leere Abstandszeile ein if (empty && !spacer) return;
* - spacer=false + leer → Zeile wird komplett übersprungen
* - Text vorhanden → wird immer angezeigt
*/
private void appendLine(StringBuilder sb, String line, boolean spacer,
ProxiedPlayer viewer, String srv, String world, String rank,
String time, String balance, int online) {
boolean isEmpty = line == null || line.trim().isEmpty();
if (isEmpty && !spacer) return; // überspringen
if (sb.length() > 0) sb.append("\n"); if (sb.length() > 0) sb.append("\n");
if (isEmpty) { sb.append(empty ? " " : c(replacePlaceholders(line, viewer, srv, world, rank, time, balance, online)));
sb.append(" "); // Abstandszeile
} else {
sb.append(c(replacePlaceholders(line, viewer, srv, world, rank, time, balance, online)));
}
} }
// ── Items ──────────────────────────────────────────────────────────────────
private Item[] buildItems(ProxiedPlayer viewer) { private Item[] buildItems(ProxiedPlayer viewer) {
String[] texts = new String[total]; String[] texts = new String[total];
net.md_5.bungee.protocol.data.Property[][] skins = new net.md_5.bungee.protocol.data.Property[total][]; net.md_5.bungee.protocol.data.Property[][] skins = new net.md_5.bungee.protocol.data.Property[total][];
int[] pings = new int[total]; int[] pings = new int[total];
for (int i = 0; i < total; i++) { for (int i = 0; i < total; i++) { texts[i] = " "; skins[i] = EMPTY_SKIN; pings[i] = 0; }
texts[i] = " ";
skins[i] = EMPTY_SKIN;
pings[i] = 0;
}
// ── Spalte 0: Info (nur im classic Layout) ─────────────────────────── boolean compact = "compact".equalsIgnoreCase(layoutMode);
// Info-Spalte (nur classic)
if (!compact) {
int base = 0, row = 0; int base = 0, row = 0;
String srv = viewer.getServer() != null ? capitalize(viewer.getServer().getInfo().getName()) : "\u2014"; String srv = viewer.getServer() != null ? capitalize(viewer.getServer().getInfo().getName()) : "\u2014";
String world = net.viper.status.StatusAPI.playerWorlds.getOrDefault(viewer.getUniqueId(), "world"); String world = net.viper.status.StatusAPI.playerWorlds.getOrDefault(viewer.getUniqueId(), "world");
String rank = getRank(viewer); String rank = getRank(viewer); String time = sdf.format(new Date());
String time = sdf.format(new Date()); String balance = getBalance(viewer); int online = ProxyServer.getInstance().getOnlineCount();
String balance = getBalance(viewer);
int online = ProxyServer.getInstance().getOnlineCount();
boolean compactMode = "compact".equalsIgnoreCase(layoutMode);
if (!compactMode) {
for (InfoEntry entry : infoEntries) { for (InfoEntry entry : infoEntries) {
if (!entry.enabled) continue; if (!entry.enabled || row + 1 >= rows) continue;
if (row + 1 >= rows) break; if (entry.label != null && !entry.label.isEmpty())
if (entry.label != null && !entry.label.isEmpty()) {
row = set(texts, base, row, c(replacePlaceholders(entry.label, viewer, srv, world, rank, time, balance, online))); row = set(texts, base, row, c(replacePlaceholders(entry.label, viewer, srv, world, rank, time, balance, online)));
}
String val; String val;
switch (entry.type) { switch (entry.type) {
case "name": val = "&f" + viewer.getName(); break; case "name": val = "&f" + viewer.getName(); break;
@@ -353,34 +368,28 @@ public class TablistModule implements Module, Listener {
} }
} }
// ── Server-Spieler Spalten ──────────────────────────────────────────── // Server-Spalten
List<String> servers = getServerOrder(); List<String> servers = getServerOrder();
int startCol = compactMode ? 0 : 1; int startCol = compact ? 0 : 1;
for (int col = startCol; col < columns && (col - startCol) < servers.size(); col++) { for (int col = startCol; col < columns && (col - startCol) < servers.size(); col++) {
base = col * rows; int base = col * rows, row = 0;
row = 0;
String sName = servers.get(col - startCol); String sName = servers.get(col - startCol);
row = set(texts, base, row, c(colorSrvHeader + capitalize(sName))); row = set(texts, base, row, c(colorSrvHeader + capitalize(sName)));
ServerInfo info = ProxyServer.getInstance().getServerInfo(sName); ServerInfo si = ProxyServer.getInstance().getServerInfo(sName);
if (info != null) { if (si != null) {
// Spieler nach Rang-Reihenfolge sortieren for (ProxiedPlayer p : sortPlayersByRank(new ArrayList<>(si.getPlayers()))) {
List<ProxiedPlayer> sorted = sortPlayersByRank(new ArrayList<>(info.getPlayers()));
for (ProxiedPlayer p : sorted) {
if (row >= rows) break; if (row >= rows) break;
String prefix = getLuckPermsPrefix(p); String prefix = getLuckPermsPrefix(p);
String display = prefix.isEmpty() set(texts, base, row, prefix.isEmpty() ? c("&7" + p.getName()) : c(prefix + "&r " + p.getName()));
? c("&7" + p.getName()) // Skin aus Cache immer aktuell
: c(prefix + "&r " + p.getName()); net.md_5.bungee.protocol.data.Property[] skin = skinCache.get(p.getUniqueId());
set(texts, base, row, display); skins[base + row] = (skin != null && skin.length > 0) ? skin : EMPTY_SKIN;
skins[base + row] = getPlayerSkin(p); pings[base + row] = p.getPing() < 0 ? 1 : p.getPing();
int ping = p.getPing();
pings[base + row] = ping < 0 ? 1 : ping;
row++; row++;
} }
} }
} }
// Alle Slots listed=true Layout bleibt erhalten
Item[] items = new Item[total]; Item[] items = new Item[total];
for (int i = 0; i < total; i++) { for (int i = 0; i < total; i++) {
Item item = new Item(); Item item = new Item();
@@ -402,58 +411,33 @@ public class TablistModule implements Module, Listener {
private void hideRealPlayers(ProxiedPlayer viewer) { private void hideRealPlayers(ProxiedPlayer viewer) {
if (sendPacketQueuedMethod == null) return; if (sendPacketQueuedMethod == null) return;
try { try {
java.util.Collection<ProxiedPlayer> online = ProxyServer.getInstance().getPlayers(); Collection<ProxiedPlayer> online = ProxyServer.getInstance().getPlayers();
if (online.isEmpty()) return; if (online.isEmpty()) return;
PlayerListItemUpdate pkt = new PlayerListItemUpdate(); PlayerListItemUpdate pkt = new PlayerListItemUpdate();
pkt.setActions(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LISTED)); pkt.setActions(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LISTED));
Item[] items = new Item[online.size()]; Item[] items = new Item[online.size()];
int idx = 0; int idx = 0;
for (ProxiedPlayer p : online) { for (ProxiedPlayer p : online) {
Item item = new Item(); Item it = new Item(); it.setUuid(p.getUniqueId()); it.setListed(false); items[idx++] = it;
item.setUuid(p.getUniqueId());
item.setListed(false);
items[idx++] = item;
} }
pkt.setItems(items); pkt.setItems(items);
sendPacketQueuedMethod.invoke(viewer, pkt); sendPacketQueuedMethod.invoke(viewer, pkt);
} catch (Exception e) { } catch (Exception e) { plugin.getLogger().warning("[TablistModule] hideRealPlayers: " + e.getMessage()); }
plugin.getLogger().warning("[TablistModule] hideRealPlayers: " + e.getMessage());
}
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void sendSlots(ProxiedPlayer viewer, Item[] items) { private void sendSlots(ProxiedPlayer viewer, Item[] items) {
if (sendPacketQueuedMethod == null) return; if (sendPacketQueuedMethod == null) return;
// Immer vollständiges ADD_PLAYER einfach und zuverlässig
boolean isNew = initializedViewers.add(viewer.getUniqueId()); PlayerListItemUpdate pkt = new PlayerListItemUpdate();
pkt.setActions(EnumSet.of(
if (isNew) {
// Erstes Mal: ADD_PLAYER + UPDATE_DISPLAY_NAME + UPDATE_LISTED
PlayerListItemUpdate addPkt = new PlayerListItemUpdate();
addPkt.setActions(EnumSet.of(
PlayerListItemUpdate.Action.ADD_PLAYER, PlayerListItemUpdate.Action.ADD_PLAYER,
PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME, PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME,
PlayerListItemUpdate.Action.UPDATE_LISTED)); PlayerListItemUpdate.Action.UPDATE_LISTED,
addPkt.setItems(items); PlayerListItemUpdate.Action.UPDATE_LATENCY));
try { sendPacketQueuedMethod.invoke(viewer, addPkt); } pkt.setItems(items);
catch (Exception e) { plugin.getLogger().warning("[TablistModule] ADD_PLAYER: " + e.getMessage()); return; } try { sendPacketQueuedMethod.invoke(viewer, pkt); }
} else { catch (Exception e) { plugin.getLogger().warning("[TablistModule] sendSlots: " + e.getMessage()); }
// Folgeupdate: nur DisplayName + Listed aktualisieren (kein Flackern)
PlayerListItemUpdate updPkt = new PlayerListItemUpdate();
updPkt.setActions(EnumSet.of(
PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME,
PlayerListItemUpdate.Action.UPDATE_LISTED));
updPkt.setItems(items);
try { sendPacketQueuedMethod.invoke(viewer, updPkt); }
catch (Exception e) { plugin.getLogger().warning("[TablistModule] UPDATE_DISPLAY_NAME: " + e.getMessage()); return; }
}
// Ping immer separat senden
PlayerListItemUpdate pingPkt = new PlayerListItemUpdate();
pingPkt.setActions(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LATENCY));
pingPkt.setItems(items);
try { sendPacketQueuedMethod.invoke(viewer, pingPkt); }
catch (Exception e) { plugin.getLogger().warning("[TablistModule] UPDATE_LATENCY: " + e.getMessage()); }
} }
private void removeFakeSlots(ProxiedPlayer viewer) { private void removeFakeSlots(ProxiedPlayer viewer) {
@@ -477,25 +461,25 @@ public class TablistModule implements Module, Listener {
// ── Helpers ──────────────────────────────────────────────────────────────── // ── Helpers ────────────────────────────────────────────────────────────────
private int set(String[] arr, int base, int row, String text) { private net.md_5.bungee.protocol.data.Property[] fetchSkin(ProxiedPlayer player) {
if (base + row < total) arr[base + row] = text == null ? " " : text;
return row + 1;
}
private net.md_5.bungee.protocol.data.Property[] getPlayerSkin(ProxiedPlayer player) {
try { try {
Object pending = player.getPendingConnection(); Object pending = player.getPendingConnection();
net.md_5.bungee.connection.LoginResult profile = net.md_5.bungee.connection.LoginResult profile =
(net.md_5.bungee.connection.LoginResult) (net.md_5.bungee.connection.LoginResult) pending.getClass().getMethod("getLoginProfile").invoke(pending);
pending.getClass().getMethod("getLoginProfile").invoke(pending); if (profile != null && profile.getProperties() != null && profile.getProperties().length > 0)
if (profile != null && profile.getProperties() != null) return profile.getProperties(); return profile.getProperties();
} catch (Exception ignored) {} } catch (Exception ignored) {}
return new net.md_5.bungee.protocol.data.Property[0]; return new net.md_5.bungee.protocol.data.Property[0];
} }
private List<String> getServerOrder() { private List<String> getServerOrder() {
if (!serverOrder.isEmpty()) return new ArrayList<>(serverOrder); List<String> list;
List<String> list = new ArrayList<>(); if (!serverOrder.isEmpty()) {
list = new ArrayList<>(serverOrder);
// Versteckte Server auch aus manueller Liste entfernen
list.removeIf(s -> hiddenServers.contains(s.toLowerCase()));
} else {
list = new ArrayList<>();
final String[] lobbyKey = {null}; final String[] lobbyKey = {null};
for (String key : ProxyServer.getInstance().getServers().keySet()) for (String key : ProxyServer.getInstance().getServers().keySet())
if (key.equalsIgnoreCase("lobby")) { lobbyKey[0] = key; break; } if (key.equalsIgnoreCase("lobby")) { lobbyKey[0] = key; break; }
@@ -503,11 +487,33 @@ public class TablistModule implements Module, Listener {
ProxyServer.getInstance().getServers().keySet().stream() ProxyServer.getInstance().getServers().keySet().stream()
.filter(s -> lobbyKey[0] == null || !s.equalsIgnoreCase(lobbyKey[0])) .filter(s -> lobbyKey[0] == null || !s.equalsIgnoreCase(lobbyKey[0]))
.filter(s -> !hiddenServers.contains(s.toLowerCase())) .filter(s -> !hiddenServers.contains(s.toLowerCase()))
.sorted(String.CASE_INSENSITIVE_ORDER) .sorted(String.CASE_INSENSITIVE_ORDER).forEach(list::add);
.forEach(list::add); }
return list; return list;
} }
private List<ProxiedPlayer> sortPlayersByRank(List<ProxiedPlayer> players) {
if (rankOrder.isEmpty()) return players;
players.sort((a, b) -> { int ia = getRankIndex(a), ib = getRankIndex(b);
return ia != ib ? Integer.compare(ia, ib) : a.getName().compareToIgnoreCase(b.getName()); });
return players;
}
private int getRankIndex(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) {
Object pg = usr.getClass().getMethod("getPrimaryGroup").invoke(usr);
if (pg != null) { String g = pg.toString().toLowerCase();
for (int i = 0; i < rankOrder.size(); i++) if (rankOrder.get(i).equalsIgnoreCase(g)) return i; }
}
} catch (Exception ignored) {}
return rankOrder.size();
}
private String getRank(ProxiedPlayer player) { private String getRank(ProxiedPlayer player) {
try { try {
Class<?> prov = Class.forName("net.luckperms.api.LuckPermsProvider"); Class<?> prov = Class.forName("net.luckperms.api.LuckPermsProvider");
@@ -546,81 +552,67 @@ public class TablistModule implements Module, Listener {
return ""; return "";
} }
/**
* Sortiert Spieler nach der konfigurierten Rang-Reihenfolge.
* Spieler mit hohem Rang (Index 0 in rankOrder) kommen zuerst.
* Spieler mit unbekanntem Rang kommen ans Ende, alphabetisch sortiert.
*/
private List<ProxiedPlayer> sortPlayersByRank(List<ProxiedPlayer> players) {
if (rankOrder.isEmpty()) return players;
players.sort((a, b) -> {
int idxA = getRankIndex(a);
int idxB = getRankIndex(b);
if (idxA != idxB) return Integer.compare(idxA, idxB);
return a.getName().compareToIgnoreCase(b.getName());
});
return players;
}
/** Gibt den Index des Spielers in der rankOrder-Liste zurück (niedrig = höher). */
private int getRankIndex(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) {
Object pg = usr.getClass().getMethod("getPrimaryGroup").invoke(usr);
if (pg != null) {
String group = pg.toString().toLowerCase();
for (int i = 0; i < rankOrder.size(); i++) {
if (rankOrder.get(i).equalsIgnoreCase(group)) return i;
}
}
}
} catch (Exception ignored) {}
return rankOrder.size(); // unbekannter Rang ans Ende
}
private static String fakeName(int i) { return String.format("~vt%03d", i); }
private static String c(String s) { return ChatColor.translateAlternateColorCodes('&', s == null ? "" : s); }
private static String capitalize(String s) { return s == null || s.isEmpty() ? s : Character.toUpperCase(s.charAt(0)) + s.substring(1); }
private static String rep(char ch, int n) { StringBuilder sb = new StringBuilder(n); for (int i=0;i<n;i++) sb.append(ch); return sb.toString(); }
private int parseInt(String s, int fb) { try { return Integer.parseInt(s == null ? "" : s.trim()); } catch (Exception e) { return fb; } }
/**
* Ersetzt alle Platzhalter in einem Text:
* %player% %rank% %server% %world% %time% %balance% %ping% %online%
*/
private String replacePlaceholders(String text, ProxiedPlayer viewer,
String srv, String world, String rank,
String time, String balance, int online) {
if (text == null) return "";
return text
.replace("%player%", viewer.getName())
.replace("%rank%", rank)
.replace("%server%", srv)
.replace("%world%", world)
.replace("%time%", time)
.replace("%balance%", balance)
.replace("%ping%", String.valueOf(viewer.getPing()))
.replace("%online%", String.valueOf(online));
}
/** Liest den Kontostand aus der StatusAPI-Economy-Map (wird von StatusAPIBridge gepusht). */
private String getBalance(ProxiedPlayer player) { private String getBalance(ProxiedPlayer player) {
try { try {
java.util.Map<?, ?> balances = (java.util.Map<?, ?>) net.viper.status.StatusAPI.class Map<?,?> balances = (Map<?,?>) net.viper.status.StatusAPI.class.getField("playerBalances").get(null);
.getField("playerBalances").get(null);
Object val = balances.get(player.getUniqueId()); Object val = balances.get(player.getUniqueId());
if (val != null) { if (val != null) return String.format("%,.2f", ((Number) val).doubleValue());
double d = ((Number) val).doubleValue();
return String.format("%,.2f", d);
}
} catch (Exception ignored) {} } catch (Exception ignored) {}
return "0.00"; return "0.00";
} }
private String replacePlaceholders(String text, ProxiedPlayer viewer, String srv, String world, String rank, String time, String balance, int online) {
if (text == null) return "";
return text.replace("%player%", viewer.getName()).replace("%rank%", rank)
.replace("%server%", srv).replace("%world%", world).replace("%time%", time)
.replace("%balance%", balance).replace("%ping%", String.valueOf(viewer.getPing()))
.replace("%online%", String.valueOf(online));
}
private int set(String[] arr, int base, int row, String text) {
if (base + row < total) arr[base + row] = text == null ? " " : text; return row + 1;
}
private static String c(String s) {
if (s == null) return "";
s = replaceHexColors(s);
return ChatColor.translateAlternateColorCodes('&', s);
}
private static String replaceHexColors(String text) {
if (text == null || (!text.contains("&#") && !text.contains("{#"))) return text;
StringBuilder sb = new StringBuilder();
int i = 0;
while (i < text.length()) {
if (i + 7 <= text.length() && text.charAt(i) == '&' && text.charAt(i+1) == '#') {
String hex = text.substring(i+2, i+8);
if (hex.matches("[0-9a-fA-F]{6}")) {
sb.append('\u00A7').append('x');
for (char ch : hex.toCharArray()) sb.append('\u00A7').append(ch);
i += 8; continue;
}
}
if (i + 8 < text.length() && text.charAt(i) == '{' && text.charAt(i+1) == '#') {
int end = text.indexOf('}', i+2);
if (end == i+8) {
String hex = text.substring(i+2, i+8);
if (hex.matches("[0-9a-fA-F]{6}")) {
sb.append('\u00A7').append('x');
for (char ch : hex.toCharArray()) sb.append('\u00A7').append(ch);
i += 9; continue;
}
}
}
sb.append(text.charAt(i)); i++;
}
return sb.toString();
}
private static String fakeName(int i) { return String.format("~vt%03d", i); }
private static String capitalize(String s){ return s==null||s.isEmpty()?s:Character.toUpperCase(s.charAt(0))+s.substring(1); }
private static String rep(char ch, int n) { StringBuilder sb=new StringBuilder(n); for(int i=0;i<n;i++) sb.append(ch); return sb.toString(); }
private int parseInt(String s, int fb) { try{return Integer.parseInt(s==null?"":s.trim());}catch(Exception e){return fb;} }
// ── Config ───────────────────────────────────────────────────────────────── // ── Config ─────────────────────────────────────────────────────────────────
private void ensureConfigExists() { private void ensureConfigExists() {
@@ -631,16 +623,13 @@ public class TablistModule implements Module, Listener {
String content = String content =
"# TablistModule Konfiguration\n" + "# TablistModule Konfiguration\n" +
"tablist.enabled=true\n" + "tablist.enabled=true\n" +
"tablist.update_interval=5\n\n" + "tablist.tab_size=160\n" +
"# Layout-Modus: classic (Trennlinien + Info-Spalte links) oder compact (wie SecretCraft)\n" + "tablist.update_interval=5\n" +
"tablist.layout=classic\n\n" + "# Layout-Modus: classic oder compact\n" +
"# Server-Reihenfolge (leer = Lobby zuerst, dann alphabetisch)\n" + "tablist.layout=compact\n" +
"tablist.server_order=\n\n" + "tablist.server_order=\n" +
"# Server die NICHT angezeigt werden (kommagetrennt, leer = alle anzeigen)\n" + "tablist.hidden_servers=\n" +
"tablist.hidden_servers=\n\n" +
"# Rang-Reihenfolge fuer Spieler-Sortierung (hoechster Rang zuerst, LuckPerms Gruppenname)\n" +
"tablist.rank_order=owner,mod,primo,vip,scout,bewohner\n\n" + "tablist.rank_order=owner,mod,primo,vip,scout,bewohner\n\n" +
"# ── Classic Layout ──────────────────────────────────────────────────\n" +
"tablist.header.line1=&8&m" + sep + "\n" + "tablist.header.line1=&8&m" + sep + "\n" +
"tablist.header.line2= &6&lViper Network\n" + "tablist.header.line2= &6&lViper Network\n" +
"tablist.header.line3=&8&m" + sep + "\n\n" + "tablist.header.line3=&8&m" + sep + "\n\n" +
@@ -649,9 +638,9 @@ public class TablistModule implements Module, Listener {
"tablist.footer.line3=&8&m" + sep + "\n\n" + "tablist.footer.line3=&8&m" + sep + "\n\n" +
"# ── Compact Layout ──────────────────────────────────────────────────\n" + "# ── Compact Layout ──────────────────────────────────────────────────\n" +
"# Platzhalter: %player% %rank% %server% %world% %time% %balance% %ping% %online%\n" + "# Platzhalter: %player% %rank% %server% %world% %time% %balance% %ping% %online%\n" +
"# spacer=true: leere Zeile = sichtbarer Abstand | spacer=false: leere Zeile = wird uebersprungen\n" + "# spacer=true: leere Zeile = Abstand | spacer=false: leere Zeile = überspringen\n" +
"tablist.compact.header.line1=&6&lViper Network &8• &2Hallo, &a%player%&7! &6Schön dass du da bist!\n" + "tablist.compact.header.line1=&6&lViper Network &8• &2Hallo, &a%player%&7!\n" +
"tablist.compact.header.line2=&dCitybuild &8• &aSurvival &8• &eMinigames &3 Für jeden etwas dabei!\n" + "tablist.compact.header.line2=&dCitybuild &8• &aSurvival &8• &eMinigames\n" +
"tablist.compact.header.line2.spacer=false\n" + "tablist.compact.header.line2.spacer=false\n" +
"tablist.compact.header.line3=\n" + "tablist.compact.header.line3=\n" +
"tablist.compact.header.line3.spacer=false\n\n" + "tablist.compact.header.line3.spacer=false\n\n" +
@@ -666,8 +655,7 @@ public class TablistModule implements Module, Listener {
"tablist.color.server_header=&6&l\n" + "tablist.color.server_header=&6&l\n" +
"tablist.time_format=HH:mm:ss / h:mm a\n" + "tablist.time_format=HH:mm:ss / h:mm a\n" +
"tablist.timezone=Europe/Berlin\n\n" + "tablist.timezone=Europe/Berlin\n\n" +
"# ── Info-Spalte (nur classic Layout) ────────────────────────────────\n" + "# ── Info-Spalte (nur classic) ────────────────────────────────────────\n" +
"# Platzhalter auch hier verfuegbar: %player% %balance% %ping% %online% usw.\n" +
"tablist.info.order=website,name,rank,server,world,time,teamspeak\n\n" + "tablist.info.order=website,name,rank,server,world,time,teamspeak\n\n" +
"tablist.info.website.enabled=true\n" + "tablist.info.website.enabled=true\n" +
"tablist.info.website.label=&b&lWebsite:\n" + "tablist.info.website.label=&b&lWebsite:\n" +
@@ -693,62 +681,75 @@ public class TablistModule implements Module, Listener {
"tablist.info.teamspeak.type=teamspeak\n" + "tablist.info.teamspeak.type=teamspeak\n" +
"tablist.info.teamspeak.value=&fts.viper-network.de\n"; "tablist.info.teamspeak.value=&fts.viper-network.de\n";
try (OutputStream out = new FileOutputStream(f)) { out.write(content.getBytes(StandardCharsets.UTF_8)); } try (OutputStream out = new FileOutputStream(f)) { out.write(content.getBytes(StandardCharsets.UTF_8)); }
catch (Exception e) { plugin.getLogger().warning("[TablistModule] Config-Fehler: " + e.getMessage()); } catch (Exception e) { plugin.getLogger().warning("[TablistModule] Config: " + e.getMessage()); }
} }
private void loadConfig() { private void loadConfig() {
File file = new File(plugin.getDataFolder(), CONFIG_FILE); File file = new File(plugin.getDataFolder(), CONFIG_FILE);
Properties p = new Properties(); Map<String, String> map = new LinkedHashMap<>();
if (file.exists()) { if (file.exists()) {
try (FileInputStream fis = new FileInputStream(file)) { try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
p.load(new InputStreamReader(fis, 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("[TablistModule] Ladefehler: " + e.getMessage()); } } catch (Exception e) { plugin.getLogger().warning("[TablistModule] Ladefehler: " + e.getMessage()); }
} }
enabled = Boolean.parseBoolean(p.getProperty("tablist.enabled", "true")); java.util.function.BiFunction<String,String,String> get = (k,d) -> map.getOrDefault(k,d);
updateInterval = parseInt(p.getProperty("tablist.update_interval", "5"), 5);
layoutMode = p.getProperty("tablist.layout", "classic").trim().toLowerCase(); configuredTabSize = parseInt(get.apply("tablist.tab_size", "0"), 0);
headerLine1 = p.getProperty("tablist.header.line1", headerLine1); enabled = Boolean.parseBoolean(get.apply("tablist.enabled", "true"));
headerLine2 = p.getProperty("tablist.header.line2", headerLine2); updateInterval = parseInt(get.apply("tablist.update_interval", "5"), 5);
headerLine3 = p.getProperty("tablist.header.line3", headerLine3); layoutMode = get.apply("tablist.layout", "compact").trim().toLowerCase();
footerLine1 = p.getProperty("tablist.footer.line1", footerLine1); headerLine1 = get.apply("tablist.header.line1", headerLine1);
footerLine2 = p.getProperty("tablist.footer.line2", footerLine2); headerLine2 = get.apply("tablist.header.line2", headerLine2);
footerLine3 = p.getProperty("tablist.footer.line3", footerLine3); headerLine3 = get.apply("tablist.header.line3", headerLine3);
compactHeader1 = p.getProperty("tablist.compact.header.line1", compactHeader1); footerLine1 = get.apply("tablist.footer.line1", footerLine1);
compactHeader2 = p.getProperty("tablist.compact.header.line2", compactHeader2); footerLine2 = get.apply("tablist.footer.line2", footerLine2);
compactHeader3 = p.getProperty("tablist.compact.header.line3", compactHeader3); footerLine3 = get.apply("tablist.footer.line3", footerLine3);
compactHeader2Spacer = Boolean.parseBoolean(p.getProperty("tablist.compact.header.line2.spacer", "false")); compactHeader1 = get.apply("tablist.compact.header.line1", compactHeader1);
compactHeader3Spacer = Boolean.parseBoolean(p.getProperty("tablist.compact.header.line3.spacer", "false")); compactHeader2 = get.apply("tablist.compact.header.line2", compactHeader2);
compactFooter1 = p.getProperty("tablist.compact.footer.line1", compactFooter1); compactHeader3 = get.apply("tablist.compact.header.line3", compactHeader3);
compactFooter2 = p.getProperty("tablist.compact.footer.line2", compactFooter2); compactHeader2Spacer = Boolean.parseBoolean(get.apply("tablist.compact.header.line2.spacer", "false"));
compactFooter3 = p.getProperty("tablist.compact.footer.line3", compactFooter3); compactHeader3Spacer = Boolean.parseBoolean(get.apply("tablist.compact.header.line3.spacer", "false"));
compactFooter4 = p.getProperty("tablist.compact.footer.line4", compactFooter4); compactFooter1 = get.apply("tablist.compact.footer.line1", compactFooter1);
compactFooter1Spacer = Boolean.parseBoolean(p.getProperty("tablist.compact.footer.line1.spacer", "false")); compactFooter2 = get.apply("tablist.compact.footer.line2", compactFooter2);
compactFooter4Spacer = Boolean.parseBoolean(p.getProperty("tablist.compact.footer.line4.spacer", "false")); compactFooter3 = get.apply("tablist.compact.footer.line3", compactFooter3);
colorSrvHeader = p.getProperty("tablist.color.server_header", colorSrvHeader); compactFooter4 = get.apply("tablist.compact.footer.line4", compactFooter4);
timeFormat = p.getProperty("tablist.time_format", timeFormat); compactFooter1Spacer = Boolean.parseBoolean(get.apply("tablist.compact.footer.line1.spacer", "false"));
timeZone = p.getProperty("tablist.timezone", timeZone); compactFooter4Spacer = Boolean.parseBoolean(get.apply("tablist.compact.footer.line4.spacer", "false"));
try { colorSrvHeader = get.apply("tablist.color.server_header", colorSrvHeader);
sdf = new SimpleDateFormat(timeFormat); timeFormat = get.apply("tablist.time_format", timeFormat);
sdf.setTimeZone(java.util.TimeZone.getTimeZone(timeZone)); timeZone = get.apply("tablist.timezone", timeZone);
} catch (Exception e) { try { sdf = new SimpleDateFormat(timeFormat); sdf.setTimeZone(java.util.TimeZone.getTimeZone(timeZone)); }
sdf = new SimpleDateFormat("HH:mm:ss / h:mm a"); catch (Exception e) { sdf = new SimpleDateFormat("HH:mm:ss / h:mm a"); }
sdf.setTimeZone(java.util.TimeZone.getTimeZone("Europe/Berlin"));
} rankOrder.clear();
String rankRaw = get.apply("tablist.rank_order", "").trim();
if (!rankRaw.isEmpty()) for (String s : rankRaw.split(",")) { String t=s.trim(); if(!t.isEmpty()) rankOrder.add(t.toLowerCase()); }
serverOrder.clear();
String raw = get.apply("tablist.server_order", "").trim();
if (!raw.isEmpty()) for (String s : raw.split(",")) { String t=s.trim(); if(!t.isEmpty()) serverOrder.add(t.toLowerCase()); }
hiddenServers.clear();
String hRaw = get.apply("tablist.hidden_servers", "").trim();
if (!hRaw.isEmpty()) for (String s : hRaw.split(",")) { String t=s.trim().toLowerCase(); if(!t.isEmpty()) hiddenServers.add(t); }
// Info-Eintraege laden
infoEntries.clear(); infoEntries.clear();
String orderRaw = p.getProperty("tablist.info.order", String orderRaw = get.apply("tablist.info.order", "website,name,rank,server,world,time,teamspeak").trim();
"website,name,rank,server,world,time,teamspeak").trim();
for (String id : orderRaw.split(",")) { for (String id : orderRaw.split(",")) {
id = id.trim(); id = id.trim(); if (id.isEmpty()) continue;
if (id.isEmpty()) continue; boolean en = Boolean.parseBoolean(get.apply("tablist.info." + id + ".enabled", "true"));
boolean enabled = Boolean.parseBoolean(p.getProperty("tablist.info." + id + ".enabled", "true")); String label = get.apply("tablist.info." + id + ".label", "");
String label = p.getProperty("tablist.info." + id + ".label", ""); String type = get.apply("tablist.info." + id + ".type", "custom");
String type = p.getProperty("tablist.info." + id + ".type", "custom"); String value = get.apply("tablist.info." + id + ".value", "");
String value = p.getProperty("tablist.info." + id + ".value", ""); infoEntries.add(new InfoEntry(label, type, value, en));
infoEntries.add(new InfoEntry(label, type, value, enabled));
} }
// Fallback wenn keine Eintraege konfiguriert
if (infoEntries.isEmpty()) { if (infoEntries.isEmpty()) {
infoEntries.add(new InfoEntry("&b&lWebsite:", "website", "&fviper-network.de", true)); infoEntries.add(new InfoEntry("&b&lWebsite:", "website", "&fviper-network.de", true));
infoEntries.add(new InfoEntry("&b&lName:", "name", "", true)); infoEntries.add(new InfoEntry("&b&lName:", "name", "", true));
@@ -758,19 +759,5 @@ public class TablistModule implements Module, Listener {
infoEntries.add(new InfoEntry("&b&lTime:", "time", "", true)); infoEntries.add(new InfoEntry("&b&lTime:", "time", "", true));
infoEntries.add(new InfoEntry("&b&lTeamspeak:", "teamspeak", "&fts.viper-network.de", true)); infoEntries.add(new InfoEntry("&b&lTeamspeak:", "teamspeak", "&fts.viper-network.de", true));
} }
rankOrder.clear();
String rankRaw = p.getProperty("tablist.rank_order", "").trim();
if (!rankRaw.isEmpty())
for (String s : rankRaw.split(",")) { String t = s.trim(); if (!t.isEmpty()) rankOrder.add(t.toLowerCase()); }
serverOrder.clear();
String raw = p.getProperty("tablist.server_order", "").trim();
if (!raw.isEmpty())
for (String s : raw.split(",")) { String t = s.trim(); if (!t.isEmpty()) serverOrder.add(t.toLowerCase()); }
hiddenServers.clear();
String hiddenRaw = p.getProperty("tablist.hidden_servers", "").trim();
if (!hiddenRaw.isEmpty())
for (String s : hiddenRaw.split(",")) { String t = s.trim().toLowerCase(); if (!t.isEmpty()) hiddenServers.add(t); }
} }
} }

View File

@@ -1,10 +1,16 @@
package net.viper.status.modules.verify; package net.viper.status.modules.verify;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.Command;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import net.viper.status.module.Module; import net.viper.status.module.Module;
@@ -38,7 +44,7 @@ public class VerifyModule implements Module {
public void onEnable(Plugin plugin) { public void onEnable(Plugin plugin) {
loadConfig(plugin); loadConfig(plugin);
ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new VerifyCommand()); ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new VerifyCommand());
plugin.getLogger().info("VerifyModule aktiviert. " + serverConfigs.size() + " Server-Konfigurationen geladen."); plugin.getLogger().fine("VerifyModule aktiviert. " + serverConfigs.size() + " Server-Konfigurationen geladen.");
} }
@Override @Override
@@ -56,7 +62,7 @@ public class VerifyModule implements Module {
if (in == null) { plugin.getLogger().warning("Standard-config '" + fileName + "' nicht in JAR."); return; } if (in == null) { plugin.getLogger().warning("Standard-config '" + fileName + "' nicht in JAR."); return; }
byte[] buffer = new byte[1024]; int length; byte[] buffer = new byte[1024]; int length;
while ((length = in.read(buffer)) > 0) out.write(buffer, 0, length); while ((length = in.read(buffer)) > 0) out.write(buffer, 0, length);
plugin.getLogger().info("Konfigurationsdatei '" + fileName + "' erstellt."); StatusAPI.debugLog(plugin, "Konfigurationsdatei '" + fileName + "' erstellt.");
} catch (Exception e) { plugin.getLogger().severe("Fehler beim Erstellen der Config: " + e.getMessage()); return; } } catch (Exception e) { plugin.getLogger().severe("Fehler beim Erstellen der Config: " + e.getMessage()); return; }
} }

View File

@@ -1,6 +1,6 @@
name: StatusAPI name: StatusAPI
main: net.viper.status.StatusAPI main: net.viper.status.StatusAPI
version: 4.1.1 version: 4.1.0
author: M_Viper author: M_Viper
description: StatusAPI für BungeeCord inkl. Update-Checker, Modul-System und ChatModule description: StatusAPI für BungeeCord inkl. Update-Checker, Modul-System und ChatModule
# Mindestanforderung: Minecraft 1.20 / BungeeCord mit PlayerChatEvent-Unterstützung # Mindestanforderung: Minecraft 1.20 / BungeeCord mit PlayerChatEvent-Unterstützung