Upload folder via GUI - src

This commit is contained in:
Git Manager GUI
2026-05-21 14:25:49 +02:00
parent c052f01feb
commit 5e9e05f509

View File

@@ -43,8 +43,8 @@ public class TablistModule implements Module, Listener {
// Grid rows ist IMMER 20 (Minecraft-Client-Layout: N Slots → ceil(N/20) Spalten à 20 Zeilen)
private static final int ROWS = 20;
private int rows = ROWS, columns = 3, total = 60, tabSizeMax = 60;
private int configuredTabSize = 0; // 0 = auto-detect aus BungeeCord
private int rows = ROWS, columns = 6, total = 120, tabSizeMax = 180;
private int configuredTabSize = 180; // Default: 180 (9 Spalten möglich)
private UUID[] fakeUuids;
// Skin-Cache (pro Spieler)
@@ -113,6 +113,7 @@ public class TablistModule implements Module, Listener {
private Plugin plugin;
private ScheduledTask updateTask;
private Method sendPacketQueuedMethod;
private java.lang.reflect.Field tabListHandlerField; // BungeeCords interner Tab-Handler
@Override public String getName() { return "TablistModule"; }
@@ -129,13 +130,19 @@ public class TablistModule implements Module, Listener {
} catch (Exception e) {
plugin.getLogger().warning("[TablistModule] initGridSize Fehler: " + e.getMessage() + " nutze Fallback 3x20");
int fbSize = configuredTabSize > 0 ? configuredTabSize : 60;
tabSizeMax = fbSize; rows = ROWS; columns = Math.min(Math.max(3, fbSize / ROWS), 8); total = ROWS * columns;
int maxCols = fbSize / ROWS; // kein hartes Limit mehr tab_size entscheidet
tabSizeMax = fbSize; rows = ROWS; columns = Math.max(3, maxCols); total = ROWS * columns;
}
initUuids();
try {
Class<?> uc = Class.forName("net.md_5.bungee.UserConnection");
sendPacketQueuedMethod = uc.getMethod("sendPacketQueued", net.md_5.bungee.protocol.DefinedPacket.class);
sendPacketQueuedMethod.setAccessible(true);
// BungeeCords internen tabListHandler-Field finden und nullbar machen
try {
tabListHandlerField = uc.getDeclaredField("tabListHandler");
tabListHandlerField.setAccessible(true);
} catch (Exception ignored) {}
plugin.getLogger().info("[TablistModule] sendPacketQueued gefunden.");
} catch (Exception e) {
plugin.getLogger().severe("[TablistModule] sendPacketQueued NICHT gefunden: " + e.getMessage());
@@ -163,14 +170,19 @@ public class TablistModule implements Module, Listener {
private void initGridSize() {
int tabSize = 60;
try {
for (ListenerInfo li : ProxyServer.getInstance().getConfig().getListeners()) {
try { Object v = li.getClass().getMethod("getTabSize").invoke(li);
if (v instanceof Number && ((Number)v).intValue() > 0) { tabSize = ((Number)v).intValue(); break; }
} catch (Exception ignored) {}
}
} catch (Exception ignored) {}
if (configuredTabSize > 0) tabSize = configuredTabSize;
// FIX: configuredTabSize (aus tablist.properties) hat Vorrang.
// Nur wenn nicht gesetzt, per Reflection aus BungeeCord lesen.
if (configuredTabSize > 0) {
tabSize = configuredTabSize;
} else {
try {
for (ListenerInfo li : ProxyServer.getInstance().getConfig().getListeners()) {
try { Object v = li.getClass().getMethod("getTabListSize").invoke(li);
if (v instanceof Number && ((Number)v).intValue() > 0) { tabSize = ((Number)v).intValue(); break; }
} catch (Exception ignored) {}
}
} catch (Exception ignored) {}
}
tabSizeMax = tabSize;
rows = ROWS;
boolean hasInfo = !"compact".equalsIgnoreCase(layoutMode);
@@ -179,11 +191,13 @@ public class TablistModule implements Module, Listener {
columns = Math.max(hasInfo ? 2 : 1, Math.min(needed, tabSize / ROWS));
total = ROWS * columns;
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().warning("[TablistModule] Nicht alle Server passen in die Tablist!");
plugin.getLogger().warning("[TablistModule] LOESUNG: Setze in BungeeCord config.yml -> tab-list-size: " + (needed * ROWS));
plugin.getLogger().warning("[TablistModule] Und in tablist.properties -> tablist.tab_size=" + (needed * ROWS));
}
// Der Client zeigt exakt (tab-list-size / 20) Spalten — BungeeCord config.yml muss stimmen!
plugin.getLogger().info("[TablistModule] tab_size=" + tabSize + " -> " + columns + "x" + ROWS + "=" + total + " (" + serverCount + " Server)");
plugin.getLogger().info("[TablistModule] Stelle sicher dass BungeeCord config.yml 'tab-list-size: " + tabSize + "' gesetzt ist!");
}
private void initUuids() {
@@ -199,7 +213,9 @@ public class TablistModule implements Module, Listener {
ProxiedPlayer p = e.getPlayer();
net.md_5.bungee.protocol.data.Property[] skin = fetchSkin(p);
if (skin != null && skin.length > 0) skinCache.put(p.getUniqueId(), skin);
disableBungeeTabHandler(p);
ProxyServer.getInstance().getScheduler().schedule(plugin, () -> {
disableBungeeTabHandler(p);
updateTablist(p);
ProxyServer.getInstance().getScheduler().schedule(plugin, this::updateAll, 2L, TimeUnit.SECONDS);
}, 2L, TimeUnit.SECONDS);
@@ -212,8 +228,10 @@ public class TablistModule implements Module, Listener {
net.md_5.bungee.protocol.data.Property[] skin = fetchSkin(switched);
if (skin != null && skin.length > 0) skinCache.put(switched.getUniqueId(), skin);
disableBungeeTabHandler(switched);
ProxyServer.getInstance().getScheduler().schedule(plugin, () -> {
disableBungeeTabHandler(switched);
net.md_5.bungee.protocol.data.Property[] freshSkin = fetchSkin(switched);
if (freshSkin != null && freshSkin.length > 0) skinCache.put(switched.getUniqueId(), freshSkin);
@@ -269,10 +287,12 @@ public class TablistModule implements Module, Listener {
} else {
int serverCount = getServerOrder().size();
int needed = (hasInfo ? 1 : 0) + Math.max(1, serverCount);
newColumns = Math.max(hasInfo ? 2 : 1, Math.min(needed, tabSizeMax / ROWS));
int effectiveTabMax = configuredTabSize > 0 ? configuredTabSize : tabSizeMax;
newColumns = Math.max(hasInfo ? 2 : 1, Math.min(needed, effectiveTabMax / ROWS));
}
int newTotal = ROWS * newColumns;
if (newColumns == columns && newTotal == total) return;
// Nur abbrechen wenn Grid identisch UND fakeUuids bereits korrekt initialisiert
if (newColumns == columns && newTotal == total && fakeUuids != null && fakeUuids.length == newTotal) return;
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
try { removeFakeSlots(p); } catch (Exception ignored) {}
}
@@ -286,6 +306,7 @@ public class TablistModule implements Module, Listener {
private void updateTablist(ProxiedPlayer viewer) {
if (viewer == null || !viewer.isConnected()) return;
try {
// DEBUG: zeige aktuelle Grid-Werte im Chat (wird nach erstem Fix entfernt)
String srv = viewer.getServer() != null ? capitalize(viewer.getServer().getInfo().getName()) : "\u2014";
String world = net.viper.status.StatusAPI.playerWorlds.getOrDefault(viewer.getUniqueId(), "world");
String rank = getRank(viewer);
@@ -307,8 +328,11 @@ public class TablistModule implements Module, Listener {
viewer.setTabHeader(
new net.md_5.bungee.api.chat.TextComponent(hComps),
new net.md_5.bungee.api.chat.TextComponent(fComps));
hideRealPlayers(viewer);
// Erst Slots senden (ADD_PLAYER), DANN echte Spieler verstecken (UPDATE_LISTED=false).
// Umgekehrte Reihenfolge führt zu "Ignoring player info update for unknown player"
// weil der Client UPDATE_LISTED für unbekannte UUIDs ignoriert.
sendSlots(viewer, buildItems(viewer));
hideRealPlayers(viewer);
} catch (Exception ex) {
plugin.getLogger().warning("[TablistModule] " + viewer.getName() + ": " + ex.getMessage());
}
@@ -363,8 +387,11 @@ public class TablistModule implements Module, Listener {
for (int i = 0; i < total; i++) { texts[i] = " "; skins[i] = EMPTY_SKIN; pings[i] = 0; }
boolean compact = "compact".equalsIgnoreCase(layoutMode);
// Ob der Spalten-Header einen Slot belegt (= "full") oder nicht (= "none"/"small")
boolean useSlotHeader = "full".equalsIgnoreCase(columnHeaderMode);
// Ob der Spalten-Header einen Slot belegt:
// "full" = explizit aktiviert, "none"/"small" = früher deaktiviert.
// FIX: Im Server-Modus immer den Servernamen in Zeile 0 schreiben,
// sonst weiß der Spieler nicht welche Spalte welcher Server ist.
boolean useSlotHeader = !"none".equalsIgnoreCase(columnHeaderMode);
// Info-Spalte (nur classic)
if (!compact) {
@@ -476,51 +503,73 @@ public class TablistModule implements Module, Listener {
private void hideRealPlayers(ProxiedPlayer viewer) {
if (sendPacketQueuedMethod == null) return;
try {
// Echte Spieler: nur listed=false setzen (KEIN Remove, sonst geht der Skin verloren!)
// Der Client kennt sie bereits per ADD_PLAYER von BungeeCord → UPDATE_LISTED reicht.
Collection<ProxiedPlayer> online = ProxyServer.getInstance().getPlayers();
if (!online.isEmpty()) {
PlayerListItemUpdate playerPkt = new PlayerListItemUpdate();
playerPkt.setActions(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LISTED));
Item[] playerItems = new Item[online.size()];
int i = 0;
for (ProxiedPlayer p : online) {
Item it = new Item(); it.setUuid(p.getUniqueId()); it.setListed(false);
playerItems[i++] = it;
}
playerPkt.setItems(playerItems);
sendPacketQueuedMethod.invoke(viewer, playerPkt);
}
// Sammle alle UUIDs die versteckt werden sollen:
// 1. Echte Spieler
// 2. BungeeCord-interne Server-Eintraege (werden von BC selbst in die Tablist geschrieben)
List<UUID> toHide = new ArrayList<>();
for (ProxiedPlayer p : online) toHide.add(p.getUniqueId());
// BungeeCord schreibt fuer jeden Server einen eigenen Eintrag mit einer
// deterministischen UUID (nameUUIDFromBytes des Server-Namens).
// Diese auch auf listed=false setzen damit die grossen Spalten-Header verschwinden.
// Server-UUIDs (BungeeCord schreibt pro Server 2 Einträge in die Tablist):
// Diese per PlayerListItemRemove entfernen das funktioniert auch für
// UUIDs die der Client noch nicht kennt, ohne Skin-Schaden.
List<UUID> serverUuids = new ArrayList<>();
for (String srvName : ProxyServer.getInstance().getServers().keySet()) {
try {
UUID srvUuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + srvName).getBytes(StandardCharsets.UTF_8));
toHide.add(srvUuid);
UUID srvUuid2 = UUID.nameUUIDFromBytes(srvName.getBytes(StandardCharsets.UTF_8));
toHide.add(srvUuid2);
serverUuids.add(UUID.nameUUIDFromBytes(("OfflinePlayer:" + srvName).getBytes(StandardCharsets.UTF_8)));
serverUuids.add(UUID.nameUUIDFromBytes(srvName.getBytes(StandardCharsets.UTF_8)));
} catch (Exception ignored) {}
}
if (toHide.isEmpty()) return;
PlayerListItemUpdate pkt = new PlayerListItemUpdate();
pkt.setActions(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LISTED));
Item[] items = new Item[toHide.size()];
int idx = 0;
for (UUID uuid : toHide) {
Item it = new Item(); it.setUuid(uuid); it.setListed(false); items[idx++] = it;
if (!serverUuids.isEmpty()) {
try {
Class<?> removeClass = Class.forName("net.md_5.bungee.protocol.packet.PlayerListItemRemove");
Object removePkt = removeClass.getDeclaredConstructor().newInstance();
removeClass.getMethod("setUuids", UUID[].class).invoke(removePkt,
(Object) serverUuids.toArray(new UUID[0]));
sendPacketQueuedMethod.invoke(viewer, removePkt);
} catch (Exception ignored) {
// Fallback: UPDATE_LISTED=false (Warnungen im Client-Log sind harmlos)
PlayerListItemUpdate srvPkt = new PlayerListItemUpdate();
srvPkt.setActions(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LISTED));
Item[] srvItems = new Item[serverUuids.size()];
for (int j = 0; j < serverUuids.size(); j++) {
Item it = new Item(); it.setUuid(serverUuids.get(j)); it.setListed(false);
srvItems[j] = it;
}
srvPkt.setItems(srvItems);
sendPacketQueuedMethod.invoke(viewer, srvPkt);
}
}
pkt.setItems(items);
sendPacketQueuedMethod.invoke(viewer, pkt);
} catch (Exception e) { plugin.getLogger().warning("[TablistModule] hideRealPlayers: " + e.getMessage()); }
}
@SuppressWarnings("unchecked")
private void sendSlots(ProxiedPlayer viewer, Item[] items) {
if (sendPacketQueuedMethod == null) return;
PlayerListItemUpdate pkt = new PlayerListItemUpdate();
pkt.setActions(EnumSet.of(
PlayerListItemUpdate.Action.ADD_PLAYER,
PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME,
PlayerListItemUpdate.Action.UPDATE_LISTED,
PlayerListItemUpdate.Action.UPDATE_LATENCY));
pkt.setItems(items);
try { sendPacketQueuedMethod.invoke(viewer, pkt); }
catch (Exception e) { plugin.getLogger().warning("[TablistModule] sendSlots: " + e.getMessage()); }
try {
// Alles in einem Paket ADD_PLAYER muss UPDATE_LISTED enthalten
// damit der Client den Slot sofort als sichtbar markiert.
// Die "Ignoring unknown player" Warnung entsteht wenn hideRealPlayers()
// VOR sendSlots läuft und der Client die UUIDs noch nicht kennt.
// Fix: hideRealPlayers wird NACH sendSlots aufgerufen (siehe updateTablist).
PlayerListItemUpdate pkt = new PlayerListItemUpdate();
pkt.setActions(EnumSet.of(
PlayerListItemUpdate.Action.ADD_PLAYER,
PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME,
PlayerListItemUpdate.Action.UPDATE_LISTED,
PlayerListItemUpdate.Action.UPDATE_LATENCY));
pkt.setItems(items);
sendPacketQueuedMethod.invoke(viewer, pkt);
} catch (Exception e) { plugin.getLogger().warning("[TablistModule] sendSlots: " + e.getMessage()); }
}
private void removeFakeSlots(ProxiedPlayer viewer) {
@@ -544,6 +593,34 @@ public class TablistModule implements Module, Listener {
// ── Helpers ────────────────────────────────────────────────────────────────
/**
* Ersetzt BungeeCords internen tabListHandler durch einen No-Op Proxy.
* null setzen crasht BungeeCord (NPE in DownstreamBridge).
* Ein leerer Proxy ignoriert alle Aufrufe lautlos.
*/
private void disableBungeeTabHandler(ProxiedPlayer player) {
if (tabListHandlerField == null) return;
try {
Class<?> tabListClass = Class.forName("net.md_5.bungee.tab.TabList");
// Wenn schon ein Proxy gesetzt ist, nichts tun
Object current = tabListHandlerField.get(player);
if (current != null && java.lang.reflect.Proxy.isProxyClass(current.getClass())) return;
Object noopProxy = java.lang.reflect.Proxy.newProxyInstance(
tabListClass.getClassLoader(),
new Class<?>[]{ tabListClass },
(proxy, method, args) -> {
Class<?> ret = method.getReturnType();
if (ret == boolean.class || ret == Boolean.class) return false;
if (ret == int.class || ret == Integer.class) return 0;
if (ret == long.class || ret == Long.class) return 0L;
return null;
}
);
tabListHandlerField.set(player, noopProxy);
} catch (Exception ignored) {}
}
/**
* ── ULTIMATE: Gibt das konfigurierte Server-Symbol für den Spieler zurück.
* Leer wenn kein Symbol für den aktuellen Server definiert ist.
@@ -1207,4 +1284,4 @@ public class TablistModule implements Module, Listener {
}
}
}
}