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) // Grid rows ist IMMER 20 (Minecraft-Client-Layout: N Slots → ceil(N/20) Spalten à 20 Zeilen)
private static final int ROWS = 20; private static final int ROWS = 20;
private int rows = ROWS, columns = 3, total = 60, tabSizeMax = 60; private int rows = ROWS, columns = 6, total = 120, tabSizeMax = 180;
private int configuredTabSize = 0; // 0 = auto-detect aus BungeeCord private int configuredTabSize = 180; // Default: 180 (9 Spalten möglich)
private UUID[] fakeUuids; private UUID[] fakeUuids;
// Skin-Cache (pro Spieler) // Skin-Cache (pro Spieler)
@@ -113,6 +113,7 @@ public class TablistModule implements Module, Listener {
private Plugin plugin; private Plugin plugin;
private ScheduledTask updateTask; private ScheduledTask updateTask;
private Method sendPacketQueuedMethod; private Method sendPacketQueuedMethod;
private java.lang.reflect.Field tabListHandlerField; // BungeeCords interner Tab-Handler
@Override public String getName() { return "TablistModule"; } @Override public String getName() { return "TablistModule"; }
@@ -129,13 +130,19 @@ public class TablistModule implements Module, Listener {
} catch (Exception e) { } catch (Exception e) {
plugin.getLogger().warning("[TablistModule] initGridSize Fehler: " + e.getMessage() + " nutze Fallback 3x20"); plugin.getLogger().warning("[TablistModule] initGridSize Fehler: " + e.getMessage() + " nutze Fallback 3x20");
int fbSize = configuredTabSize > 0 ? configuredTabSize : 60; 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(); 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);
// 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."); 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());
@@ -163,14 +170,19 @@ public class TablistModule implements Module, Listener {
private void initGridSize() { private void initGridSize() {
int tabSize = 60; int tabSize = 60;
// FIX: configuredTabSize (aus tablist.properties) hat Vorrang.
// Nur wenn nicht gesetzt, per Reflection aus BungeeCord lesen.
if (configuredTabSize > 0) {
tabSize = configuredTabSize;
} else {
try { try {
for (ListenerInfo li : ProxyServer.getInstance().getConfig().getListeners()) { for (ListenerInfo li : ProxyServer.getInstance().getConfig().getListeners()) {
try { Object v = li.getClass().getMethod("getTabSize").invoke(li); try { Object v = li.getClass().getMethod("getTabListSize").invoke(li);
if (v instanceof Number && ((Number)v).intValue() > 0) { tabSize = ((Number)v).intValue(); break; } if (v instanceof Number && ((Number)v).intValue() > 0) { tabSize = ((Number)v).intValue(); break; }
} catch (Exception ignored) {} } catch (Exception ignored) {}
} }
} catch (Exception ignored) {} } catch (Exception ignored) {}
if (configuredTabSize > 0) tabSize = configuredTabSize; }
tabSizeMax = tabSize; tabSizeMax = tabSize;
rows = ROWS; rows = ROWS;
boolean hasInfo = !"compact".equalsIgnoreCase(layoutMode); 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)); columns = Math.max(hasInfo ? 2 : 1, Math.min(needed, tabSize / ROWS));
total = ROWS * columns; total = ROWS * columns;
if (needed > tabSize / ROWS) { if (needed > tabSize / ROWS) {
plugin.getLogger().warning("[TablistModule] Nicht alle Server passen in die Tablist! " plugin.getLogger().warning("[TablistModule] Nicht alle Server passen in die Tablist!");
+ "Erhöhe tab-size in der BungeeCord config.yml auf mindestens " + (needed * ROWS) plugin.getLogger().warning("[TablistModule] LOESUNG: Setze in BungeeCord config.yml -> tab-list-size: " + (needed * ROWS));
+ " (aktuell: " + tabSize + ")"); 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] 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() { private void initUuids() {
@@ -199,7 +213,9 @@ public class TablistModule implements Module, Listener {
ProxiedPlayer p = e.getPlayer(); ProxiedPlayer p = e.getPlayer();
net.md_5.bungee.protocol.data.Property[] skin = fetchSkin(p); net.md_5.bungee.protocol.data.Property[] skin = fetchSkin(p);
if (skin != null && skin.length > 0) skinCache.put(p.getUniqueId(), skin); if (skin != null && skin.length > 0) skinCache.put(p.getUniqueId(), skin);
disableBungeeTabHandler(p);
ProxyServer.getInstance().getScheduler().schedule(plugin, () -> { ProxyServer.getInstance().getScheduler().schedule(plugin, () -> {
disableBungeeTabHandler(p);
updateTablist(p); updateTablist(p);
ProxyServer.getInstance().getScheduler().schedule(plugin, this::updateAll, 2L, TimeUnit.SECONDS); ProxyServer.getInstance().getScheduler().schedule(plugin, this::updateAll, 2L, TimeUnit.SECONDS);
}, 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); net.md_5.bungee.protocol.data.Property[] skin = fetchSkin(switched);
if (skin != null && skin.length > 0) skinCache.put(switched.getUniqueId(), skin); if (skin != null && skin.length > 0) skinCache.put(switched.getUniqueId(), skin);
disableBungeeTabHandler(switched);
ProxyServer.getInstance().getScheduler().schedule(plugin, () -> { ProxyServer.getInstance().getScheduler().schedule(plugin, () -> {
disableBungeeTabHandler(switched);
net.md_5.bungee.protocol.data.Property[] freshSkin = fetchSkin(switched); net.md_5.bungee.protocol.data.Property[] freshSkin = fetchSkin(switched);
if (freshSkin != null && freshSkin.length > 0) skinCache.put(switched.getUniqueId(), freshSkin); if (freshSkin != null && freshSkin.length > 0) skinCache.put(switched.getUniqueId(), freshSkin);
@@ -269,10 +287,12 @@ public class TablistModule implements Module, Listener {
} else { } else {
int serverCount = getServerOrder().size(); int serverCount = getServerOrder().size();
int needed = (hasInfo ? 1 : 0) + Math.max(1, serverCount); 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; 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()) { for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
try { removeFakeSlots(p); } catch (Exception ignored) {} try { removeFakeSlots(p); } catch (Exception ignored) {}
} }
@@ -286,6 +306,7 @@ public class TablistModule implements Module, Listener {
private void updateTablist(ProxiedPlayer viewer) { private void updateTablist(ProxiedPlayer viewer) {
if (viewer == null || !viewer.isConnected()) return; if (viewer == null || !viewer.isConnected()) return;
try { 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 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);
@@ -307,8 +328,11 @@ public class TablistModule implements Module, Listener {
viewer.setTabHeader( viewer.setTabHeader(
new net.md_5.bungee.api.chat.TextComponent(hComps), new net.md_5.bungee.api.chat.TextComponent(hComps),
new net.md_5.bungee.api.chat.TextComponent(fComps)); 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)); sendSlots(viewer, buildItems(viewer));
hideRealPlayers(viewer);
} catch (Exception ex) { } catch (Exception ex) {
plugin.getLogger().warning("[TablistModule] " + viewer.getName() + ": " + ex.getMessage()); 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; } for (int i = 0; i < total; i++) { texts[i] = " "; skins[i] = EMPTY_SKIN; pings[i] = 0; }
boolean compact = "compact".equalsIgnoreCase(layoutMode); boolean compact = "compact".equalsIgnoreCase(layoutMode);
// Ob der Spalten-Header einen Slot belegt (= "full") oder nicht (= "none"/"small") // Ob der Spalten-Header einen Slot belegt:
boolean useSlotHeader = "full".equalsIgnoreCase(columnHeaderMode); // "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) // Info-Spalte (nur classic)
if (!compact) { if (!compact) {
@@ -476,42 +503,64 @@ 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 {
// 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(); 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: // Server-UUIDs (BungeeCord schreibt pro Server 2 Einträge in die Tablist):
// 1. Echte Spieler // Diese per PlayerListItemRemove entfernen das funktioniert auch für
// 2. BungeeCord-interne Server-Eintraege (werden von BC selbst in die Tablist geschrieben) // UUIDs die der Client noch nicht kennt, ohne Skin-Schaden.
List<UUID> toHide = new ArrayList<>(); List<UUID> serverUuids = 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.
for (String srvName : ProxyServer.getInstance().getServers().keySet()) { for (String srvName : ProxyServer.getInstance().getServers().keySet()) {
try { try {
UUID srvUuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + srvName).getBytes(StandardCharsets.UTF_8)); serverUuids.add(UUID.nameUUIDFromBytes(("OfflinePlayer:" + srvName).getBytes(StandardCharsets.UTF_8)));
toHide.add(srvUuid); serverUuids.add(UUID.nameUUIDFromBytes(srvName.getBytes(StandardCharsets.UTF_8)));
UUID srvUuid2 = UUID.nameUUIDFromBytes(srvName.getBytes(StandardCharsets.UTF_8));
toHide.add(srvUuid2);
} catch (Exception ignored) {} } catch (Exception ignored) {}
} }
if (!serverUuids.isEmpty()) {
if (toHide.isEmpty()) return; try {
PlayerListItemUpdate pkt = new PlayerListItemUpdate(); Class<?> removeClass = Class.forName("net.md_5.bungee.protocol.packet.PlayerListItemRemove");
pkt.setActions(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LISTED)); Object removePkt = removeClass.getDeclaredConstructor().newInstance();
Item[] items = new Item[toHide.size()]; removeClass.getMethod("setUuids", UUID[].class).invoke(removePkt,
int idx = 0; (Object) serverUuids.toArray(new UUID[0]));
for (UUID uuid : toHide) { sendPacketQueuedMethod.invoke(viewer, removePkt);
Item it = new Item(); it.setUuid(uuid); it.setListed(false); items[idx++] = it; } 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()); } } catch (Exception e) { 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;
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(); PlayerListItemUpdate pkt = new PlayerListItemUpdate();
pkt.setActions(EnumSet.of( pkt.setActions(EnumSet.of(
PlayerListItemUpdate.Action.ADD_PLAYER, PlayerListItemUpdate.Action.ADD_PLAYER,
@@ -519,8 +568,8 @@ public class TablistModule implements Module, Listener {
PlayerListItemUpdate.Action.UPDATE_LISTED, PlayerListItemUpdate.Action.UPDATE_LISTED,
PlayerListItemUpdate.Action.UPDATE_LATENCY)); PlayerListItemUpdate.Action.UPDATE_LATENCY));
pkt.setItems(items); pkt.setItems(items);
try { sendPacketQueuedMethod.invoke(viewer, pkt); } sendPacketQueuedMethod.invoke(viewer, pkt);
catch (Exception e) { plugin.getLogger().warning("[TablistModule] sendSlots: " + e.getMessage()); } } catch (Exception e) { plugin.getLogger().warning("[TablistModule] sendSlots: " + e.getMessage()); }
} }
private void removeFakeSlots(ProxiedPlayer viewer) { private void removeFakeSlots(ProxiedPlayer viewer) {
@@ -544,6 +593,34 @@ public class TablistModule implements Module, Listener {
// ── Helpers ──────────────────────────────────────────────────────────────── // ── 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. * ── ULTIMATE: Gibt das konfigurierte Server-Symbol für den Spieler zurück.
* Leer wenn kein Symbol für den aktuellen Server definiert ist. * Leer wenn kein Symbol für den aktuellen Server definiert ist.