New config system

This commit is contained in:
DenaryDev 2022-12-11 18:56:52 +05:00
parent c09c82899c
commit 8da4845534
13 changed files with 354 additions and 140 deletions

View file

@ -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. This is a client-side mod. The server doesn't need to have it installed. It works even when playing on vanilla servers.
## Configuration ## 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: This mod's config file is `betterpingdisplay.json`. It contains the following options:
| Option | Default Value | Description | | 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.16.x**
* **1.17.x** * **1.17.x**
* **1.18.x** * **1.18.x**
* **1.19.x**
## Requirements ## Requirements
* [Fabric](https://fabricmc.net/) * [Fabric](https://fabricmc.net/)

View file

@ -1,5 +1,5 @@
plugins { plugins {
id 'fabric-loom' version '0.12-SNAPSHOT' id 'fabric-loom' version '1.0-SNAPSHOT'
id 'maven-publish' id 'maven-publish'
} }
@ -10,11 +10,20 @@ archivesBaseName = project.archives_base_name
version = project.mod_version version = project.mod_version
group = project.maven_group group = project.maven_group
repositories {
maven { url "https://maven.shedaniel.me/" }
maven { url "https://maven.terraformersmc.com/releases/" }
}
dependencies { dependencies {
minecraft "com.mojang:minecraft:${project.minecraft_version}" minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_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' testImplementation 'junit:junit:4.13'
} }

View file

@ -4,14 +4,18 @@ org.gradle.jvmargs=-Xmx1G
# Fabric Properties # Fabric Properties
# check these on https://fabricmc.net/use # check these on https://fabricmc.net/use
minecraft_version=1.19.2 minecraft_version=1.19.2
yarn_mappings=1.19.2+build.18 yarn_mappings=1.19.2+build.28
loader_version=0.14.9 loader_version=0.14.10
# Mod Properties # Mod Properties
mod_version = 1.1.1 mod_version = 1.2.0
maven_group = com.vladmarica maven_group = com.vladmarica
archives_base_name = BetterPingDisplay-Fabric archives_base_name = BetterPingDisplay-Fabric
# Dependencies # Dependencies
# currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api # 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

View file

@ -1,47 +1,33 @@
package com.vladmarica.betterpingdisplay; package com.vladmarica.betterpingdisplay;
import com.vladmarica.betterpingdisplay.Config.ConfigData; import com.vladmarica.betterpingdisplay.config.Config;
import java.io.File; import com.vladmarica.betterpingdisplay.config.ConfigManager;
import java.nio.file.Path;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
public class BetterPingDisplayMod implements ModInitializer { public class BetterPingDisplayMod implements ModInitializer {
public static final String MODID = "betterpingdisplay"; public static final String MODID = "betterpingdisplay";
public static final Logger LOGGER = LogManager.getLogger(MODID); public static final Logger LOGGER = LogManager.getLogger(MODID);
private static final String CONFIG_FILE_NAME = MODID + ".json";
private static BetterPingDisplayMod INSTANCE; private static BetterPingDisplayMod INSTANCE;
private Config config = new Config(); private ConfigManager configManager = new ConfigManager();
private Config config;
@Override @Override
public void onInitialize() { public void onInitialize() {
INSTANCE = this; INSTANCE = this;
Path configFilePath = FabricLoader.getInstance().getConfigDir().resolve(CONFIG_FILE_NAME); configManager = new ConfigManager();
File configFile = configFilePath.toFile(); config = configManager.getConfig();
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());
}
}
LOGGER.info("BetterPingDisplay mod loaded"); LOGGER.info("BetterPingDisplay mod loaded");
} }
public ConfigManager getConfigManager() {
return configManager;
}
public Config getConfig() { public Config getConfig() {
return config; return config;
} }

View file

@ -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";
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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<Boolean, Text> 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<Text>();
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]);
}
}

View file

@ -2,7 +2,7 @@ package com.vladmarica.betterpingdisplay.hud;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import com.vladmarica.betterpingdisplay.BetterPingDisplayMod; import com.vladmarica.betterpingdisplay.BetterPingDisplayMod;
import com.vladmarica.betterpingdisplay.Config; import com.vladmarica.betterpingdisplay.config.Config;
import com.vladmarica.betterpingdisplay.mixin.PlayerListHudInvoker; import com.vladmarica.betterpingdisplay.mixin.PlayerListHudInvoker;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer; import net.minecraft.client.font.TextRenderer;
@ -13,25 +13,25 @@ import net.minecraft.client.util.math.MatrixStack;
public final class CustomPlayerListHud { public final class CustomPlayerListHud {
private static final int PING_TEXT_RENDER_OFFSET = -13; private static final int PING_TEXT_RENDER_OFFSET = -13;
private static final int PING_BARS_WIDTH = 11; 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( public static void renderPingDisplay(
MinecraftClient client, PlayerListHud hud, MatrixStack matrixStack, int width, int x, int y, PlayerListEntry player) { MinecraftClient client, PlayerListHud hud, MatrixStack matrixStack, int width, int x, int y, PlayerListEntry player) {
TextRenderer textRenderer = client.textRenderer; 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 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; int textX = width + x - pingStringWidth + PING_TEXT_RENDER_OFFSET;
if (!config.shouldRenderPingBars()) { if (!CONFIG.shouldRenderPingBars()) {
textX += PING_BARS_WIDTH; textX += PING_BARS_WIDTH;
} }
// Draw the ping text for the given player // Draw the ping text for the given player
textRenderer.drawWithShadow(matrixStack, pingString, (float) textX, (float) y, pingTextColor); textRenderer.drawWithShadow(matrixStack, pingString, (float) textX, (float) y, pingTextColor);
if (config.shouldRenderPingBars()) { if (CONFIG.shouldRenderPingBars()) {
((PlayerListHudInvoker) hud).invokeRenderLatencyIcon(matrixStack, width, x, y, player); ((PlayerListHudInvoker) hud).invokeRenderLatencyIcon(matrixStack, width, x, y, player);
} else { } else {
// If we don't render ping bars, we need to reset the render system color so the rest // If we don't render ping bars, we need to reset the render system color so the rest

View file

@ -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"
}

View file

@ -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`"
}

View file

@ -21,6 +21,9 @@
"entrypoints": { "entrypoints": {
"main": [ "main": [
"com.vladmarica.betterpingdisplay.BetterPingDisplayMod" "com.vladmarica.betterpingdisplay.BetterPingDisplayMod"
],
"modmenu": [
"com.vladmarica.betterpingdisplay.ModMenuIntegration"
] ]
}, },
"mixins": [ "mixins": [