diff --git a/README.md b/README.md index 3b679a9..32732d8 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ Go [**here**](https://github.com/vladmarica/better-ping-display) for the Minecra This is a client-side mod. The server doesn't need to have it installed. It works even when playing on vanilla servers. ## Configuration + +__If you have a [ModMenu](https://modrinth.com/mod/modmenu) installed, you can edit mod's config using it.__ + This mod's config file is `betterpingdisplay.json`. It contains the following options: | Option | Default Value | Description | @@ -25,6 +28,7 @@ This mod's config file is `betterpingdisplay.json`. It contains the following op * **1.16.x** * **1.17.x** * **1.18.x** +* **1.19.x** ## Requirements * [Fabric](https://fabricmc.net/) diff --git a/build.gradle b/build.gradle index c54a9fe..5b38cc3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '0.12-SNAPSHOT' + id 'fabric-loom' version '1.0-SNAPSHOT' id 'maven-publish' } @@ -10,11 +10,20 @@ archivesBaseName = project.archives_base_name version = project.mod_version group = project.maven_group +repositories { + maven { url "https://maven.shedaniel.me/" } + maven { url "https://maven.terraformersmc.com/releases/" } +} + dependencies { minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + include(modImplementation("me.shedaniel.cloth:cloth-config-fabric:${project.cloth_config_version}") { + exclude(group: "net.fabricmc.fabric-api") + }) + modImplementation "com.terraformersmc:modmenu:${project.modmenu_version}" testImplementation 'junit:junit:4.13' } diff --git a/gradle.properties b/gradle.properties index ca95ae6..154a327 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,14 +4,18 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/use minecraft_version=1.19.2 - yarn_mappings=1.19.2+build.18 - loader_version=0.14.9 + yarn_mappings=1.19.2+build.28 + loader_version=0.14.10 # Mod Properties - mod_version = 1.1.1 + mod_version = 1.2.0 maven_group = com.vladmarica 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 - fabric_version=0.62.0+1.19.2 + fabric_version=0.67.1+1.19.2 + # ModMenu api + modmenu_version=4.1.1 + # Cloth config for config screen + cloth_config_version=8.2.88 diff --git a/src/main/java/com/vladmarica/betterpingdisplay/BetterPingDisplayMod.java b/src/main/java/com/vladmarica/betterpingdisplay/BetterPingDisplayMod.java index 29a4e33..bd2c51e 100644 --- a/src/main/java/com/vladmarica/betterpingdisplay/BetterPingDisplayMod.java +++ b/src/main/java/com/vladmarica/betterpingdisplay/BetterPingDisplayMod.java @@ -1,47 +1,33 @@ package com.vladmarica.betterpingdisplay; -import com.vladmarica.betterpingdisplay.Config.ConfigData; -import java.io.File; -import java.nio.file.Path; +import com.vladmarica.betterpingdisplay.config.Config; +import com.vladmarica.betterpingdisplay.config.ConfigManager; import net.fabricmc.api.ModInitializer; -import net.fabricmc.loader.api.FabricLoader; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class BetterPingDisplayMod implements ModInitializer { public static final String MODID = "betterpingdisplay"; public static final Logger LOGGER = LogManager.getLogger(MODID); - private static final String CONFIG_FILE_NAME = MODID + ".json"; private static BetterPingDisplayMod INSTANCE; - private Config config = new Config(); + private ConfigManager configManager = new ConfigManager(); + private Config config; @Override public void onInitialize() { INSTANCE = this; - Path configFilePath = FabricLoader.getInstance().getConfigDir().resolve(CONFIG_FILE_NAME); - File configFile = configFilePath.toFile(); - if (configFile.exists()) { - 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()); - } - } else { - try { - LOGGER.warn("Could not find config file, creating a default one"); - Config.writeConfigFile(configFile, new ConfigData()); - } catch (Exception ex) { - LOGGER.error("Failed to write default config file. Error: {}", ex.getMessage()); - } - } + configManager = new ConfigManager(); + config = configManager.getConfig(); LOGGER.info("BetterPingDisplay mod loaded"); } + public ConfigManager getConfigManager() { + return configManager; + } + public Config getConfig() { return config; } diff --git a/src/main/java/com/vladmarica/betterpingdisplay/Config.java b/src/main/java/com/vladmarica/betterpingdisplay/Config.java deleted file mode 100644 index 90373a8..0000000 --- a/src/main/java/com/vladmarica/betterpingdisplay/Config.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.vladmarica.betterpingdisplay; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.annotations.Expose; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.Serializable; - -public class Config { - private static final int DEFAULT_PING_TEXT_COLOR = 0xA0A0A0; - private static final String DEFAULT_PING_TEXT_FORMAT = "%dms"; - - private final boolean autoColorPingText; - private final boolean renderPingBars; - private int textColor = DEFAULT_PING_TEXT_COLOR; - private String textFormatString = DEFAULT_PING_TEXT_FORMAT; - - public Config(ConfigData configFileFormat) { - if (configFileFormat.pingTextColor.startsWith("#")) { - try { - textColor = Integer.parseInt(configFileFormat.pingTextColor.substring(1), 16); - } - catch (NumberFormatException ex) { - BetterPingDisplayMod.LOGGER.error("Config option 'pingTextColor' is invalid - it must be a hex color code"); - } - } - else { - BetterPingDisplayMod.LOGGER.error("Config option 'pingTextColor' is invalid - it must be a hex color code"); - } - - if (configFileFormat.pingTextFormatString.contains("%d")) { - textFormatString = configFileFormat.pingTextFormatString; - } - else { - BetterPingDisplayMod.LOGGER.error("Config option 'pingTextFormatString' is invalid - it needs to contain %d"); - } - - autoColorPingText = configFileFormat.autoColorPingText; - renderPingBars = configFileFormat.renderPingBars; - } - - public Config() { - this(new ConfigData()); - } - - public int getTextColor() { - return this.textColor; - } - - public String getTextFormatString() { - 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 { - Gson gson = new Gson(); - reader = new FileReader(configFile); - return gson.fromJson(reader, ConfigData.class); - } - finally { - if (reader != null) { - reader.close(); - } - } - } - - public static void writeConfigFile(File configFile, ConfigData data) throws IOException { - FileWriter writer = null; - try { - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - writer = new FileWriter(configFile); - writer.write(gson.toJson(data)); - } finally { - if (writer != null) { - writer.close(); - } - } - } - - public static class ConfigData implements Serializable { - @Expose - private boolean autoColorPingText = true; - - @Expose - private boolean renderPingBars = false; - - @Expose - private String pingTextColor = "#A0A0A0"; - - @Expose - private String pingTextFormatString = "%dms"; - } -} diff --git a/src/main/java/com/vladmarica/betterpingdisplay/ModMenuIntegration.java b/src/main/java/com/vladmarica/betterpingdisplay/ModMenuIntegration.java new file mode 100644 index 0000000..5f65f5a --- /dev/null +++ b/src/main/java/com/vladmarica/betterpingdisplay/ModMenuIntegration.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022 DenaryDev + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package com.vladmarica.betterpingdisplay; + +import com.terraformersmc.modmenu.api.ConfigScreenFactory; +import com.terraformersmc.modmenu.api.ModMenuApi; +import com.vladmarica.betterpingdisplay.gui.ConfigScreenBuilder; + +public class ModMenuIntegration implements ModMenuApi { + + @Override + public ConfigScreenFactory getModConfigScreenFactory() { + return ConfigScreenBuilder::build; + } +} diff --git a/src/main/java/com/vladmarica/betterpingdisplay/config/Config.java b/src/main/java/com/vladmarica/betterpingdisplay/config/Config.java new file mode 100644 index 0000000..121bf84 --- /dev/null +++ b/src/main/java/com/vladmarica/betterpingdisplay/config/Config.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022 DenaryDev + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package com.vladmarica.betterpingdisplay.config; + +import com.google.gson.annotations.Expose; + +import java.io.Serializable; + +public class Config implements Serializable { + @Expose + private boolean autoColorPingText = true; + + @Expose + private boolean renderPingBars = false; + + @Expose + private String pingTextColor = "#A0A0A0"; + + @Expose + private String pingTextFormatString = "%dms"; + + private int textColor = Integer.parseInt("A0A0A0", 16); + + public boolean shouldAutoColorPingText() { + return autoColorPingText; + } + + public void setAutoColorPingText(boolean autoColorPingText) { + this.autoColorPingText = autoColorPingText; + } + + public boolean shouldRenderPingBars() { + return renderPingBars; + } + + public void setRenderPingBars(boolean renderPingBars) { + this.renderPingBars = renderPingBars; + } + + public String getPingTextColor() { + return pingTextColor; + } + + public String getPingTextFormat() { + return pingTextFormatString; + } + + public void setPingTextFormat(String pingTextFormat) { + this.pingTextFormatString = pingTextFormat; + } + + public int getTextColor() { + return textColor; + } + + public void setTextColor(int textColor) { + this.pingTextColor = "#" + Integer.toHexString(textColor); + this.textColor = textColor; + } +} diff --git a/src/main/java/com/vladmarica/betterpingdisplay/config/ConfigManager.java b/src/main/java/com/vladmarica/betterpingdisplay/config/ConfigManager.java new file mode 100644 index 0000000..0780fcc --- /dev/null +++ b/src/main/java/com/vladmarica/betterpingdisplay/config/ConfigManager.java @@ -0,0 +1,87 @@ +package com.vladmarica.betterpingdisplay.config; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.vladmarica.betterpingdisplay.BetterPingDisplayMod; +import net.fabricmc.loader.api.FabricLoader; +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.nio.charset.Charset; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +public class ConfigManager { + + private Config config; + + private final Gson gson; + private final File configFile; + + private final Executor executor = Executors.newSingleThreadExecutor(); + + public ConfigManager() { + this.gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create(); + this.configFile = new File(FabricLoader.getInstance().getConfigDir().toString(), BetterPingDisplayMod.MODID + ".json"); + readConfig(false); + } + + public Config getConfig() { + return config; + } + + public void readConfig(boolean async) { + Runnable task = () -> { + try { + if (configFile.exists()) { + String content = FileUtils.readFileToString(configFile, Charset.defaultCharset()); + config = gson.fromJson(content, Config.class); + + if (config.getPingTextColor().startsWith("#")) { + try { + config.setTextColor(Integer.parseInt(config.getPingTextColor().substring(1), 16)); + } catch (NumberFormatException ex) { + BetterPingDisplayMod.LOGGER.error("Config option 'pingTextColor' is invalid - it must be a hex color code"); + } + } else { + BetterPingDisplayMod.LOGGER.error("Config option 'pingTextColor' is invalid - it must be a hex color code"); + } + + if (!config.getPingTextFormat().contains("%d")) { + config.setPingTextFormat(""); + BetterPingDisplayMod.LOGGER.error("Config option 'pingTextFormatString' is invalid - it needs to contain %d"); + } + } else { + writeNewConfig(); + } + } catch (Exception ex) { + ex.printStackTrace(); + writeNewConfig(); + } + }; + + if (async) executor.execute(task); + else task.run(); + } + + public void writeNewConfig() { + config = new Config(); + writeConfig(false); + } + + public void writeConfig(boolean async) { + Runnable task = () -> { + try { + if (config != null) { + String serialized = gson.toJson(config); + FileUtils.writeStringToFile(configFile, serialized, Charset.defaultCharset()); + } + } catch (Exception e) { + e.printStackTrace(); + } + }; + + if (async) executor.execute(task); + else task.run(); + } +} diff --git a/src/main/java/com/vladmarica/betterpingdisplay/gui/ConfigScreenBuilder.java b/src/main/java/com/vladmarica/betterpingdisplay/gui/ConfigScreenBuilder.java new file mode 100644 index 0000000..48b3b9f --- /dev/null +++ b/src/main/java/com/vladmarica/betterpingdisplay/gui/ConfigScreenBuilder.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2022 DenaryDev + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package com.vladmarica.betterpingdisplay.gui; + +import com.vladmarica.betterpingdisplay.BetterPingDisplayMod; +import com.vladmarica.betterpingdisplay.config.Config; +import me.shedaniel.clothconfig2.api.ConfigBuilder; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.resource.language.I18n; +import net.minecraft.text.Text; + +import java.util.ArrayList; +import java.util.Optional; +import java.util.function.Function; + +public class ConfigScreenBuilder { + + private static final Function yesNoSupplier = bool -> { + if (bool) return Text.translatable("label.betterpingdisplay.on"); + else return Text.translatable("label.betterpingdisplay.off"); + }; + + public static Screen build(Screen parent) { + final var defaults = new Config(); + final var current = BetterPingDisplayMod.instance().getConfig(); + + final var builder = ConfigBuilder.create() + .setParentScreen(parent) + .setTitle(Text.translatable("title.betterpingdisplay.config")) + .transparentBackground() + .setDoesConfirmSave(true) + .setSavingRunnable(() -> BetterPingDisplayMod.instance().getConfigManager().writeConfig(true)); + + final var entryBuilder = builder.entryBuilder(); + final var category = builder.getOrCreateCategory(Text.translatable("category.betterpingdisplay.main")); + + final var toggleAutoColorPingText = entryBuilder.startBooleanToggle(Text.translatable("options.betterpingdisplay.autoColorPingText"), current.shouldAutoColorPingText()) + .setDefaultValue(defaults.shouldAutoColorPingText()) + .setYesNoTextSupplier(yesNoSupplier) + .setTooltip(getTooltip("autoColorPingText")) + .setSaveConsumer(current::setAutoColorPingText) + .build(); + + final var toggleRenderPingBars = entryBuilder.startBooleanToggle(Text.translatable("options.betterpingdisplay.renderPingBars"), current.shouldRenderPingBars()) + .setDefaultValue(defaults.shouldRenderPingBars()) + .setYesNoTextSupplier(yesNoSupplier) + .setTooltip(getTooltip("renderPingBars")) + .setSaveConsumer(current::setRenderPingBars) + .build(); + + final var setPingTextColor = entryBuilder.startColorField(Text.translatable("options.betterpingdisplay.pingTextColor"), current.getTextColor()) + .setDefaultValue(defaults.getTextColor()) + .setTooltip(getTooltip("pingTextColor")) + .setSaveConsumer(current::setTextColor) + .build(); + + final var setPingTextFormat = entryBuilder.startStrField(Text.translatable("options.betterpingdisplay.pingTextFormat"), current.getPingTextFormat()) + .setDefaultValue(defaults.getPingTextFormat()) + .setTooltip(getTooltip("pingTextFormat")) + .setErrorSupplier(val -> { + if (!val.contains("%d")) { + return Optional.of(Text.translatable("options.betterpingdisplay.pingTextFormat.error")); + } + return Optional.empty(); + }) + .setSaveConsumer(current::setPingTextFormat) + .build(); + + category.addEntry(toggleAutoColorPingText); + category.addEntry(toggleRenderPingBars); + category.addEntry(setPingTextColor); + category.addEntry(setPingTextFormat); + + return builder.build(); + } + + private static Text[] getTooltip(String key) { + final var list = new ArrayList(); + + for (int i = 0; i < 10; i++) { + String finalKey = key + ".tooltip." + (i + 1); + String value = I18n.translate(finalKey); + + if (value.equals(finalKey)) break; + + list.add(Text.of(value)); + } + + return list.size() == 0 ? null : list.toArray(new Text[0]); + } +} diff --git a/src/main/java/com/vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java b/src/main/java/com/vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java index ed66ea5..d0890d2 100644 --- a/src/main/java/com/vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java +++ b/src/main/java/com/vladmarica/betterpingdisplay/hud/CustomPlayerListHud.java @@ -2,7 +2,7 @@ package com.vladmarica.betterpingdisplay.hud; import com.mojang.blaze3d.systems.RenderSystem; import com.vladmarica.betterpingdisplay.BetterPingDisplayMod; -import com.vladmarica.betterpingdisplay.Config; +import com.vladmarica.betterpingdisplay.config.Config; import com.vladmarica.betterpingdisplay.mixin.PlayerListHudInvoker; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; @@ -13,25 +13,25 @@ import net.minecraft.client.util.math.MatrixStack; public final class CustomPlayerListHud { private static final int PING_TEXT_RENDER_OFFSET = -13; private static final int PING_BARS_WIDTH = 11; - private static final Config config = BetterPingDisplayMod.instance().getConfig(); + private static final Config CONFIG = BetterPingDisplayMod.instance().getConfig(); public static void renderPingDisplay( MinecraftClient client, PlayerListHud hud, MatrixStack matrixStack, int width, int x, int y, PlayerListEntry player) { TextRenderer textRenderer = client.textRenderer; - String pingString = String.format(config.getTextFormatString(), player.getLatency()); + String pingString = String.format(CONFIG.getPingTextFormat(), player.getLatency()); int pingStringWidth = textRenderer.getWidth(pingString); - int pingTextColor = config.shouldAutoColorPingText() ? PingColors.getColor(player.getLatency()) : config.getTextColor(); + int pingTextColor = CONFIG.shouldAutoColorPingText() ? PingColors.getColor(player.getLatency()) : CONFIG.getTextColor(); int textX = width + x - pingStringWidth + PING_TEXT_RENDER_OFFSET; - if (!config.shouldRenderPingBars()) { + if (!CONFIG.shouldRenderPingBars()) { textX += PING_BARS_WIDTH; } // Draw the ping text for the given player textRenderer.drawWithShadow(matrixStack, pingString, (float) textX, (float) y, pingTextColor); - if (config.shouldRenderPingBars()) { + if (CONFIG.shouldRenderPingBars()) { ((PlayerListHudInvoker) hud).invokeRenderLatencyIcon(matrixStack, width, x, y, player); } else { // If we don't render ping bars, we need to reset the render system color so the rest diff --git a/src/main/resources/assets/betterpingdisplay/lang/en_us.json b/src/main/resources/assets/betterpingdisplay/lang/en_us.json new file mode 100644 index 0000000..a6654db --- /dev/null +++ b/src/main/resources/assets/betterpingdisplay/lang/en_us.json @@ -0,0 +1,23 @@ +{ + "label.betterpingdisplay.on": "\u00A7aOn", + "label.betterpingdisplay.off": "\u00A7eOff", + + "category.betterpingdisplay.main": "BetterPingDisplay Settings", + + "options.betterpingdisplay.autoColorPingText": "Auto-color ping text", + "options.betterpingdisplay.autoColorPingText.tooltip.1": "Whether to color a player's ping based on their latency.", + "options.betterpingdisplay.autoColorPingText.tooltip.2": "E.g, low latency = green, high latency = red", + + "options.betterpingdisplay.renderPingBars": "Render ping bars", + "options.betterpingdisplay.renderPingBars.tooltip.1": "Whether to also draw the default Minecraft ping bars", + + "options.betterpingdisplay.pingTextColor": "Ping text color", + "options.betterpingdisplay.pingTextColor.tooltip.1": "The ping text color to use.", + "options.betterpingdisplay.pingTextColor.tooltip.2": "It only works when the auto-color ping text is turned off", + + "options.betterpingdisplay.pingTextFormat": "Ping text format", + "options.betterpingdisplay.pingTextFormat.tooltip.1": "The format string for ping text.", + "options.betterpingdisplay.pingTextFormat.tooltip.2": "Must include a `%d`, which will be replaced", + "options.betterpingdisplay.pingTextFormat.tooltip.3": "dynamically by the actual ping value.", + "options.betterpingdisplay.pingTextFormat.error": "This parameter must contain %d" +} \ No newline at end of file diff --git a/src/main/resources/assets/betterpingdisplay/lang/ru_ru.json b/src/main/resources/assets/betterpingdisplay/lang/ru_ru.json new file mode 100644 index 0000000..5bc4f2c --- /dev/null +++ b/src/main/resources/assets/betterpingdisplay/lang/ru_ru.json @@ -0,0 +1,22 @@ +{ + "label.betterpingdisplay.on": "\u00A7aВкл", + "label.betterpingdisplay.off": "\u00A7eВыкл", + + "category.betterpingdisplay.main": "Настройки BetterPingDisplay", + + "options.betterpingdisplay.autoColorPingText": "Автоматический цвет пинга", + "options.betterpingdisplay.autoColorPingText.tooltip.1": "Следует ли изменять цвет пинга в зависимости от значения.", + "options.betterpingdisplay.autoColorPingText.tooltip.2": "Например, низкий пинг = зелёный, высокий пинг = красный", + + "options.betterpingdisplay.renderPingBars": "Отображать пинг-бары", + "options.betterpingdisplay.renderPingBars.tooltip.1": "Следует ли также рисовать пинг-бары Minecraft по умолчанию", + + "options.betterpingdisplay.pingTextColor": "Цвет пинга", + "options.betterpingdisplay.pingTextColor.tooltip.2": "Это работает только если автоматический цвет выключен", + + "options.betterpingdisplay.pingTextFormat": "Формат пинга", + "options.betterpingdisplay.pingTextFormat.tooltip.1": "Формат для текста пинга", + "options.betterpingdisplay.pingTextFormat.tooltip.2": "Должен содержать `%d`, который будет заменён", + "options.betterpingdisplay.pingTextFormat.tooltip.3": "на текущее значение пинга игрока.", + "options.betterpingdisplay.pingTextFormat.error": "Этот параметр должен содержать `%d`" +} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 79136cf..877e902 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -21,6 +21,9 @@ "entrypoints": { "main": [ "com.vladmarica.betterpingdisplay.BetterPingDisplayMod" + ], + "modmenu": [ + "com.vladmarica.betterpingdisplay.ModMenuIntegration" ] }, "mixins": [