Upload folder via GUI - src
This commit is contained in:
@@ -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 {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user