diff --git a/.gitignore b/.gitignore index c303bcb..2f33118 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ build/ out/ .idea/ run/ +logs/ +src/main/java/net/fabricmc/* \ No newline at end of file diff --git a/README.md b/README.md index 657eeef..142dd9a 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,24 @@ [![](http://cf.way2muchnoise.eu/better-ping-display-fabric.svg)](https://curseforge.com/minecraft/mc-mods/better-ping-display-fabric) [![](http://cf.way2muchnoise.eu/versions/better-ping-display-fabric.svg)](https://curseforge.com/minecraft/mc-mods/better-ping-display-fabric) -A [Fabric](https://fabricmc.net/) mod for Minecraft to display each player's ping in the player list as a number. There is also a config file -for changing the text color and format. +A [Fabric](https://fabricmc.net/) mod for Minecraft to display each player's ping in the player list as a number. Go [**here**](https://github.com/vladmarica/better-ping-display) for the Minecraft Forge edition of this mod. -![](https://i.imgur.com/LYB3o4h.png) +![](https://i.imgur.com/HTrH0i2.png) This is a client-side mod. The server doesn't need to have it installed. It works even when playing on vanilla servers. +## Configuration +This mod's config file is `betterpingdisplay.json`. It contains the following options: + +| Option | Default Value | Description | +|---|---|---| +| autoColorPingText | `true` | Whether to color a player's ping based on their latency. E.g, low latency = green, high latency = red | +| renderPingBars | `false` | Whether to also draw the default Minecraft ping bars | +| pingTextColor | `#A0A0A0` | The ping text color to use. Only works whens `autoColorPingText` is false | +| pingTextFormatString | `%dms` | The format string for ping text. Must include a `%d`, which will be replaced dynamically by the actual ping value. + ## Supported Minecraft Versions * **1.15.x** * **1.16.x** diff --git a/build.gradle b/build.gradle index ba9e972..8f22a16 100644 --- a/build.gradle +++ b/build.gradle @@ -11,13 +11,11 @@ version = project.mod_version group = project.maven_group dependencies { - //to change the versions see the gradle.properties file minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - - // Fabric API. This is technically optional, but you probably want it anyway. modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + testImplementation 'junit:junit:4.13' } processResources { @@ -33,9 +31,6 @@ processResources { } } -// ensure that the encoding is set to UTF-8, no matter what the system default is -// this fixes some edge cases with special characters not displaying correctly -// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html tasks.withType(JavaCompile) { options.encoding = "UTF-8" } @@ -72,3 +67,17 @@ publishing { // mavenLocal() } } + +sourceSets { + main.java { + include 'com/vladmarica/betterpingdisplay/**/*' + exclude 'net/fabricmc/*' + } + test.java { + include 'com/vladmarica/betterpingdisplay/**/*' + } +} + +test { + useJUnit() +} diff --git a/gradle.properties b/gradle.properties index 3f9c45b..9df68ad 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,9 +8,9 @@ org.gradle.jvmargs=-Xmx1G loader_version=0.9.3+build.207 # Mod Properties - mod_version = 1.0.0 + mod_version = 1.1.0 maven_group = com.vladmarica - archives_base_name = BetterPingDisplay + archives_base_name = BetterPingDisplay-Fabric # Dependencies # currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api diff --git a/src/main/java/com/vladmarica/betterpingdisplay/BetterPingDisplayMod.java b/src/main/java/com/vladmarica/betterpingdisplay/BetterPingDisplayMod.java index 0a2e622..b9b3450 100644 --- a/src/main/java/com/vladmarica/betterpingdisplay/BetterPingDisplayMod.java +++ b/src/main/java/com/vladmarica/betterpingdisplay/BetterPingDisplayMod.java @@ -26,6 +26,7 @@ public class BetterPingDisplayMod implements ModInitializer { try { ConfigData data = Config.loadConfigFile(configFile); config = new Config(data); + Config.writeConfigFile(configFile, data); } catch (Exception ex) { LOGGER.error("Failed to load config file, using default. Error: {}", ex.getMessage()); } diff --git a/src/main/java/com/vladmarica/betterpingdisplay/Config.java b/src/main/java/com/vladmarica/betterpingdisplay/Config.java index 0fba95b..3b65a9f 100644 --- a/src/main/java/com/vladmarica/betterpingdisplay/Config.java +++ b/src/main/java/com/vladmarica/betterpingdisplay/Config.java @@ -13,28 +13,33 @@ public class Config { private static final int DEFAULT_PING_TEXT_COLOR = 0xA0A0A0; private static final String DEFAULT_PING_TEXT_FORMAT = "%dms"; + private boolean autoColorPingText; + private boolean renderPingBars; private int textColor = DEFAULT_PING_TEXT_COLOR; private String textFormatString = DEFAULT_PING_TEXT_FORMAT; public Config(ConfigData confileFileFormat) { - if (confileFileFormat.textColor.startsWith("#")) { + if (confileFileFormat.pingTextColor.startsWith("#")) { try { - textColor = Integer.parseInt(confileFileFormat.textColor.substring(1), 16); + textColor = Integer.parseInt(confileFileFormat.pingTextColor.substring(1), 16); } catch (NumberFormatException ex) { - BetterPingDisplayMod.LOGGER.error("Config option 'textColor' is invalid - it must be a hex color code"); + BetterPingDisplayMod.LOGGER.error("Config option 'pingTextColor' is invalid - it must be a hex color code"); } } else { - BetterPingDisplayMod.LOGGER.error("Config option 'textColor' is invalid - it must be a hex color code"); + BetterPingDisplayMod.LOGGER.error("Config option 'pingTextColor' is invalid - it must be a hex color code"); } - if (confileFileFormat.textFormatString.contains("%d")) { - textFormatString = confileFileFormat.textFormatString; + if (confileFileFormat.pingTextFormatString.contains("%d")) { + textFormatString = confileFileFormat.pingTextFormatString; } else { - BetterPingDisplayMod.LOGGER.error("Config option 'textFormatString' is invalid - it needs to contain %d"); + BetterPingDisplayMod.LOGGER.error("Config option 'pingTextFormatString' is invalid - it needs to contain %d"); } + + autoColorPingText = confileFileFormat.autoColorPingText; + renderPingBars = confileFileFormat.renderPingBars; } public Config() { @@ -49,6 +54,14 @@ public class Config { return this.textFormatString; } + public boolean shouldAutoColorPingText() { + return this.autoColorPingText; + } + + public boolean shouldRenderPingBars() { + return this.renderPingBars; + } + public static ConfigData loadConfigFile(File configFile) throws IOException { FileReader reader = null; try { @@ -78,9 +91,15 @@ public class Config { public static class ConfigData implements Serializable { @Expose - private String textColor = "#A0A0A0"; + private boolean autoColorPingText = true; @Expose - private String textFormatString = "%dms"; + private boolean renderPingBars = false; + + @Expose + private String pingTextColor = "#A0A0A0"; + + @Expose + private String pingTextFormatString = "%dms"; } } diff --git a/src/main/java/com/vladmarica/betterpingdisplay/hud/ColorUtil.java b/src/main/java/com/vladmarica/betterpingdisplay/hud/ColorUtil.java new file mode 100644 index 0000000..599506e --- /dev/null +++ b/src/main/java/com/vladmarica/betterpingdisplay/hud/ColorUtil.java @@ -0,0 +1,32 @@ +package com.vladmarica.betterpingdisplay.hud; + +public class ColorUtil { + + public static int interpolate(int colorStart, int colorEnd, float offset) { + if (offset < 0 || offset > 1) { + throw new IllegalArgumentException("Offset must be between 0.0 and 1.0"); + } + + int redDiff = getRed(colorEnd) - getRed(colorStart); + int greenDiff = getGreen(colorEnd) - getGreen(colorStart); + int blueDiff = getBlue(colorEnd) - getBlue(colorStart); + + int newRed = Math.round(getRed(colorStart) + (redDiff * offset)); + int newGreen = Math.round(getGreen(colorStart) + (greenDiff * offset)); + int newBlue = Math.round(getBlue(colorStart) + (blueDiff * offset)); + + return (newRed << 16) | (newGreen << 8) | newBlue; + } + + static int getRed(int color) { + return (color >> 16) & 0xFF; + } + + static int getGreen(int color) { + return (color >> 8) & 0xFF; + } + + static int getBlue(int color) { + return color & 0xFF; + } +} diff --git a/src/main/java/com/vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java b/src/main/java/com/vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java index 834edfa..1766299 100644 --- a/src/main/java/com/vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java +++ b/src/main/java/com/vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java @@ -5,7 +5,6 @@ import com.google.common.collect.Ordering; import com.mojang.authlib.GameProfile; import com.mojang.blaze3d.systems.RenderSystem; import com.vladmarica.betterpingdisplay.Config; -import com.vladmarica.betterpingdisplay.hud.CustomPlayerListHud.EntryOrderComparator; import com.vladmarica.betterpingdisplay.BetterPingDisplayMod; import java.util.Comparator; import java.util.Iterator; @@ -36,6 +35,7 @@ public final class CustomPlayerListHud { private static final int PING_TEXT_RENDER_OFFSET = -13; private static final int PLAYER_SLOT_EXTRA_WIDTH = 45; private static final int PLAYER_ICON_WIDTH = 9; + private static final int PING_BARS_WIDTH = 11; public static void render(PlayerListHud hud, MatrixStack stack, int width, Scoreboard scoreboard, ScoreboardObjective obj) { MinecraftClient mc = MinecraftClient.getInstance(); @@ -53,10 +53,10 @@ public final class CustomPlayerListHud { int n; while(playerListIterator.hasNext()) { PlayerListEntry playerListEntry = (PlayerListEntry)playerListIterator.next(); - n = mc.textRenderer.getWidth(hud.getPlayerName(playerListEntry)); + n = mc.textRenderer.getStringWidth(hud.getPlayerName(playerListEntry).asFormattedString()); i = Math.max(i, n); if (obj != null && obj.getRenderType() != ScoreboardCriterion.RenderType.HEARTS) { - n = textRenderer.getWidth(" " + scoreboard.getPlayerScore(playerListEntry.getProfile().getName(), obj).getScore()); + n = textRenderer.getStringWidth(" " + scoreboard.getPlayerScore(playerListEntry.getProfile().getName(), obj).getScore()); j = Math.max(j, n); } } @@ -84,23 +84,23 @@ public final class CustomPlayerListHud { int s = width / 2 - (r * n + (n - 1) * 5) / 2; int t = 10; int u = r * n + (n - 1) * 5; - List list2 = null; + List list2 = null; if (header != null) { - list2 = mc.textRenderer.wrapLines(header, width - 50); + list2 = mc.textRenderer.wrapStringToWidthAsList(header.asFormattedString(), width - 50); String string; - for(Iterator var18 = list2.iterator(); var18.hasNext(); u = Math.max(u, mc.textRenderer.getWidth(string))) { + for(Iterator var18 = list2.iterator(); var18.hasNext(); u = Math.max(u, mc.textRenderer.getStringWidth(string))) { string = (String)var18.next(); } } - List list3 = null; + List list3 = null; String string3; Iterator var36; if (footer != null) { - list3 = mc.textRenderer.wrapLines(footer, width - 50); + list3 = mc.textRenderer.wrapStringToWidthAsList(footer.asFormattedString(), width - 50); - for(var36 = list3.iterator(); var36.hasNext(); u = Math.max(u, mc.textRenderer.getWidth(string3))) { + for(var36 = list3.iterator(); var36.hasNext(); u = Math.max(u, mc.textRenderer.getStringWidth(string3))) { string3 = (String)var36.next(); } } @@ -115,18 +115,18 @@ public final class CustomPlayerListHud { var10001 = t - 1; var10002 = width / 2 + u / 2 + 1; var10004 = list2.size(); - DrawableHelper.fill(stack, var10000, var10001, var10002, t + var10004 * 9, Integer.MIN_VALUE); + DrawableHelper.fill(var10000, var10001, var10002, t + var10004 * 9, Integer.MIN_VALUE); for(var36 = list2.iterator(); var36.hasNext(); t += 9) { string3 = (String)var36.next(); - y = mc.textRenderer.getWidth(string3); - mc.textRenderer.drawWithShadow(stack, string3, (float)(width / 2 - y / 2), (float)t, -1); + y = mc.textRenderer.getStringWidth(string3); + mc.textRenderer.drawWithShadow(string3, (float)(width / 2 - y / 2), (float)t, -1); } ++t; } - DrawableHelper.fill(stack, width / 2 - u / 2 - 1, t - 1, width / 2 + u / 2 + 1, t + m * 9, Integer.MIN_VALUE); + DrawableHelper.fill(width / 2 - u / 2 - 1, t - 1, width / 2 + u / 2 + 1, t + m * 9, Integer.MIN_VALUE); int w = mc.options.getTextBackgroundColor(553648127); int ai; @@ -135,7 +135,7 @@ public final class CustomPlayerListHud { ai = x % m; int aa = s + y * r + y * 5; int ab = t + ai * 9; - DrawableHelper.fill(stack, aa, ab, aa + r, ab + 8, w); + DrawableHelper.fill(stack, aa, ab, aa + r -1, ab + 8, w); RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F); RenderSystem.enableAlphaTest(); RenderSystem.enableBlend(); @@ -150,42 +150,58 @@ public final class CustomPlayerListHud { mc.getTextureManager().bindTexture(player.getSkinTexture()); ah = 8 + (bl2 ? 8 : 0); int ad = 8 * (bl2 ? -1 : 1); - DrawableHelper.drawTexture(stack, aa, ab, 8, 8, 8.0F, (float)ah, 8, ad, 64, 64); + DrawableHelper.blit(aa, ab, 8, 8, 8.0F, (float)ah, 8, ad, 64, 64); if (playerEntity != null && playerEntity.isPartVisible(PlayerModelPart.HAT)) { int ae = 8 + (bl2 ? 8 : 0); int af = 8 * (bl2 ? -1 : 1); - DrawableHelper.drawTexture(stack, aa, ab, 8, 8, 40.0F, (float)ae, 8, af, 64, 64); + DrawableHelper.blit(aa, ab, 8, 8, 40.0F, (float)ae, 8, af, 64, 64); } aa += 9; } - Text playerName = hud.getPlayerName(player); + String string4 = hud.getPlayerName(player).asFormattedString(); if (player.getGameMode() == GameMode.SPECTATOR) { - mc.textRenderer.drawWithShadow(stack, playerName, (float)aa, (float)ab, -1862270977); + mc.textRenderer.drawWithShadow(Formatting.ITALIC + string4, (float)aa, (float)ab, -1862270977); } else { - mc.textRenderer.drawWithShadow(stack, playerName, (float)aa, (float)ab, -1); + mc.textRenderer.drawWithShadow(string4, (float)aa, (float)ab, -1); } if (obj != null && player.getGameMode() != GameMode.SPECTATOR) { int ag = aa + i + 1; ah = ag + q; if (ah - ag > 5) { - PlayerListHudUtil.renderScoreboardObjective(hud, stack, obj, ab, gameProfile.getName(), ag, ah, player); + PlayerListHudUtil.renderScoreboardObjective(hud, obj, ab, gameProfile.getName(), ag, ah, player); } } // Here is the magic, rendering the ping text String pingString = String.format(config.getTextFormatString(), player.getLatency()); - int pingStringWidth = textRenderer.getWidth(pingString); - textRenderer.draw( - stack, - pingString, - (float) r + aa - pingStringWidth + PING_TEXT_RENDER_OFFSET - (displayPlayerIcons ? PLAYER_ICON_WIDTH : 0), - (float) ab, - config.getTextColor()); + int pingStringWidth = textRenderer.getStringWidth(pingString); + int textX = r + aa - pingStringWidth + PING_TEXT_RENDER_OFFSET; - PlayerListHudUtil.renderLatencyIcon(hud, stack ,r, aa - (displayPlayerIcons ? PLAYER_ICON_WIDTH : 0), ab, player); + if (displayPlayerIcons) { + textX -= PLAYER_ICON_WIDTH; + } + + if (!config.shouldRenderPingBars()) { + textX += PING_BARS_WIDTH; + } + + int pingTextColor = config.shouldAutoColorPingText() + ? PingColors.getColor(player.getLatency()) + : config.getTextColor(); + + textRenderer.drawWithShadow(pingString, (float) textX, (float) ab, pingTextColor); + + if (config.shouldRenderPingBars()) { + PlayerListHudUtil.renderLatencyIcon( + hud, r, aa - (displayPlayerIcons ? PLAYER_ICON_WIDTH : 0), ab, player); + } else { + // If we don't render ping bars, we need to reset the render system color so the rest + // of the player list renders properly + RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F); + } } } @@ -195,12 +211,12 @@ public final class CustomPlayerListHud { var10001 = t - 1; var10002 = width / 2 + u / 2 + 1; var10004 = list3.size(); - DrawableHelper.fill(stack, var10000, var10001, var10002, t + var10004 * 9, Integer.MIN_VALUE); + DrawableHelper.fill(var10000, var10001, var10002, t + var10004 * 9, Integer.MIN_VALUE); for(Iterator var39 = list3.iterator(); var39.hasNext(); t += 9) { String string5 = (String)var39.next(); - ai = textRenderer.getWidth(string5); - textRenderer.drawWithShadow(stack, string5, (float)(width / 2 - ai / 2), (float)t, -1); + ai = textRenderer.getStringWidth(string5); + textRenderer.drawWithShadow(string5, (float)(width / 2 - ai / 2), (float)t, -1); } } } diff --git a/src/main/java/com/vladmarica/betterpingdisplay/hud/PingColors.java b/src/main/java/com/vladmarica/betterpingdisplay/hud/PingColors.java new file mode 100644 index 0000000..e981567 --- /dev/null +++ b/src/main/java/com/vladmarica/betterpingdisplay/hud/PingColors.java @@ -0,0 +1,37 @@ +package com.vladmarica.betterpingdisplay.hud; + +import net.minecraft.util.math.MathHelper; + +public class PingColors { + public static final int PING_START = 0; + public static final int PING_MID = 150; + public static final int PING_END = 300; + + public static final int COLOR_GREY = 0x535353; + public static final int COLOR_START = 0x00E676; + public static final int COLOR_MID = 0xD6CD30; + public static final int COLOR_END = 0xE53935; + + public static int getColor(int ping) { + if (ping < PING_START) { + return COLOR_GREY; + } + + if (ping < PING_MID) { + return ColorUtil.interpolate( + COLOR_START, + COLOR_MID, + computeOffset(PING_START, PING_MID, ping)); + } + + return ColorUtil.interpolate( + COLOR_MID, + COLOR_END, + computeOffset(PING_MID, PING_END, Math.min(ping, PING_END))); + } + + static float computeOffset(int start, int end, int value) { + float offset = (value - start) / (float) ( end - start); + return MathHelper.clamp(offset, 0.0F, 1.0F); + } +} diff --git a/src/main/java/com/vladmarica/betterpingdisplay/hud/PlayerListHudUtil.java b/src/main/java/com/vladmarica/betterpingdisplay/hud/PlayerListHudUtil.java index ce32726..fc1ebd5 100644 --- a/src/main/java/com/vladmarica/betterpingdisplay/hud/PlayerListHudUtil.java +++ b/src/main/java/com/vladmarica/betterpingdisplay/hud/PlayerListHudUtil.java @@ -8,10 +8,12 @@ import net.minecraft.scoreboard.ScoreboardObjective; import net.minecraft.text.Text; public class PlayerListHudUtil { + /** Calls {@link net.minecraft.client.gui.hud.PlayerListHud#renderLatencyIcon}. */ static void renderLatencyIcon(PlayerListHud hud, MatrixStack stack, int x, int offsetX, int y, PlayerListEntry player) { ((PlayerListHudAccessor) hud).invokeRenderLatencyIcon(stack, x, offsetX, y, player); } + /** Calls {@link net.minecraft.client.gui.hud.PlayerListHud#renderScoreboardObjective} */ static void renderScoreboardObjective(PlayerListHud hud, MatrixStack stack, ScoreboardObjective obj, int i, String str, int j, int k, PlayerListEntry player) { ((PlayerListHudAccessor) hud).invokeRenderScoreboardObjective(obj, i, str, j, k, player, stack); } diff --git a/src/main/resources/assets/betterpingdisplay/icon.png b/src/main/resources/assets/betterpingdisplay/icon.png index 87f1b2e..de82d52 100644 Binary files a/src/main/resources/assets/betterpingdisplay/icon.png and b/src/main/resources/assets/betterpingdisplay/icon.png differ diff --git a/src/test/java/com/vladmarica/betterpingdisplay/hud/ColorUtilTest.java b/src/test/java/com/vladmarica/betterpingdisplay/hud/ColorUtilTest.java new file mode 100644 index 0000000..65a94c0 --- /dev/null +++ b/src/test/java/com/vladmarica/betterpingdisplay/hud/ColorUtilTest.java @@ -0,0 +1,48 @@ +package com.vladmarica.betterpingdisplay.hud; + +import static com.vladmarica.betterpingdisplay.hud.PingColors.COLOR_MID; +import static com.vladmarica.betterpingdisplay.hud.PingColors.COLOR_START; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ColorUtilTest { + + @Test + public void testExtractComponents() { + int color = 0xD6C130; + assertEquals(0xD6, ColorUtil.getRed(color)); + assertEquals(0xC1, ColorUtil.getGreen(color)); + assertEquals(0x30, ColorUtil.getBlue(color)); + } + + @Test + public void testInterpolation_zero() { + int color = ColorUtil.interpolate(COLOR_START, COLOR_MID, 0F); + assertEquals(COLOR_START, color); + } + + @Test + public void testInterpolation_middle() { + int color = ColorUtil.interpolate(COLOR_START, COLOR_MID, 0.5F); + assertEquals(0x6b, ColorUtil.getRed(color)); + assertEquals(0xda, ColorUtil.getGreen(color)); + assertEquals(0x53, ColorUtil.getBlue(color)); + } + + @Test + public void testInterpolation_one() { + int color = ColorUtil.interpolate(COLOR_START, COLOR_MID, 1F); + assertEquals(COLOR_MID, color); + } + + @Test + public void testInterpolation_invalidOffset() { + assertThrows(IllegalArgumentException.class, () -> ColorUtil.interpolate(0, 1, -0.1F)); + assertThrows(IllegalArgumentException.class, () -> ColorUtil.interpolate(0, 1, 1.1F)); + } +}