diff --git a/src/main/java/serverutils/ServerUtilitiesConfig.java b/src/main/java/serverutils/ServerUtilitiesConfig.java index 5114c580..2353eca8 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 { @@ -781,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 { @@ -811,4 +816,32 @@ 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) + @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/ServerUtilitiesClient.java b/src/main/java/serverutils/client/ServerUtilitiesClient.java index 558663cd..5b09bc0b 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 (ServerUtilitiesConfig.mixins.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/tab/ModernTabRenderer.java b/src/main/java/serverutils/client/tab/ModernTabRenderer.java new file mode 100644 index 00000000..78caddef --- /dev/null +++ b/src/main/java/serverutils/client/tab/ModernTabRenderer.java @@ -0,0 +1,335 @@ +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.ServerUtilitiesConfig; +import serverutils.client.gui.misc.GuiPlayerInfoWrapper; +import serverutils.lib.util.StringUtils; + +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 = new ArrayList<>(allPlayers.subList(0, playerCount)); + } + + int maxNameWidth = 0; + for (GuiPlayerInfo player : players) { + int w = font.getStringWidth(resolveDisplayName(player, scoreboard, displayHandler)); + 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 = ServerUtilitiesConfig.tab.showPingNumber; + boolean showPingBars = ServerUtilitiesConfig.tab.showPingBars; + 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 = ServerUtilitiesConfig.tab.showPlayerHeads; + 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 displayName = resolveDisplayName(player, scoreboard, displayHandler); + + 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 - maxPingTextWidth - maxSuffixWidth - 2; + 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 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; + if (ch.hasServerData()) { + text = isHeader ? ch.getHeader() : ch.getFooter(); + } else { + text = isHeader ? ServerUtilitiesConfig.tab.headerText : ServerUtilitiesConfig.tab.footerText; + } + if (text == null || text.isEmpty()) return ""; + return StringUtils.addFormatting(text.replace("\\n", "\n")); + } + + 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..98353c50 --- /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 = "SU|TabHF"; + public static final TabChannelHandler INSTANCE = new TabChannelHandler(); + + private volatile String header = ""; + private volatile String footer = ""; + private volatile 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 SU|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..fc104663 --- /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 = "SU|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 SU|TabUD action: {}", action); + } + } catch (Exception e) { + ServerUtilities.LOGGER.warn("Failed to parse SU|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..da360b91 --- /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.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +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 ConcurrentHashMap<>(); + + 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/mixin/Mixins.java b/src/main/java/serverutils/mixin/Mixins.java index c339c0e3..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) @@ -87,6 +88,10 @@ 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) + .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 new file mode 100644 index 00000000..c7267b16 --- /dev/null +++ b/src/main/java/serverutils/mixins/early/forge/MixinGuiIngameForge_ModernTab.java @@ -0,0 +1,52 @@ +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.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) { + 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)); + } + } +}