Soft-delete copy _trash/2026-05-07T19-39-23-130Z/src/main/java/net/viper/status/modules/tablist/TablistModule.java
This commit is contained in:
@@ -0,0 +1,579 @@
|
|||||||
|
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); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user