Delete src/main/java/net/viper/status/modules/tablist/TablistModule.java via Git Manager GUI

This commit is contained in:
2026-05-07 19:49:48 +00:00
parent 5492479774
commit a4c5b88841

View File

@@ -1,579 +0,0 @@
package net.viper.status.modules.tablist;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.ServerSwitchEvent;
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.md_5.bungee.protocol.packet.PlayerListItem;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import net.md_5.bungee.protocol.packet.PlayerListItemUpdate;
import net.viper.status.module.Module;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
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;
/**
* TablistModule fuer StatusAPI (BungeeCord 1.19.3+).
*
* Liest die tab_size aus der BungeeCord-Konfiguration und berechnet
* ROWS/COLUMNS dynamisch:
* tab_size=60 -> 3 Spalten x 20 Zeilen
* tab_size=80 -> 4 Spalten x 20 Zeilen
*
* Layout:
* Spalte 0 = Info (Website / Name / Rank / Server / World / Time / TS)
* Spalte 1 = Spieler auf Server 1 (Lobby)
* Spalte 2 = Spieler auf Server 2
* Spalte 3 = Spieler auf Server 3 (nur bei tab_size=80)
*/
public class TablistModule implements Module, Listener {
private static final String CONFIG_FILE = "tablist.properties";
// Wird beim Start dynamisch aus BungeeCord tab_size ermittelt
private int rows = 20;
private int columns = 4;
private int total = rows * columns;
// Fake-UUIDs werden nach Bestimmung von total initialisiert
private UUID[] fakeUuids;
// ── Config ─────────────────────────────────────────────────────────────────
private boolean enabled = true;
private int updateInterval = 5;
private String headerLine1 = "&8&m" + rep('\u2501', 53);
private String headerLine2 = " &6&lViper Network";
private String headerLine3 = "&8&m" + rep('\u2501', 53);
private String footerLine1 = "&8&m" + rep('\u2501', 53);
private String footerLine2 = " &7Discord: &ediscord.viper-network.de &8| &7Shop: &eviper-network.de/shop";
private String footerLine3 = "&8&m" + rep('\u2501', 53);
private String labelWebsite = "&b&lWebsite:";
private String valueWebsite = "&fviper-network.de";
private String labelName = "&b&lName:";
private String labelRank = "&b&lRank:";
private String labelServer = "&b&lServer:";
private String labelWorld = "&b&lWorld:";
private String labelTime = "&b&lTime:";
private String labelTeamspeak = "&b&lTeamspeak:";
private String valueTeamspeak = "&fts.viper-network.de";
private String colorSrvHeader = "&6&l";
private String timeFormat = "HH:mm:ss / h:mm a";
private SimpleDateFormat sdf;
private List<String> serverOrder = new ArrayList<>();
private Set<String> hiddenServers = new HashSet<>();
// ── State ──────────────────────────────────────────────────────────────────
private Plugin plugin;
private ScheduledTask updateTask;
private Method sendPacketQueuedMethod;
private int tabSizeMax = 80; // maximale Slots laut BungeeCord tab_size
// ══════════════════════════════════════════════════════════════════════════
@Override public String getName() { return "TablistModule"; }
@Override
public void onEnable(Plugin plugin) {
this.plugin = plugin;
ensureConfigExists();
loadConfig();
if (!enabled) { plugin.getLogger().info("[TablistModule] Deaktiviert."); return; }
// Tab-Size aus BungeeCord auslesen und ROWS/COLUMNS berechnen
initGridSize();
// Fake-UUIDs initialisieren
fakeUuids = new UUID[total];
for (int i = 0; i < total; i++) {
fakeUuids[i] = new UUID(0xFFFEDEAD00000000L, (long) i);
}
// Reflection-Cache fuer sendPacketQueued
try {
Class<?> ucClass = Class.forName("net.md_5.bungee.UserConnection");
sendPacketQueuedMethod = ucClass.getMethod("sendPacketQueued",
net.md_5.bungee.protocol.DefinedPacket.class);
sendPacketQueuedMethod.setAccessible(true);
} catch (Exception e) {
plugin.getLogger().severe("[TablistModule] sendPacketQueued nicht gefunden: " + e.getMessage());
return;
}
ProxyServer.getInstance().getPluginManager().registerListener(plugin, this);
updateTask = ProxyServer.getInstance().getScheduler().schedule(
plugin, this::updateAll, 2L, Math.max(1, updateInterval), TimeUnit.SECONDS);
// Server-Erkennung loggen
ProxyServer.getInstance().getScheduler().schedule(plugin, () -> {
List<String> all = new ArrayList<>(ProxyServer.getInstance().getServers().keySet());
plugin.getLogger().info("[TablistModule] Alle BungeeCord-Server: " + all);
plugin.getLogger().info("[TablistModule] Tablist-Spalten (" + columns + "x" + rows + "): " + getServerOrder());
}, 3L, TimeUnit.SECONDS);
plugin.getLogger().info("[TablistModule] Aktiviert. Grid=" + columns + "x" + rows
+ " (total=" + total + "), Interval=" + updateInterval + "s");
}
@Override
public void onDisable(Plugin plugin) {
if (updateTask != null) { updateTask.cancel(); updateTask = null; }
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
try {
removeFakeSlots(p);
p.setTabHeader(new TextComponent(""), new TextComponent(""));
} catch (Exception ignored) {}
}
}
/**
* Liest tab_size aus dem ersten BungeeCord-Listener und berechnet ROWS/COLUMNS.
* Minecraft zeigt immer 20 Zeilen, die Spaltenanzahl ergibt sich aus tab_size/20.
* Fallback: 4 Spalten x 20 Zeilen = 80.
*/
private void initGridSize() {
int tabSize = 80;
try {
for (ListenerInfo li : ProxyServer.getInstance().getConfig().getListeners()) {
// getTabSize() nicht im API-Interface, daher Reflection
try {
java.lang.reflect.Method m = li.getClass().getMethod("getTabSize");
Object val = m.invoke(li);
if (val instanceof Number && ((Number) val).intValue() > 0) {
tabSize = ((Number) val).intValue();
break;
}
} catch (Exception ignored) {}
}
} catch (Exception e) {
plugin.getLogger().warning("[TablistModule] Konnte tab_size nicht lesen, nutze 80.");
}
rows = 20;
tabSizeMax = tabSize;
// Beim Start noch keine Server bekannt → maxColumns als Startwert, recalculateGrid korrigiert später
columns = Math.max(2, tabSize / rows);
total = rows * columns;
// Sofort korrekt berechnen falls Server bereits bekannt
int serverCount = getServerOrder().size();
if (serverCount > 0) {
int needed = 1 + serverCount;
columns = Math.max(2, Math.min(needed, tabSize / rows));
total = rows * columns;
}
plugin.getLogger().info("[TablistModule] BungeeCord tab_size=" + tabSize
+ " -> " + columns + " Spalten x " + rows + " Zeilen = " + total + " Slots");
}
// ══════════════════════════════════════════════════════════════════════════
// Events
// ══════════════════════════════════════════════════════════════════════════
@EventHandler public void onLogin(PostLoginEvent e) {
if (!enabled) return;
ProxyServer.getInstance().getScheduler().schedule(plugin,
() -> updateTablist(e.getPlayer()), 3L, TimeUnit.SECONDS);
}
@EventHandler public void onSwitch(ServerSwitchEvent e) {
if (!enabled) return;
ProxyServer.getInstance().getScheduler().schedule(plugin,
() -> updateTablist(e.getPlayer()), 1L, TimeUnit.SECONDS);
}
@EventHandler public void onDisconnect(PlayerDisconnectEvent e) {
if (!enabled) return;
ProxyServer.getInstance().getScheduler().schedule(plugin, this::updateAll, 1L, TimeUnit.SECONDS);
}
// ══════════════════════════════════════════════════════════════════════════
// Core
// ══════════════════════════════════════════════════════════════════════════
private void updateAll() {
recalculateGrid();
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) updateTablist(p);
}
/**
* Berechnet Spaltenanzahl dynamisch anhand der aktuell sichtbaren Server.
* Spalte 0 = Info, Spalten 1..n = Server
* Minimum: 2 Spalten (Info + 1 Server)
* Maximum: durch tab_size begrenzt
*/
private void recalculateGrid() {
int serverCount = getServerOrder().size();
int needed = 1 + serverCount; // Info-Spalte + Server-Spalten
int maxColumns = tabSizeMax / rows;
int newColumns = Math.max(2, Math.min(needed, maxColumns));
int newTotal = rows * newColumns;
if (newColumns == columns && newTotal == total) return;
// Grid hat sich geändert alte Fake-Slots bei allen Spielern entfernen
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
try { removeFakeSlots(p); } catch (Exception ignored) {}
}
columns = newColumns;
total = newTotal;
// Fake-UUIDs neu initialisieren
fakeUuids = new UUID[total];
for (int i = 0; i < total; i++) {
fakeUuids[i] = new UUID(0xFFFEDEAD00000000L, (long) i);
}
plugin.getLogger().info("[TablistModule] Grid: "
+ columns + " Spalten x " + rows + " Zeilen = " + total
+ " Slots (" + serverCount + " Server)");
}
private void updateTablist(ProxiedPlayer viewer) {
if (viewer == null || !viewer.isConnected()) return;
try {
String header = c(headerLine1) + "\n" + c(headerLine2) + "\n" + c(headerLine3);
String footer = c(footerLine1) + "\n" + c(footerLine2) + "\n" + c(footerLine3);
viewer.setTabHeader(new TextComponent(header), new TextComponent(footer));
hideRealPlayers(viewer);
sendSlots(viewer, buildItems(viewer));
} catch (Exception ex) {
plugin.getLogger().warning("[TablistModule] Fehler fuer " + viewer.getName() + ": " + ex.getMessage());
}
}
/**
* Setzt alle echten Spieler-Slots auf listed=false damit sie in der Tablist
* unsichtbar werden und unsere Fake-Slots nicht verschieben.
*/
@SuppressWarnings("unchecked")
private void hideRealPlayers(ProxiedPlayer viewer) {
if (sendPacketQueuedMethod == null) return;
try {
java.util.Collection<ProxiedPlayer> online = ProxyServer.getInstance().getPlayers();
if (online.isEmpty()) return;
PlayerListItemUpdate pkt = new PlayerListItemUpdate();
pkt.setActions(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LISTED));
Item[] items = new Item[online.size()];
int idx = 0;
for (ProxiedPlayer p : online) {
Item item = new Item();
item.setUuid(p.getUniqueId());
item.setListed(false);
items[idx++] = item;
}
pkt.setItems(items);
sendPacketQueuedMethod.invoke(viewer, pkt);
} catch (Exception e) {
plugin.getLogger().warning("[TablistModule] hideRealPlayers: " + e.getMessage());
}
}
// ══════════════════════════════════════════════════════════════════════════
// Item-Matrix aufbauen
// ══════════════════════════════════════════════════════════════════════════
private Item[] buildItems(ProxiedPlayer viewer) {
String[] texts = new String[total];
net.md_5.bungee.protocol.data.Property[][] skins = new net.md_5.bungee.protocol.data.Property[total][];
int[] pings = new int[total];
for (int i = 0; i < total; i++) {
texts[i] = " ";
skins[i] = new net.md_5.bungee.protocol.data.Property[0];
pings[i] = 0; // Fake-Ping fuer leere Slots
}
// ── Spalte 0: Info ────────────────────────────────────────────────────
int base = 0, row = 0;
row = set(texts, base, row, c(labelWebsite));
row = set(texts, base, row, c(valueWebsite));
row = set(texts, base, row, " ");
row = set(texts, base, row, c(labelName));
row = set(texts, base, row, c("&f" + viewer.getName()));
row = set(texts, base, row, " ");
row = set(texts, base, row, c(labelRank));
row = set(texts, base, row, c("&f" + getRank(viewer)));
row = set(texts, base, row, " ");
String srv = viewer.getServer() != null ? capitalize(viewer.getServer().getInfo().getName()) : "\u2014";
row = set(texts, base, row, c(labelServer));
row = set(texts, base, row, c("&f" + srv));
row = set(texts, base, row, " ");
row = set(texts, base, row, c(labelWorld));
String world = net.viper.status.StatusAPI.playerWorlds.getOrDefault(viewer.getUniqueId(), "world");
row = set(texts, base, row, c("&f" + world));
row = set(texts, base, row, " ");
row = set(texts, base, row, c(labelTime));
row = set(texts, base, row, c("&f[" + sdf.format(new Date()) + "]"));
row = set(texts, base, row, " ");
row = set(texts, base, row, c(labelTeamspeak));
row = set(texts, base, row, c(valueTeamspeak));
// ── Spalten 1 bis (columns-1): Server-Spieler ─────────────────────────
List<String> servers = getServerOrder();
for (int col = 1; col < columns && (col - 1) < servers.size(); col++) {
base = col * rows;
row = 0;
String sName = servers.get(col - 1);
row = set(texts, base, row, c(colorSrvHeader + capitalize(sName)));
ServerInfo info = ProxyServer.getInstance().getServerInfo(sName);
if (info != null) {
for (ProxiedPlayer p : info.getPlayers()) {
if (row >= rows) break;
String prefix = getLuckPermsPrefix(p);
String display = prefix.isEmpty()
? c("&7" + p.getName())
: c(prefix + "&r " + p.getName());
set(texts, base, row, display);
skins[base + row] = getPlayerSkin(p);
pings[base + row] = Math.max(0, p.getPing());
row++;
}
}
}
// Items zusammenbauen
Item[] items = new Item[total];
for (int i = 0; i < total; i++) {
Item item = new Item();
item.setUuid(fakeUuids[i]);
item.setUsername(fakeName(i));
item.setProperties(skins[i]);
item.setGamemode(0);
item.setPing(pings[i]);
item.setListed(true);
String text = texts[i];
item.setDisplayName(new TextComponent(text == null || text.isEmpty() ? " " : text));
items[i] = item;
}
return items;
}
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 net.md_5.bungee.protocol.data.Property[] getPlayerSkin(ProxiedPlayer player) {
try {
Object pending = player.getPendingConnection();
net.md_5.bungee.connection.LoginResult profile =
(net.md_5.bungee.connection.LoginResult)
pending.getClass().getMethod("getLoginProfile").invoke(pending);
if (profile != null && profile.getProperties() != null) {
return profile.getProperties();
}
} catch (Exception ignored) {}
return new net.md_5.bungee.protocol.data.Property[0];
}
// ══════════════════════════════════════════════════════════════════════════
// Pakete senden
// ══════════════════════════════════════════════════════════════════════════
@SuppressWarnings("unchecked")
private void sendSlots(ProxiedPlayer viewer, Item[] items) {
if (sendPacketQueuedMethod == null) return;
PlayerListItemUpdate pkt = new PlayerListItemUpdate();
EnumSet actions = EnumSet.of(
PlayerListItemUpdate.Action.ADD_PLAYER,
PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME,
PlayerListItemUpdate.Action.UPDATE_LISTED,
PlayerListItemUpdate.Action.UPDATE_LATENCY
);
pkt.setActions(actions);
pkt.setItems(items);
try { sendPacketQueuedMethod.invoke(viewer, pkt); }
catch (Exception e) { plugin.getLogger().warning("[TablistModule] sendPacketQueued: " + e.getMessage()); }
}
private void removeFakeSlots(ProxiedPlayer viewer) {
if (sendPacketQueuedMethod == null || fakeUuids == null) return;
try {
Class<?> cls = Class.forName("net.md_5.bungee.protocol.packet.PlayerListItemRemove");
Object pkt = cls.getDeclaredConstructor().newInstance();
cls.getMethod("setUuids", UUID[].class).invoke(pkt, (Object) fakeUuids.clone());
sendPacketQueuedMethod.invoke(viewer, pkt);
} catch (Exception e) {
try {
PlayerListItem rem = new PlayerListItem();
rem.setAction(PlayerListItem.Action.REMOVE_PLAYER);
Item[] items = new Item[total];
for (int i = 0; i < total; i++) { Item it = new Item(); it.setUuid(fakeUuids[i]); items[i] = it; }
rem.setItems(items);
sendPacketQueuedMethod.invoke(viewer, rem);
} catch (Exception ignored) {}
}
}
// ══════════════════════════════════════════════════════════════════════════
// Helpers
// ══════════════════════════════════════════════════════════════════════════
private List<String> getServerOrder() {
if (!serverOrder.isEmpty()) return new ArrayList<>(serverOrder);
List<String> list = new ArrayList<>();
final String[] lobbyKey = { null };
for (String key : ProxyServer.getInstance().getServers().keySet()) {
if (key.equalsIgnoreCase("lobby")) { lobbyKey[0] = key; break; }
}
if (lobbyKey[0] != null) list.add(lobbyKey[0]);
ProxyServer.getInstance().getServers().keySet().stream()
.filter(s -> lobbyKey[0] == null || !s.equalsIgnoreCase(lobbyKey[0]))
.filter(s -> !hiddenServers.contains(s.toLowerCase()))
.sorted(String.CASE_INSENSITIVE_ORDER)
.forEach(list::add);
return list;
}
private String getRank(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();
Object pg = usr.getClass().getMethod("getPrimaryGroup").invoke(usr);
if (pg != null && !pg.toString().isEmpty()) return "[" + pg.toString().toUpperCase() + "]";
}
} catch (Exception ignored) {}
return "NONE";
}
private String getLuckPermsPrefix(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) return ChatColor.translateAlternateColorCodes('&', pfx.toString());
}
} catch (Exception ignored) {}
return "";
}
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; } }
// ══════════════════════════════════════════════════════════════════════════
// Config
// ══════════════════════════════════════════════════════════════════════════
private void ensureConfigExists() {
File f = new File(plugin.getDataFolder(), CONFIG_FILE);
if (f.exists()) return;
if (!plugin.getDataFolder().exists()) plugin.getDataFolder().mkdirs();
String sep = rep('\u2501', 53);
String content =
"# TablistModule Konfiguration\n" +
"tablist.enabled=true\n" +
"tablist.update_interval=5\n\n" +
"# Server-Spalten Reihenfolge (leer = Lobby zuerst, dann alle alphabetisch)\n" +
"# Beispiel: tablist.server_order=lobby,survival,citybuild\n" +
"tablist.server_order=\n\n" +
"# Server die NICHT angezeigt werden (kommagetrennt, leer = alle anzeigen)\n" +
"tablist.hidden_servers=\n\n" +
"tablist.header.line1=&8&m" + sep + "\n" +
"tablist.header.line2= &6&lViper Network\n" +
"tablist.header.line3=&8&m" + sep + "\n\n" +
"tablist.footer.line1=&8&m" + sep + "\n" +
"tablist.footer.line2= &7Discord: &ediscord.viper-network.de &8| &7Shop: &eviper-network.de/shop\n" +
"tablist.footer.line3=&8&m" + sep + "\n\n" +
"tablist.info.label.website=&b&lWebsite:\n" +
"tablist.info.value.website=&fviper-network.de\n" +
"tablist.info.label.name=&b&lName:\n" +
"tablist.info.label.rank=&b&lRank:\n" +
"tablist.info.label.server=&b&lServer:\n" +
"tablist.info.label.world=&b&lWorld:\n" +
"tablist.info.label.time=&b&lTime:\n" +
"tablist.info.label.teamspeak=&b&lTeamspeak:\n" +
"tablist.info.value.teamspeak=&fts.viper-network.de\n\n" +
"tablist.color.server_header=&6&l\n" +
"tablist.time_format=HH:mm:ss / h:mm a\n";
try (OutputStream out = new FileOutputStream(f)) { out.write(content.getBytes(StandardCharsets.UTF_8)); }
catch (Exception e) { plugin.getLogger().warning("[TablistModule] Config-Fehler: " + e.getMessage()); }
}
private void loadConfig() {
File file = new File(plugin.getDataFolder(), CONFIG_FILE);
Properties p = new Properties();
if (file.exists()) {
try (FileInputStream fis = new FileInputStream(file)) {
p.load(new InputStreamReader(fis, StandardCharsets.UTF_8));
} catch (Exception e) { plugin.getLogger().warning("[TablistModule] Ladefehler: " + e.getMessage()); }
}
enabled = Boolean.parseBoolean(p.getProperty("tablist.enabled", "true"));
updateInterval = parseInt(p.getProperty("tablist.update_interval", "5"), 5);
headerLine1 = p.getProperty("tablist.header.line1", headerLine1);
headerLine2 = p.getProperty("tablist.header.line2", headerLine2);
headerLine3 = p.getProperty("tablist.header.line3", headerLine3);
footerLine1 = p.getProperty("tablist.footer.line1", footerLine1);
footerLine2 = p.getProperty("tablist.footer.line2", footerLine2);
footerLine3 = p.getProperty("tablist.footer.line3", footerLine3);
labelWebsite = p.getProperty("tablist.info.label.website", labelWebsite);
valueWebsite = p.getProperty("tablist.info.value.website", valueWebsite);
labelName = p.getProperty("tablist.info.label.name", labelName);
labelRank = p.getProperty("tablist.info.label.rank", labelRank);
labelServer = p.getProperty("tablist.info.label.server", labelServer);
labelWorld = p.getProperty("tablist.info.label.world", labelWorld);
labelTime = p.getProperty("tablist.info.label.time", labelTime);
labelTeamspeak = p.getProperty("tablist.info.label.teamspeak", labelTeamspeak);
valueTeamspeak = p.getProperty("tablist.info.value.teamspeak", valueTeamspeak);
colorSrvHeader = p.getProperty("tablist.color.server_header", colorSrvHeader);
timeFormat = p.getProperty("tablist.time_format", timeFormat);
try { sdf = new SimpleDateFormat(timeFormat); }
catch (Exception e) { sdf = new SimpleDateFormat("HH:mm:ss / h:mm a"); }
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); }
}
}
}