From ac4fc2acbb9087f2c4ac459ef462fe4a07c0f4f0 Mon Sep 17 00:00:00 2001 From: alppp Date: Wed, 20 May 2026 16:58:50 +0300 Subject: [PATCH 1/3] backport modern tab list (1.12.2-style player list) --- .../serverutils/ServerUtilitiesConfig.java | 24 ++ .../client/ServerUtilitiesClient.java | 7 + .../client/ServerUtilitiesClientConfig.java | 20 ++ .../client/tab/ModernTabRenderer.java | 339 ++++++++++++++++++ .../client/tab/TabChannelHandler.java | 71 ++++ .../client/tab/TabDisplayHandler.java | 116 ++++++ .../serverutils/client/tab/TabSkinCache.java | 41 +++ .../ServerUtilitiesClientEventHandler.java | 6 + .../ServerUtilitiesPlayerEventHandler.java | 8 + src/main/java/serverutils/mixin/Mixins.java | 3 + .../forge/MixinGuiIngameForge_ModernTab.java | 54 +++ .../serverutils/net/MessageTabConfig.java | 62 ++++ .../net/ServerUtilitiesNetHandler.java | 1 + 13 files changed, 752 insertions(+) create mode 100644 src/main/java/serverutils/client/tab/ModernTabRenderer.java create mode 100644 src/main/java/serverutils/client/tab/TabChannelHandler.java create mode 100644 src/main/java/serverutils/client/tab/TabDisplayHandler.java create mode 100644 src/main/java/serverutils/client/tab/TabSkinCache.java create mode 100644 src/main/java/serverutils/mixins/early/forge/MixinGuiIngameForge_ModernTab.java create mode 100644 src/main/java/serverutils/net/MessageTabConfig.java diff --git a/src/main/java/serverutils/ServerUtilitiesConfig.java b/src/main/java/serverutils/ServerUtilitiesConfig.java index 5114c580..9d061ac2 100644 --- a/src/main/java/serverutils/ServerUtilitiesConfig.java +++ b/src/main/java/serverutils/ServerUtilitiesConfig.java @@ -43,6 +43,7 @@ public class ServerUtilitiesConfig { public static final Mixins mixins = new Mixins(); public static final MOTD motd = new MOTD(); public static final Transfer transfer = new Transfer(); + public static final Tab tab = new Tab(); public static class General { @@ -811,4 +812,27 @@ public static class Transfer { @Config.DefaultStringList({}) public String[] whitelist; } + + public static class Tab { + + @Config.Comment("Show player head icons in the modern tab overlay.") + @Config.DefaultBoolean(true) + public boolean showPlayerHeads; + + @Config.Comment("Show numeric ping value (e.g. 42ms) next to the signal bars.") + @Config.DefaultBoolean(true) + public boolean showPingNumber; + + @Config.Comment("Show signal bars in the modern tab overlay.") + @Config.DefaultBoolean(true) + public boolean showPingBars; + + @Config.Comment("Header text for the modern tab overlay. Use & for color/format codes, \\n for line breaks. Overridden by proxy plugin channel.") + @Config.DefaultString("&b&lGTNH Server\\n&7A modern tab list for 1.7.10") + public String headerText; + + @Config.Comment("Footer text for the modern tab overlay. Use & for color/format codes, \\n for line breaks. Overridden by proxy plugin channel.") + @Config.DefaultString("&7Ping: &a< 150ms &e< 300ms &c< 600ms &4< 1000ms &8>= 1000ms\\n&8Powered by &dServerUtilities") + public String footerText; + } } diff --git a/src/main/java/serverutils/client/ServerUtilitiesClient.java b/src/main/java/serverutils/client/ServerUtilitiesClient.java index 558663cd..53bab7f7 100644 --- a/src/main/java/serverutils/client/ServerUtilitiesClient.java +++ b/src/main/java/serverutils/client/ServerUtilitiesClient.java @@ -21,6 +21,8 @@ import serverutils.ServerUtilitiesConfig; import serverutils.client.gui.BuiltinChunkMap; import serverutils.client.gui.SidebarButtonManager; +import serverutils.client.tab.TabChannelHandler; +import serverutils.client.tab.TabDisplayHandler; import serverutils.command.client.CommandClientConfig; import serverutils.command.client.CommandKaomoji; import serverutils.command.client.CommandPing; @@ -80,6 +82,11 @@ public void postInit(FMLPostInitializationEvent event) { NavigatorIntegration.init(); } + if (ServerUtilitiesClientConfig.modernTabOverlay) { + TabChannelHandler.INSTANCE.registerChannel(); + TabDisplayHandler.INSTANCE.registerChannel(); + } + if (Loader.isModLoaded("FTBU") || Loader.isModLoaded("FTBL")) { throw new IncompatibleModException(); } diff --git a/src/main/java/serverutils/client/ServerUtilitiesClientConfig.java b/src/main/java/serverutils/client/ServerUtilitiesClientConfig.java index 15f3fcf7..ce3a51d0 100644 --- a/src/main/java/serverutils/client/ServerUtilitiesClientConfig.java +++ b/src/main/java/serverutils/client/ServerUtilitiesClientConfig.java @@ -50,6 +50,26 @@ public class ServerUtilitiesClientConfig { @Config.DefaultInt(18000) public static int button_nighttime; + @Config.Comment("Replaces the vanilla player list (TAB overlay) with a modern-style tab list featuring player heads, dynamic columns, and header/footer support.") + @Config.DefaultBoolean(true) + @Config.RequiresMcRestart + public static boolean modernTabOverlay; + + @Config.Ignore + public static boolean tabShowPlayerHeads = true; + + @Config.Ignore + public static boolean tabShowPingNumber = true; + + @Config.Ignore + public static boolean tabShowPingBars = true; + + @Config.Ignore + public static String tabHeaderText = ""; + + @Config.Ignore + public static String tabFooterText = ""; + @Config.Ignore private static long show_shutdown_timer_ms = -1L; diff --git a/src/main/java/serverutils/client/tab/ModernTabRenderer.java b/src/main/java/serverutils/client/tab/ModernTabRenderer.java new file mode 100644 index 00000000..c2a5a34e --- /dev/null +++ b/src/main/java/serverutils/client/tab/ModernTabRenderer.java @@ -0,0 +1,339 @@ +package serverutils.client.tab; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiPlayerInfo; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.network.NetHandlerPlayClient; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.scoreboard.Score; +import net.minecraft.scoreboard.ScoreObjective; +import net.minecraft.scoreboard.ScorePlayerTeam; +import net.minecraft.scoreboard.Scoreboard; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; + +import org.lwjgl.opengl.GL11; + +import serverutils.client.ServerUtilitiesClientConfig; + +public class ModernTabRenderer { + + public static final ModernTabRenderer INSTANCE = new ModernTabRenderer(); + + private static final int MAX_PLAYERS = 80; + private static final int MAX_ROWS = 20; + private static final int ENTRY_HEIGHT = 9; + private static final int HEAD_SIZE = 8; + private static final int HEAD_PADDING = 9; + private static final int PING_ICON_WIDTH = 10; + private static final int PING_ICON_HEIGHT = 8; + + private static final int ENTRY_BG_COLOR = 553648127; + private static final int OVERLAY_BG_COLOR = Integer.MIN_VALUE; + + private ModernTabRenderer() {} + + public void render(ScaledResolution resolution) { + Minecraft mc = Minecraft.getMinecraft(); + FontRenderer font = mc.fontRenderer; + NetHandlerPlayClient handler = mc.thePlayer.sendQueue; + Scoreboard scoreboard = mc.theWorld.getScoreboard(); + ScoreObjective objective = scoreboard.func_96539_a(0); + + int screenWidth = resolution.getScaledWidth(); + + @SuppressWarnings("unchecked") + List allPlayers = (List) handler.playerInfoList; + int playerCount = Math.min(allPlayers.size(), MAX_PLAYERS); + if (playerCount == 0 && objective == null) return; + + TabDisplayHandler displayHandler = TabDisplayHandler.INSTANCE; + + List players; + Map sortMap = displayHandler.getSortIndexMap(); + if (sortMap != null && !sortMap.isEmpty()) { + players = new ArrayList<>(allPlayers.subList(0, playerCount)); + players.sort((a, b) -> { + Integer idxA = sortMap.get(a.name); + Integer idxB = sortMap.get(b.name); + if (idxA == null && idxB == null) return a.name.compareToIgnoreCase(b.name); + if (idxA == null) return 1; + if (idxB == null) return -1; + return Integer.compare(idxA, idxB); + }); + } else { + players = allPlayers.subList(0, playerCount); + } + + int maxNameWidth = 0; + for (GuiPlayerInfo player : players) { + String customName = displayHandler.getDisplayName(player.name); + String displayName; + if (customName != null) { + displayName = customName; + } else { + ScorePlayerTeam team = scoreboard.getPlayersTeam(player.name); + displayName = ScorePlayerTeam.formatPlayerName(team, player.name); + } + int w = font.getStringWidth(displayName); + if (w > maxNameWidth) maxNameWidth = w; + } + + int maxScoreWidth = 0; + if (objective != null) { + for (GuiPlayerInfo player : players) { + Score score = objective.getScoreboard().func_96529_a(player.name, objective); + String scoreStr = EnumChatFormatting.YELLOW + "" + score.getScorePoints(); + int w = font.getStringWidth(scoreStr); + if (w > maxScoreWidth) maxScoreWidth = w; + } + maxScoreWidth += 5; + } + + boolean showPingNumber = ServerUtilitiesClientConfig.tabShowPingNumber; + boolean showPingBars = ServerUtilitiesClientConfig.tabShowPingBars; + int maxPingTextWidth = 0; + if (showPingNumber) { + for (GuiPlayerInfo player : players) { + String pingText = formatPing(player.responseTime); + int w = font.getStringWidth(pingText); + if (w > maxPingTextWidth) maxPingTextWidth = w; + } + maxPingTextWidth += 2; + } + + int maxSuffixWidth = 0; + for (GuiPlayerInfo player : players) { + String suffix = displayHandler.getSuffix(player.name); + if (suffix != null) { + int w = font.getStringWidth(suffix); + if (w > maxSuffixWidth) maxSuffixWidth = w; + } + } + if (maxSuffixWidth > 0) maxSuffixWidth += 5; + + int columns = 1; + int rows = playerCount; + while (rows > MAX_ROWS) { + columns++; + rows = (playerCount + columns - 1) / columns; + } + + boolean showHeads = ServerUtilitiesClientConfig.tabShowPlayerHeads; + int headSpace = showHeads ? HEAD_PADDING : 0; + + int pingBarSpace = showPingBars ? PING_ICON_WIDTH + 1 : 0; + int entryWidth = headSpace + maxNameWidth + + maxScoreWidth + + maxSuffixWidth + + maxPingTextWidth + + pingBarSpace + + 3; + int totalGridWidth = Math.min(columns * entryWidth, screenWidth - 50); + int columnWidth = totalGridWidth / columns; + int gridLeft = (screenWidth - (columnWidth * columns + (columns - 1) * 5)) / 2; + int startY = 10; + + String headerText = resolveHeaderFooter(true); + String footerText = resolveHeaderFooter(false); + + List headerLines = wrapText(font, headerText, screenWidth - 50); + List footerLines = wrapText(font, footerText, screenWidth - 50); + + int headerMaxWidth = getMaxLineWidth(font, headerLines); + int footerMaxWidth = getMaxLineWidth(font, footerLines); + int gridWidth = columnWidth * columns + (columns - 1) * 5; + int bgWidth = Math.max(gridWidth, Math.max(headerMaxWidth, footerMaxWidth)); + + int centerX = screenWidth / 2; + + if (!headerLines.isEmpty()) { + int headerHeight = headerLines.size() * font.FONT_HEIGHT; + Gui.drawRect( + centerX - bgWidth / 2 - 1, + startY - 1, + centerX + bgWidth / 2 + 1, + startY + headerHeight, + OVERLAY_BG_COLOR); + for (String line : headerLines) { + int lineWidth = font.getStringWidth(line); + font.drawStringWithShadow(line, centerX - lineWidth / 2, startY, -1); + startY += font.FONT_HEIGHT; + } + startY += 1; + } + + Gui.drawRect( + centerX - bgWidth / 2 - 1, + startY - 1, + centerX + bgWidth / 2 + 1, + startY + rows * ENTRY_HEIGHT, + OVERLAY_BG_COLOR); + + Set activeNames = new HashSet<>(); + + for (int i = 0; i < playerCount; i++) { + int col = i / rows; + int row = i % rows; + int x = gridLeft + col * (columnWidth + 5); + int y = startY + row * ENTRY_HEIGHT; + + GuiPlayerInfo player = players.get(i); + activeNames.add(player.name); + + String customName = displayHandler.getDisplayName(player.name); + String displayName; + if (customName != null) { + displayName = customName; + } else { + ScorePlayerTeam team = scoreboard.getPlayersTeam(player.name); + displayName = ScorePlayerTeam.formatPlayerName(team, player.name); + } + + Gui.drawRect(x, y, x + columnWidth, y + 8, ENTRY_BG_COLOR); + + GL11.glEnable(GL11.GL_ALPHA_TEST); + GL11.glEnable(GL11.GL_BLEND); + OpenGlHelper.glBlendFunc(770, 771, 1, 0); + + int textX = x; + + if (showHeads) { + ResourceLocation skinLoc = TabSkinCache.INSTANCE.getOrLoadSkin(player.name); + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + Minecraft.getMinecraft().getTextureManager().bindTexture(skinLoc); + Gui.func_152125_a(x, y, 8.0F, 8.0F, 8, 8, HEAD_SIZE, HEAD_SIZE, 64.0F, 32.0F); + Gui.func_152125_a(x, y, 40.0F, 8.0F, 8, 8, HEAD_SIZE, HEAD_SIZE, 64.0F, 32.0F); + textX += HEAD_PADDING; + } + + font.drawStringWithShadow(displayName, textX, y, -1); + + if (objective != null) { + int nameEndX = textX + font.getStringWidth(displayName) + 5; + int scoreEndX = x + columnWidth - pingBarSpace - 2 - 5; + if (scoreEndX - nameEndX > 5) { + Score score = objective.getScoreboard().func_96529_a(player.name, objective); + String scoreStr = EnumChatFormatting.YELLOW + "" + score.getScorePoints(); + font.drawStringWithShadow(scoreStr, scoreEndX - font.getStringWidth(scoreStr), y, -1); + } + } + + String suffix = displayHandler.getSuffix(player.name); + if (suffix != null) { + int suffixRight = x + columnWidth - pingBarSpace - maxPingTextWidth - 2; + font.drawStringWithShadow(suffix, suffixRight - font.getStringWidth(suffix), y, -1); + } + + if (showPingNumber) { + String pingText = formatPing(player.responseTime); + int pingColor = getPingColor(player.responseTime); + int pingTextX = x + columnWidth - pingBarSpace - font.getStringWidth(pingText) - 2; + font.drawStringWithShadow(pingText, pingTextX, y, pingColor); + } + + if (showPingBars) { + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + Minecraft.getMinecraft().getTextureManager().bindTexture(Gui.icons); + int pingLevel = getPingLevel(player.responseTime); + Gui.func_146110_a( + x + columnWidth - PING_ICON_WIDTH - 1, + y, + 0, + 176 + pingLevel * 8, + PING_ICON_WIDTH, + PING_ICON_HEIGHT, + 256, + 256); + } + } + + if (!footerLines.isEmpty()) { + int footerY = startY + rows * ENTRY_HEIGHT + 1; + int footerHeight = footerLines.size() * font.FONT_HEIGHT; + Gui.drawRect( + centerX - bgWidth / 2 - 1, + footerY - 1, + centerX + bgWidth / 2 + 1, + footerY + footerHeight, + OVERLAY_BG_COLOR); + for (String line : footerLines) { + int lineWidth = font.getStringWidth(line); + font.drawStringWithShadow(line, centerX - lineWidth / 2, footerY, -1); + footerY += font.FONT_HEIGHT; + } + } + + TabSkinCache.INSTANCE.cleanup(activeNames); + + GL11.glDisable(GL11.GL_BLEND); + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + } + + private static String resolveHeaderFooter(boolean isHeader) { + String text; + TabChannelHandler ch = TabChannelHandler.INSTANCE; + if (ch.hasServerData()) { + text = isHeader ? ch.getHeader() : ch.getFooter(); + } else { + text = isHeader ? ServerUtilitiesClientConfig.tabHeaderText : ServerUtilitiesClientConfig.tabFooterText; + } + if (text == null || text.isEmpty()) return ""; + return text.replace("\\n", "\n").replaceAll("&([0-9a-fk-or])", "§$1"); + } + + private static List wrapText(FontRenderer font, String text, int maxWidth) { + List lines = new ArrayList<>(); + if (text == null || text.isEmpty()) return lines; + + for (String segment : text.split("\n", -1)) { + if (segment.isEmpty()) { + lines.add(""); + continue; + } + @SuppressWarnings("unchecked") + List wrapped = font.listFormattedStringToWidth(segment, maxWidth); + lines.addAll(wrapped); + } + return lines; + } + + private static int getMaxLineWidth(FontRenderer font, List lines) { + int max = 0; + for (String line : lines) { + int w = font.getStringWidth(line); + if (w > max) max = w; + } + return max; + } + + private static int getPingLevel(int ping) { + if (ping < 0) return 5; + if (ping < 150) return 0; + if (ping < 300) return 1; + if (ping < 600) return 2; + if (ping < 1000) return 3; + return 4; + } + + private static String formatPing(int ping) { + return ping < 0 ? "?" : ping + "ms"; + } + + private static int getPingColor(int ping) { + if (ping < 0) return 0xAAAAAA; + if (ping < 150) return 0x55FF55; + if (ping < 300) return 0xAAFF55; + if (ping < 600) return 0xFFFF55; + if (ping < 1000) return 0xFF5555; + return 0xAA0000; + } +} diff --git a/src/main/java/serverutils/client/tab/TabChannelHandler.java b/src/main/java/serverutils/client/tab/TabChannelHandler.java new file mode 100644 index 00000000..2b97206f --- /dev/null +++ b/src/main/java/serverutils/client/tab/TabChannelHandler.java @@ -0,0 +1,71 @@ +package serverutils.client.tab; + +import java.nio.charset.StandardCharsets; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.network.FMLEventChannel; +import cpw.mods.fml.common.network.FMLNetworkEvent; +import cpw.mods.fml.common.network.NetworkRegistry; +import io.netty.buffer.ByteBuf; +import serverutils.ServerUtilities; + +public class TabChannelHandler { + + public static final String CHANNEL_NAME = "HP|TabHF"; + public static final TabChannelHandler INSTANCE = new TabChannelHandler(); + + private String header = ""; + private String footer = ""; + private boolean hasServerData = false; + + private TabChannelHandler() {} + + public void registerChannel() { + FMLEventChannel channel = NetworkRegistry.INSTANCE.newEventDrivenChannel(CHANNEL_NAME); + channel.register(this); + } + + @SubscribeEvent + public void onClientPacket(FMLNetworkEvent.ClientCustomPacketEvent event) { + try { + ByteBuf payload = event.packet.payload(); + byte[] bytes = new byte[payload.readableBytes()]; + payload.readBytes(bytes); + String json = new String(bytes, StandardCharsets.UTF_8); + + JsonObject obj = new JsonParser().parse(json).getAsJsonObject(); + String h = obj.has("header") ? obj.get("header").getAsString() : ""; + String f = obj.has("footer") ? obj.get("footer").getAsString() : ""; + setServerData(h, f); + } catch (Exception e) { + ServerUtilities.LOGGER.warn("Failed to parse HP|TabHF packet", e); + } + } + + public void setServerData(String header, String footer) { + this.header = header != null ? header : ""; + this.footer = footer != null ? footer : ""; + this.hasServerData = true; + } + + public void clear() { + header = ""; + footer = ""; + hasServerData = false; + } + + public String getHeader() { + return header; + } + + public String getFooter() { + return footer; + } + + public boolean hasServerData() { + return hasServerData; + } +} diff --git a/src/main/java/serverutils/client/tab/TabDisplayHandler.java b/src/main/java/serverutils/client/tab/TabDisplayHandler.java new file mode 100644 index 00000000..7ffdd15b --- /dev/null +++ b/src/main/java/serverutils/client/tab/TabDisplayHandler.java @@ -0,0 +1,116 @@ +package serverutils.client.tab; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.network.FMLEventChannel; +import cpw.mods.fml.common.network.FMLNetworkEvent; +import cpw.mods.fml.common.network.NetworkRegistry; +import io.netty.buffer.ByteBuf; +import serverutils.ServerUtilities; + +public class TabDisplayHandler { + + public static final String CHANNEL_NAME = "HP|TabUD"; + public static final TabDisplayHandler INSTANCE = new TabDisplayHandler(); + + private final Map displayNames = new ConcurrentHashMap<>(); + private final Map suffixes = new ConcurrentHashMap<>(); + private volatile Map sortIndexMap; + + private TabDisplayHandler() {} + + public void registerChannel() { + FMLEventChannel channel = NetworkRegistry.INSTANCE.newEventDrivenChannel(CHANNEL_NAME); + channel.register(this); + } + + @SubscribeEvent + public void onClientPacket(FMLNetworkEvent.ClientCustomPacketEvent event) { + try { + ByteBuf payload = event.packet.payload(); + byte[] bytes = new byte[payload.readableBytes()]; + payload.readBytes(bytes); + String json = new String(bytes, StandardCharsets.UTF_8); + + JsonObject obj = new JsonParser().parse(json).getAsJsonObject(); + String action = obj.get("action").getAsString(); + + switch (action) { + case "update": + handleUpdate(obj); + break; + case "remove": + handleRemove(obj); + break; + case "sort": + handleSort(obj); + break; + default: + ServerUtilities.LOGGER.warn("Unknown HP|TabUD action: {}", action); + } + } catch (Exception e) { + ServerUtilities.LOGGER.warn("Failed to parse HP|TabUD packet", e); + } + } + + private void handleUpdate(JsonObject obj) { + JsonObject players = obj.getAsJsonObject("players"); + for (Map.Entry entry : players.entrySet()) { + String name = entry.getKey(); + JsonObject data = entry.getValue().getAsJsonObject(); + if (data.has("displayName")) { + displayNames.put(name, data.get("displayName").getAsString()); + } + if (data.has("suffix")) { + suffixes.put(name, data.get("suffix").getAsString()); + } else { + suffixes.remove(name); + } + } + } + + private void handleRemove(JsonObject obj) { + JsonArray players = obj.getAsJsonArray("players"); + for (JsonElement elem : players) { + String name = elem.getAsString(); + displayNames.remove(name); + suffixes.remove(name); + } + } + + private void handleSort(JsonObject obj) { + JsonArray order = obj.getAsJsonArray("order"); + Map indexMap = new HashMap<>(); + for (int i = 0; i < order.size(); i++) { + indexMap.put(order.get(i).getAsString(), i); + } + sortIndexMap = indexMap; + } + + public String getDisplayName(String playerName) { + return displayNames.get(playerName); + } + + public String getSuffix(String playerName) { + return suffixes.get(playerName); + } + + public Map getSortIndexMap() { + return sortIndexMap; + } + + public void clear() { + displayNames.clear(); + suffixes.clear(); + sortIndexMap = null; + } +} diff --git a/src/main/java/serverutils/client/tab/TabSkinCache.java b/src/main/java/serverutils/client/tab/TabSkinCache.java new file mode 100644 index 00000000..0987fadd --- /dev/null +++ b/src/main/java/serverutils/client/tab/TabSkinCache.java @@ -0,0 +1,41 @@ +package serverutils.client.tab; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import net.minecraft.client.entity.AbstractClientPlayer; +import net.minecraft.util.ResourceLocation; + +public class TabSkinCache { + + public static final TabSkinCache INSTANCE = new TabSkinCache(); + + private final Map cache = new HashMap<>(); + + private TabSkinCache() {} + + public ResourceLocation getOrLoadSkin(String playerName) { + ResourceLocation loc = cache.get(playerName); + if (loc == null) { + loc = AbstractClientPlayer.getLocationSkin(playerName); + AbstractClientPlayer.getDownloadImageSkin(loc, playerName); + cache.put(playerName, loc); + } + return loc; + } + + public void cleanup(Collection activePlayers) { + Iterator it = cache.keySet().iterator(); + while (it.hasNext()) { + if (!activePlayers.contains(it.next())) { + it.remove(); + } + } + } + + public void clear() { + cache.clear(); + } +} diff --git a/src/main/java/serverutils/handlers/ServerUtilitiesClientEventHandler.java b/src/main/java/serverutils/handlers/ServerUtilitiesClientEventHandler.java index 05a1e3b2..9d382bcc 100644 --- a/src/main/java/serverutils/handlers/ServerUtilitiesClientEventHandler.java +++ b/src/main/java/serverutils/handlers/ServerUtilitiesClientEventHandler.java @@ -27,6 +27,9 @@ import serverutils.client.ServerUtilitiesClientConfig; import serverutils.client.gui.GuiClaimedChunks; import serverutils.client.gui.GuiClientConfig; +import serverutils.client.tab.TabChannelHandler; +import serverutils.client.tab.TabDisplayHandler; +import serverutils.client.tab.TabSkinCache; import serverutils.events.client.CustomClickEvent; import serverutils.integration.navigator.NavigatorIntegration; import serverutils.lib.OtherMods; @@ -62,6 +65,9 @@ public static void onClientDisconnected(FMLNetworkEvent.ClientDisconnectionFromS if (OtherMods.isNavigatorLoaded()) { NavigatorIntegration.CLAIMS.clear(); } + TabSkinCache.INSTANCE.clear(); + TabChannelHandler.INSTANCE.clear(); + TabDisplayHandler.INSTANCE.clear(); } @SubscribeEvent diff --git a/src/main/java/serverutils/handlers/ServerUtilitiesPlayerEventHandler.java b/src/main/java/serverutils/handlers/ServerUtilitiesPlayerEventHandler.java index 352b0c81..9cfec2d4 100644 --- a/src/main/java/serverutils/handlers/ServerUtilitiesPlayerEventHandler.java +++ b/src/main/java/serverutils/handlers/ServerUtilitiesPlayerEventHandler.java @@ -47,6 +47,7 @@ import serverutils.lib.util.ServerUtils; import serverutils.lib.util.StringUtils; import serverutils.lib.util.permission.PermissionAPI; +import serverutils.net.MessageTabConfig; import serverutils.net.MessageUpdateTabName; import serverutils.task.backup.BackupTask; @@ -92,6 +93,13 @@ public static void onPlayerLoggedIn(ForgePlayerLoggedInEvent event) { new MessageUpdateTabName(Universe.get().getOnlinePlayers()).sendTo(player); } + new MessageTabConfig( + ServerUtilitiesConfig.tab.headerText, + ServerUtilitiesConfig.tab.footerText, + ServerUtilitiesConfig.tab.showPlayerHeads, + ServerUtilitiesConfig.tab.showPingNumber, + ServerUtilitiesConfig.tab.showPingBars).sendTo(player); + BackupTask.hadPlayer = true; } diff --git a/src/main/java/serverutils/mixin/Mixins.java b/src/main/java/serverutils/mixin/Mixins.java index c339c0e3..6a55f3b3 100644 --- a/src/main/java/serverutils/mixin/Mixins.java +++ b/src/main/java/serverutils/mixin/Mixins.java @@ -87,6 +87,9 @@ public enum Mixins implements IMixins { .setPhase(Phase.LATE) .setApplyIf(() -> ranks.enabled && ranks.command_permissions) .addCommonMixins("brigadier.MixinCommandNode", "brigadier.MixinLiteralCommandNode")), + MODERN_TAB_OVERLAY(new MixinBuilder("Modern-style player list overlay") + .setPhase(Phase.EARLY) + .addClientMixins("forge.MixinGuiIngameForge_ModernTab")), ; // spotless:on diff --git a/src/main/java/serverutils/mixins/early/forge/MixinGuiIngameForge_ModernTab.java b/src/main/java/serverutils/mixins/early/forge/MixinGuiIngameForge_ModernTab.java new file mode 100644 index 00000000..932b3aa7 --- /dev/null +++ b/src/main/java/serverutils/mixins/early/forge/MixinGuiIngameForge_ModernTab.java @@ -0,0 +1,54 @@ +package serverutils.mixins.early.forge; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiIngame; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.network.NetHandlerPlayClient; +import net.minecraft.scoreboard.ScoreObjective; +import net.minecraftforge.client.GuiIngameForge; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.common.MinecraftForge; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import serverutils.client.ServerUtilitiesClientConfig; +import serverutils.client.tab.ModernTabRenderer; + +@Mixin(GuiIngameForge.class) +public class MixinGuiIngameForge_ModernTab extends GuiIngame { + + @Shadow(remap = false) + private ScaledResolution res; + + @Shadow(remap = false) + private RenderGameOverlayEvent eventParent; + + private MixinGuiIngameForge_ModernTab(Minecraft mc) { + super(mc); + } + + @SuppressWarnings("unchecked") + @Inject(method = "renderPlayerList", at = @At("HEAD"), cancellable = true, remap = false) + private void serverutilities$modernTab(int width, int height, CallbackInfo ci) { + if (!ServerUtilitiesClientConfig.modernTabOverlay) return; + ci.cancel(); + + ScoreObjective scoreobjective = mc.theWorld.getScoreboard().func_96539_a(0); + NetHandlerPlayClient handler = mc.thePlayer.sendQueue; + + if (mc.gameSettings.keyBindPlayerList.getIsKeyPressed() + && (!mc.isIntegratedServerRunning() || handler.playerInfoList.size() > 1 || scoreobjective != null)) { + if (MinecraftForge.EVENT_BUS + .post(new RenderGameOverlayEvent.Pre(eventParent, RenderGameOverlayEvent.ElementType.PLAYER_LIST))) + return; + mc.mcProfiler.startSection("playerList"); + ModernTabRenderer.INSTANCE.render(res); + MinecraftForge.EVENT_BUS + .post(new RenderGameOverlayEvent.Post(eventParent, RenderGameOverlayEvent.ElementType.PLAYER_LIST)); + } + } +} diff --git a/src/main/java/serverutils/net/MessageTabConfig.java b/src/main/java/serverutils/net/MessageTabConfig.java new file mode 100644 index 00000000..5db74ab5 --- /dev/null +++ b/src/main/java/serverutils/net/MessageTabConfig.java @@ -0,0 +1,62 @@ +package serverutils.net; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import serverutils.client.ServerUtilitiesClientConfig; +import serverutils.client.tab.TabChannelHandler; +import serverutils.lib.io.DataIn; +import serverutils.lib.io.DataOut; +import serverutils.lib.net.MessageToClient; +import serverutils.lib.net.NetworkWrapper; + +public class MessageTabConfig extends MessageToClient { + + private String tabHeaderText; + private String tabFooterText; + private boolean tabShowPlayerHeads; + private boolean tabShowPingNumber; + private boolean tabShowPingBars; + + public MessageTabConfig() {} + + public MessageTabConfig(String headerText, String footerText, boolean showHeads, boolean showPingNumber, + boolean showPingBars) { + this.tabHeaderText = headerText != null ? headerText : ""; + this.tabFooterText = footerText != null ? footerText : ""; + this.tabShowPlayerHeads = showHeads; + this.tabShowPingNumber = showPingNumber; + this.tabShowPingBars = showPingBars; + } + + @Override + public NetworkWrapper getWrapper() { + return ServerUtilitiesNetHandler.GENERAL; + } + + @Override + public void writeData(DataOut data) { + data.writeString(tabHeaderText); + data.writeString(tabFooterText); + data.writeBoolean(tabShowPlayerHeads); + data.writeBoolean(tabShowPingNumber); + data.writeBoolean(tabShowPingBars); + } + + @Override + public void readData(DataIn data) { + tabHeaderText = data.readString(); + tabFooterText = data.readString(); + tabShowPlayerHeads = data.readBoolean(); + tabShowPingNumber = data.readBoolean(); + tabShowPingBars = data.readBoolean(); + } + + @Override + @SideOnly(Side.CLIENT) + public void onMessage() { + TabChannelHandler.INSTANCE.setServerData(tabHeaderText, tabFooterText); + ServerUtilitiesClientConfig.tabShowPlayerHeads = tabShowPlayerHeads; + ServerUtilitiesClientConfig.tabShowPingNumber = tabShowPingNumber; + ServerUtilitiesClientConfig.tabShowPingBars = tabShowPingBars; + } +} diff --git a/src/main/java/serverutils/net/ServerUtilitiesNetHandler.java b/src/main/java/serverutils/net/ServerUtilitiesNetHandler.java index 80bb7240..acfe2e6a 100644 --- a/src/main/java/serverutils/net/ServerUtilitiesNetHandler.java +++ b/src/main/java/serverutils/net/ServerUtilitiesNetHandler.java @@ -26,6 +26,7 @@ public static void init() { GENERAL.register(new MessageInvseeContainer()); GENERAL.register(new MessageInvseeSwitch()); GENERAL.register(new MessageTransfer()); + GENERAL.register(new MessageTabConfig()); CLAIMS.register(new MessageClaimedChunksRequest()); CLAIMS.register(new MessageClaimedChunksUpdate()); From 38d81debf66d6ba7f7dba4040e1621064dc9b209 Mon Sep 17 00:00:00 2001 From: alppp Date: Fri, 5 Jun 2026 19:26:42 +0300 Subject: [PATCH 2/3] use @Config.Sync for tab settings and fix some tab list bugs --- .../serverutils/ServerUtilitiesConfig.java | 5 ++ .../client/ServerUtilitiesClientConfig.java | 15 ----- .../client/tab/ModernTabRenderer.java | 17 ++--- .../client/tab/TabChannelHandler.java | 6 +- .../serverutils/client/tab/TabSkinCache.java | 4 +- .../ServerUtilitiesPlayerEventHandler.java | 8 --- .../serverutils/net/MessageTabConfig.java | 62 ------------------- .../net/ServerUtilitiesNetHandler.java | 1 - 8 files changed, 19 insertions(+), 99 deletions(-) delete mode 100644 src/main/java/serverutils/net/MessageTabConfig.java diff --git a/src/main/java/serverutils/ServerUtilitiesConfig.java b/src/main/java/serverutils/ServerUtilitiesConfig.java index 9d061ac2..d59b2660 100644 --- a/src/main/java/serverutils/ServerUtilitiesConfig.java +++ b/src/main/java/serverutils/ServerUtilitiesConfig.java @@ -817,22 +817,27 @@ public static class Tab { @Config.Comment("Show player head icons in the modern tab overlay.") @Config.DefaultBoolean(true) + @Config.Sync public boolean showPlayerHeads; @Config.Comment("Show numeric ping value (e.g. 42ms) next to the signal bars.") @Config.DefaultBoolean(true) + @Config.Sync public boolean showPingNumber; @Config.Comment("Show signal bars in the modern tab overlay.") @Config.DefaultBoolean(true) + @Config.Sync public boolean showPingBars; @Config.Comment("Header text for the modern tab overlay. Use & for color/format codes, \\n for line breaks. Overridden by proxy plugin channel.") @Config.DefaultString("&b&lGTNH Server\\n&7A modern tab list for 1.7.10") + @Config.Sync public String headerText; @Config.Comment("Footer text for the modern tab overlay. Use & for color/format codes, \\n for line breaks. Overridden by proxy plugin channel.") @Config.DefaultString("&7Ping: &a< 150ms &e< 300ms &c< 600ms &4< 1000ms &8>= 1000ms\\n&8Powered by &dServerUtilities") + @Config.Sync public String footerText; } } diff --git a/src/main/java/serverutils/client/ServerUtilitiesClientConfig.java b/src/main/java/serverutils/client/ServerUtilitiesClientConfig.java index ce3a51d0..526d955c 100644 --- a/src/main/java/serverutils/client/ServerUtilitiesClientConfig.java +++ b/src/main/java/serverutils/client/ServerUtilitiesClientConfig.java @@ -55,21 +55,6 @@ public class ServerUtilitiesClientConfig { @Config.RequiresMcRestart public static boolean modernTabOverlay; - @Config.Ignore - public static boolean tabShowPlayerHeads = true; - - @Config.Ignore - public static boolean tabShowPingNumber = true; - - @Config.Ignore - public static boolean tabShowPingBars = true; - - @Config.Ignore - public static String tabHeaderText = ""; - - @Config.Ignore - public static String tabFooterText = ""; - @Config.Ignore private static long show_shutdown_timer_ms = -1L; diff --git a/src/main/java/serverutils/client/tab/ModernTabRenderer.java b/src/main/java/serverutils/client/tab/ModernTabRenderer.java index c2a5a34e..15c5b8f0 100644 --- a/src/main/java/serverutils/client/tab/ModernTabRenderer.java +++ b/src/main/java/serverutils/client/tab/ModernTabRenderer.java @@ -22,7 +22,8 @@ import org.lwjgl.opengl.GL11; -import serverutils.client.ServerUtilitiesClientConfig; +import serverutils.ServerUtilitiesConfig; +import serverutils.lib.util.StringUtils; public class ModernTabRenderer { @@ -70,7 +71,7 @@ public void render(ScaledResolution resolution) { return Integer.compare(idxA, idxB); }); } else { - players = allPlayers.subList(0, playerCount); + players = new ArrayList<>(allPlayers.subList(0, playerCount)); } int maxNameWidth = 0; @@ -98,8 +99,8 @@ public void render(ScaledResolution resolution) { maxScoreWidth += 5; } - boolean showPingNumber = ServerUtilitiesClientConfig.tabShowPingNumber; - boolean showPingBars = ServerUtilitiesClientConfig.tabShowPingBars; + boolean showPingNumber = ServerUtilitiesConfig.tab.showPingNumber; + boolean showPingBars = ServerUtilitiesConfig.tab.showPingBars; int maxPingTextWidth = 0; if (showPingNumber) { for (GuiPlayerInfo player : players) { @@ -127,7 +128,7 @@ public void render(ScaledResolution resolution) { rows = (playerCount + columns - 1) / columns; } - boolean showHeads = ServerUtilitiesClientConfig.tabShowPlayerHeads; + boolean showHeads = ServerUtilitiesConfig.tab.showPlayerHeads; int headSpace = showHeads ? HEAD_PADDING : 0; int pingBarSpace = showPingBars ? PING_ICON_WIDTH + 1 : 0; @@ -219,7 +220,7 @@ public void render(ScaledResolution resolution) { if (objective != null) { int nameEndX = textX + font.getStringWidth(displayName) + 5; - int scoreEndX = x + columnWidth - pingBarSpace - 2 - 5; + int scoreEndX = x + columnWidth - pingBarSpace - maxPingTextWidth - maxSuffixWidth - 2; if (scoreEndX - nameEndX > 5) { Score score = objective.getScoreboard().func_96529_a(player.name, objective); String scoreStr = EnumChatFormatting.YELLOW + "" + score.getScorePoints(); @@ -284,10 +285,10 @@ private static String resolveHeaderFooter(boolean isHeader) { if (ch.hasServerData()) { text = isHeader ? ch.getHeader() : ch.getFooter(); } else { - text = isHeader ? ServerUtilitiesClientConfig.tabHeaderText : ServerUtilitiesClientConfig.tabFooterText; + text = isHeader ? ServerUtilitiesConfig.tab.headerText : ServerUtilitiesConfig.tab.footerText; } if (text == null || text.isEmpty()) return ""; - return text.replace("\\n", "\n").replaceAll("&([0-9a-fk-or])", "§$1"); + return StringUtils.addFormatting(text.replace("\\n", "\n")); } private static List wrapText(FontRenderer font, String text, int maxWidth) { diff --git a/src/main/java/serverutils/client/tab/TabChannelHandler.java b/src/main/java/serverutils/client/tab/TabChannelHandler.java index 2b97206f..d021eee9 100644 --- a/src/main/java/serverutils/client/tab/TabChannelHandler.java +++ b/src/main/java/serverutils/client/tab/TabChannelHandler.java @@ -17,9 +17,9 @@ public class TabChannelHandler { public static final String CHANNEL_NAME = "HP|TabHF"; public static final TabChannelHandler INSTANCE = new TabChannelHandler(); - private String header = ""; - private String footer = ""; - private boolean hasServerData = false; + private volatile String header = ""; + private volatile String footer = ""; + private volatile boolean hasServerData = false; private TabChannelHandler() {} diff --git a/src/main/java/serverutils/client/tab/TabSkinCache.java b/src/main/java/serverutils/client/tab/TabSkinCache.java index 0987fadd..da360b91 100644 --- a/src/main/java/serverutils/client/tab/TabSkinCache.java +++ b/src/main/java/serverutils/client/tab/TabSkinCache.java @@ -1,9 +1,9 @@ package serverutils.client.tab; import java.util.Collection; -import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import net.minecraft.client.entity.AbstractClientPlayer; import net.minecraft.util.ResourceLocation; @@ -12,7 +12,7 @@ public class TabSkinCache { public static final TabSkinCache INSTANCE = new TabSkinCache(); - private final Map cache = new HashMap<>(); + private final Map cache = new ConcurrentHashMap<>(); private TabSkinCache() {} diff --git a/src/main/java/serverutils/handlers/ServerUtilitiesPlayerEventHandler.java b/src/main/java/serverutils/handlers/ServerUtilitiesPlayerEventHandler.java index 9cfec2d4..352b0c81 100644 --- a/src/main/java/serverutils/handlers/ServerUtilitiesPlayerEventHandler.java +++ b/src/main/java/serverutils/handlers/ServerUtilitiesPlayerEventHandler.java @@ -47,7 +47,6 @@ import serverutils.lib.util.ServerUtils; import serverutils.lib.util.StringUtils; import serverutils.lib.util.permission.PermissionAPI; -import serverutils.net.MessageTabConfig; import serverutils.net.MessageUpdateTabName; import serverutils.task.backup.BackupTask; @@ -93,13 +92,6 @@ public static void onPlayerLoggedIn(ForgePlayerLoggedInEvent event) { new MessageUpdateTabName(Universe.get().getOnlinePlayers()).sendTo(player); } - new MessageTabConfig( - ServerUtilitiesConfig.tab.headerText, - ServerUtilitiesConfig.tab.footerText, - ServerUtilitiesConfig.tab.showPlayerHeads, - ServerUtilitiesConfig.tab.showPingNumber, - ServerUtilitiesConfig.tab.showPingBars).sendTo(player); - BackupTask.hadPlayer = true; } diff --git a/src/main/java/serverutils/net/MessageTabConfig.java b/src/main/java/serverutils/net/MessageTabConfig.java deleted file mode 100644 index 5db74ab5..00000000 --- a/src/main/java/serverutils/net/MessageTabConfig.java +++ /dev/null @@ -1,62 +0,0 @@ -package serverutils.net; - -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; -import serverutils.client.ServerUtilitiesClientConfig; -import serverutils.client.tab.TabChannelHandler; -import serverutils.lib.io.DataIn; -import serverutils.lib.io.DataOut; -import serverutils.lib.net.MessageToClient; -import serverutils.lib.net.NetworkWrapper; - -public class MessageTabConfig extends MessageToClient { - - private String tabHeaderText; - private String tabFooterText; - private boolean tabShowPlayerHeads; - private boolean tabShowPingNumber; - private boolean tabShowPingBars; - - public MessageTabConfig() {} - - public MessageTabConfig(String headerText, String footerText, boolean showHeads, boolean showPingNumber, - boolean showPingBars) { - this.tabHeaderText = headerText != null ? headerText : ""; - this.tabFooterText = footerText != null ? footerText : ""; - this.tabShowPlayerHeads = showHeads; - this.tabShowPingNumber = showPingNumber; - this.tabShowPingBars = showPingBars; - } - - @Override - public NetworkWrapper getWrapper() { - return ServerUtilitiesNetHandler.GENERAL; - } - - @Override - public void writeData(DataOut data) { - data.writeString(tabHeaderText); - data.writeString(tabFooterText); - data.writeBoolean(tabShowPlayerHeads); - data.writeBoolean(tabShowPingNumber); - data.writeBoolean(tabShowPingBars); - } - - @Override - public void readData(DataIn data) { - tabHeaderText = data.readString(); - tabFooterText = data.readString(); - tabShowPlayerHeads = data.readBoolean(); - tabShowPingNumber = data.readBoolean(); - tabShowPingBars = data.readBoolean(); - } - - @Override - @SideOnly(Side.CLIENT) - public void onMessage() { - TabChannelHandler.INSTANCE.setServerData(tabHeaderText, tabFooterText); - ServerUtilitiesClientConfig.tabShowPlayerHeads = tabShowPlayerHeads; - ServerUtilitiesClientConfig.tabShowPingNumber = tabShowPingNumber; - ServerUtilitiesClientConfig.tabShowPingBars = tabShowPingBars; - } -} diff --git a/src/main/java/serverutils/net/ServerUtilitiesNetHandler.java b/src/main/java/serverutils/net/ServerUtilitiesNetHandler.java index acfe2e6a..80bb7240 100644 --- a/src/main/java/serverutils/net/ServerUtilitiesNetHandler.java +++ b/src/main/java/serverutils/net/ServerUtilitiesNetHandler.java @@ -26,7 +26,6 @@ public static void init() { GENERAL.register(new MessageInvseeContainer()); GENERAL.register(new MessageInvseeSwitch()); GENERAL.register(new MessageTransfer()); - GENERAL.register(new MessageTabConfig()); CLAIMS.register(new MessageClaimedChunksRequest()); CLAIMS.register(new MessageClaimedChunksUpdate()); From 6e82f7f23a8d647e70eed7e5f737aceb38520998 Mon Sep 17 00:00:00 2001 From: alppp Date: Sun, 7 Jun 2026 16:16:33 +0300 Subject: [PATCH 3/3] rename tab channels to SU, gate tab mixins via setApplyIf, show nicknames --- .../serverutils/ServerUtilitiesConfig.java | 4 +++ .../client/ServerUtilitiesClient.java | 2 +- .../client/ServerUtilitiesClientConfig.java | 5 ---- .../client/tab/ModernTabRenderer.java | 29 ++++++++----------- .../client/tab/TabChannelHandler.java | 4 +-- .../client/tab/TabDisplayHandler.java | 6 ++-- src/main/java/serverutils/mixin/Mixins.java | 2 ++ .../forge/MixinGuiIngameForge_ModernTab.java | 2 -- 8 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/main/java/serverutils/ServerUtilitiesConfig.java b/src/main/java/serverutils/ServerUtilitiesConfig.java index d59b2660..2353eca8 100644 --- a/src/main/java/serverutils/ServerUtilitiesConfig.java +++ b/src/main/java/serverutils/ServerUtilitiesConfig.java @@ -782,6 +782,10 @@ public static class Mixins { @Config.Comment("Enable grief protection for farmland trampling.") @Config.DefaultBoolean(true) public boolean farmlandTramplingProtection; + + @Config.Comment("Replaces the vanilla player list (TAB overlay) with a modern-style tab list featuring player heads, dynamic columns, and header/footer support.") + @Config.DefaultBoolean(true) + public boolean modernTabOverlay; } public static class MOTD { diff --git a/src/main/java/serverutils/client/ServerUtilitiesClient.java b/src/main/java/serverutils/client/ServerUtilitiesClient.java index 53bab7f7..5b09bc0b 100644 --- a/src/main/java/serverutils/client/ServerUtilitiesClient.java +++ b/src/main/java/serverutils/client/ServerUtilitiesClient.java @@ -82,7 +82,7 @@ public void postInit(FMLPostInitializationEvent event) { NavigatorIntegration.init(); } - if (ServerUtilitiesClientConfig.modernTabOverlay) { + if (ServerUtilitiesConfig.mixins.modernTabOverlay) { TabChannelHandler.INSTANCE.registerChannel(); TabDisplayHandler.INSTANCE.registerChannel(); } diff --git a/src/main/java/serverutils/client/ServerUtilitiesClientConfig.java b/src/main/java/serverutils/client/ServerUtilitiesClientConfig.java index 526d955c..15f3fcf7 100644 --- a/src/main/java/serverutils/client/ServerUtilitiesClientConfig.java +++ b/src/main/java/serverutils/client/ServerUtilitiesClientConfig.java @@ -50,11 +50,6 @@ public class ServerUtilitiesClientConfig { @Config.DefaultInt(18000) public static int button_nighttime; - @Config.Comment("Replaces the vanilla player list (TAB overlay) with a modern-style tab list featuring player heads, dynamic columns, and header/footer support.") - @Config.DefaultBoolean(true) - @Config.RequiresMcRestart - public static boolean modernTabOverlay; - @Config.Ignore private static long show_shutdown_timer_ms = -1L; diff --git a/src/main/java/serverutils/client/tab/ModernTabRenderer.java b/src/main/java/serverutils/client/tab/ModernTabRenderer.java index 15c5b8f0..78caddef 100644 --- a/src/main/java/serverutils/client/tab/ModernTabRenderer.java +++ b/src/main/java/serverutils/client/tab/ModernTabRenderer.java @@ -23,6 +23,7 @@ import org.lwjgl.opengl.GL11; import serverutils.ServerUtilitiesConfig; +import serverutils.client.gui.misc.GuiPlayerInfoWrapper; import serverutils.lib.util.StringUtils; public class ModernTabRenderer { @@ -76,15 +77,7 @@ public void render(ScaledResolution resolution) { int maxNameWidth = 0; for (GuiPlayerInfo player : players) { - String customName = displayHandler.getDisplayName(player.name); - String displayName; - if (customName != null) { - displayName = customName; - } else { - ScorePlayerTeam team = scoreboard.getPlayersTeam(player.name); - displayName = ScorePlayerTeam.formatPlayerName(team, player.name); - } - int w = font.getStringWidth(displayName); + int w = font.getStringWidth(resolveDisplayName(player, scoreboard, displayHandler)); if (w > maxNameWidth) maxNameWidth = w; } @@ -190,14 +183,7 @@ public void render(ScaledResolution resolution) { GuiPlayerInfo player = players.get(i); activeNames.add(player.name); - String customName = displayHandler.getDisplayName(player.name); - String displayName; - if (customName != null) { - displayName = customName; - } else { - ScorePlayerTeam team = scoreboard.getPlayersTeam(player.name); - displayName = ScorePlayerTeam.formatPlayerName(team, player.name); - } + String displayName = resolveDisplayName(player, scoreboard, displayHandler); Gui.drawRect(x, y, x + columnWidth, y + 8, ENTRY_BG_COLOR); @@ -279,6 +265,15 @@ public void render(ScaledResolution resolution) { GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); } + private static String resolveDisplayName(GuiPlayerInfo player, Scoreboard scoreboard, + TabDisplayHandler displayHandler) { + String proxyName = displayHandler.getDisplayName(player.name); + if (proxyName != null) return proxyName; + if (player instanceof GuiPlayerInfoWrapper wrapper && wrapper.displayName != null) return wrapper.displayName; + ScorePlayerTeam team = scoreboard.getPlayersTeam(player.name); + return ScorePlayerTeam.formatPlayerName(team, player.name); + } + private static String resolveHeaderFooter(boolean isHeader) { String text; TabChannelHandler ch = TabChannelHandler.INSTANCE; diff --git a/src/main/java/serverutils/client/tab/TabChannelHandler.java b/src/main/java/serverutils/client/tab/TabChannelHandler.java index d021eee9..98353c50 100644 --- a/src/main/java/serverutils/client/tab/TabChannelHandler.java +++ b/src/main/java/serverutils/client/tab/TabChannelHandler.java @@ -14,7 +14,7 @@ public class TabChannelHandler { - public static final String CHANNEL_NAME = "HP|TabHF"; + public static final String CHANNEL_NAME = "SU|TabHF"; public static final TabChannelHandler INSTANCE = new TabChannelHandler(); private volatile String header = ""; @@ -41,7 +41,7 @@ public void onClientPacket(FMLNetworkEvent.ClientCustomPacketEvent event) { String f = obj.has("footer") ? obj.get("footer").getAsString() : ""; setServerData(h, f); } catch (Exception e) { - ServerUtilities.LOGGER.warn("Failed to parse HP|TabHF packet", e); + ServerUtilities.LOGGER.warn("Failed to parse SU|TabHF packet", e); } } diff --git a/src/main/java/serverutils/client/tab/TabDisplayHandler.java b/src/main/java/serverutils/client/tab/TabDisplayHandler.java index 7ffdd15b..fc104663 100644 --- a/src/main/java/serverutils/client/tab/TabDisplayHandler.java +++ b/src/main/java/serverutils/client/tab/TabDisplayHandler.java @@ -19,7 +19,7 @@ public class TabDisplayHandler { - public static final String CHANNEL_NAME = "HP|TabUD"; + public static final String CHANNEL_NAME = "SU|TabUD"; public static final TabDisplayHandler INSTANCE = new TabDisplayHandler(); private final Map displayNames = new ConcurrentHashMap<>(); @@ -55,10 +55,10 @@ public void onClientPacket(FMLNetworkEvent.ClientCustomPacketEvent event) { handleSort(obj); break; default: - ServerUtilities.LOGGER.warn("Unknown HP|TabUD action: {}", action); + ServerUtilities.LOGGER.warn("Unknown SU|TabUD action: {}", action); } } catch (Exception e) { - ServerUtilities.LOGGER.warn("Failed to parse HP|TabUD packet", e); + ServerUtilities.LOGGER.warn("Failed to parse SU|TabUD packet", e); } } diff --git a/src/main/java/serverutils/mixin/Mixins.java b/src/main/java/serverutils/mixin/Mixins.java index 6a55f3b3..ac803685 100644 --- a/src/main/java/serverutils/mixin/Mixins.java +++ b/src/main/java/serverutils/mixin/Mixins.java @@ -20,6 +20,7 @@ public enum Mixins implements IMixins { "minecraft.MixinICommand")), REPLACE_TAB_NAMES(new MixinBuilder() .setPhase(Phase.EARLY) + .setApplyIf(() -> !mixins.modernTabOverlay) .addClientMixins("forge.MixinGuiIngameForge")), VANILLA_TP_BACK_COMPAT(new MixinBuilder("/back compat for the vanilla /tp") .setPhase(Phase.EARLY) @@ -89,6 +90,7 @@ public enum Mixins implements IMixins { .addCommonMixins("brigadier.MixinCommandNode", "brigadier.MixinLiteralCommandNode")), MODERN_TAB_OVERLAY(new MixinBuilder("Modern-style player list overlay") .setPhase(Phase.EARLY) + .setApplyIf(() -> mixins.modernTabOverlay) .addClientMixins("forge.MixinGuiIngameForge_ModernTab")), ; // spotless:on diff --git a/src/main/java/serverutils/mixins/early/forge/MixinGuiIngameForge_ModernTab.java b/src/main/java/serverutils/mixins/early/forge/MixinGuiIngameForge_ModernTab.java index 932b3aa7..c7267b16 100644 --- a/src/main/java/serverutils/mixins/early/forge/MixinGuiIngameForge_ModernTab.java +++ b/src/main/java/serverutils/mixins/early/forge/MixinGuiIngameForge_ModernTab.java @@ -15,7 +15,6 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import serverutils.client.ServerUtilitiesClientConfig; import serverutils.client.tab.ModernTabRenderer; @Mixin(GuiIngameForge.class) @@ -34,7 +33,6 @@ private MixinGuiIngameForge_ModernTab(Minecraft mc) { @SuppressWarnings("unchecked") @Inject(method = "renderPlayerList", at = @At("HEAD"), cancellable = true, remap = false) private void serverutilities$modernTab(int width, int height, CallbackInfo ci) { - if (!ServerUtilitiesClientConfig.modernTabOverlay) return; ci.cancel(); ScoreObjective scoreobjective = mc.theWorld.getScoreboard().func_96539_a(0);