From c7d9218a43d0c5f0bd9ea81117b90f8f003f8753 Mon Sep 17 00:00:00 2001 From: nichtDanger Date: Sun, 27 Jul 2025 15:04:12 +0200 Subject: [PATCH 01/14] Xp Meter Mining --- gradle.properties | 2 +- .../elementsutils/ElementsUtilsClient.java | 12 ++ .../eposs/elementsutils/config/ModConfig.java | 2 + .../feature/xpmeter/XpMeter.java | 185 ++++++++++++++++++ .../mixin/client/InGameHudAccessor.java | 15 ++ .../rendering/ScreenRendering.java | 2 + .../elements-utils.client.mixins.json | 1 + .../assets/elements-utils/lang/de_de.json | 3 + .../assets/elements-utils/lang/en_us.json | 3 + src/main/resources/fabric.mod.json | 3 +- 10 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 src/client/java/dev/eposs/elementsutils/feature/xpmeter/XpMeter.java create mode 100644 src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudAccessor.java diff --git a/gradle.properties b/gradle.properties index ffb57dc..93e62d7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ loader_version=0.16.14 loom_version=1.10-SNAPSHOT # Mod Properties -mod_version=1.4.0+1.21.4 +mod_version=1.4.0.787+1.21.4 maven_group=dev.eposs.elementsutils archives_base_name=elements-utils diff --git a/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java b/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java index 306831b..e36b354 100644 --- a/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java +++ b/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java @@ -7,6 +7,7 @@ import dev.eposs.elementsutils.feature.pet.PetDisplay; import dev.eposs.elementsutils.feature.playerbase.BaseBorderDisplay; import dev.eposs.elementsutils.feature.potion.PotionDisplay; +import dev.eposs.elementsutils.feature.xpmeter.XpMeter; import dev.eposs.elementsutils.rendering.ScreenRendering; import dev.eposs.elementsutils.util.DevUtil; import me.shedaniel.autoconfig.AutoConfig; @@ -31,6 +32,7 @@ public class ElementsUtilsClient implements ClientModInitializer { private static KeyBinding baseDisplayToggle; private static KeyBinding bossTimerToggle; + private static KeyBinding measureTrigger; private static KeyBinding devUtils; @Override @@ -63,6 +65,7 @@ private void clientTick(MinecraftClient client) { onKeyEvent(client); PetDisplay.updatePet(client); PotionDisplay.updatePotions(client); + XpMeter.updateXpMeter(client); } private void onJoin(ClientPlayNetworkHandler handler, PacketSender sender, MinecraftClient client) { @@ -116,6 +119,12 @@ private void registerKeyBinding() { category )); + measureTrigger = KeyBindingHelper.registerKeyBinding(new KeyBinding( + getKeyBindingTranslation("measureTrigger"), + GLFW.GLFW_KEY_UNKNOWN, + category + )); + devUtils = KeyBindingHelper.registerKeyBinding(new KeyBinding( getKeyBindingTranslation("devUtils"), GLFW.GLFW_KEY_UNKNOWN, @@ -130,6 +139,9 @@ private void onKeyEvent(MinecraftClient client) { while (bossTimerToggle.wasPressed()) { BossTimerDisplay.toggleDisplay(client); } + while (measureTrigger.wasPressed()) { + XpMeter.startMeasuring(client); + } while (devUtils.wasPressed()) { DevUtil.doSomething(client); } diff --git a/src/client/java/dev/eposs/elementsutils/config/ModConfig.java b/src/client/java/dev/eposs/elementsutils/config/ModConfig.java index fc5d359..5a69638 100644 --- a/src/client/java/dev/eposs/elementsutils/config/ModConfig.java +++ b/src/client/java/dev/eposs/elementsutils/config/ModConfig.java @@ -83,6 +83,8 @@ public enum Position { } + public Integer measuringXpTarget = 500; + @ConfigEntry.Gui.CollapsibleObject public DevUtilsConfig devUtils = new DevUtilsConfig(); public static class DevUtilsConfig { diff --git a/src/client/java/dev/eposs/elementsutils/feature/xpmeter/XpMeter.java b/src/client/java/dev/eposs/elementsutils/feature/xpmeter/XpMeter.java new file mode 100644 index 0000000..8e8d04f --- /dev/null +++ b/src/client/java/dev/eposs/elementsutils/feature/xpmeter/XpMeter.java @@ -0,0 +1,185 @@ +package dev.eposs.elementsutils.feature.xpmeter; + +import dev.eposs.elementsutils.config.ModConfig; +import dev.eposs.elementsutils.mixin.client.InGameHudAccessor; +import dev.eposs.elementsutils.util.Util; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.text.Text; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class XpMeter { + private static boolean measuringXp = false; + private static int startXp = 0; + private static int currentProgress = 0; + private static long startTime = 0; + private static int noXpTicks = 0; + + private static double displayedElapsedSeconds = 0.0; + private static double displayedXpPerSecond = 0.0; + private static long lastDisplayUpdate = 0; + + private static int getTargetXp() { + return ModConfig.getConfig().measuringXpTarget; + } + + public static boolean isMeasuringXp() { + return measuringXp; + } + + public static float getXpProgress() { + return Math.min(currentProgress / (float) getTargetXp(), 1.0f); + } + + public static int getCurrentProgress() { + return currentProgress; + } + + private static final Pattern MINING_XP_PATTERN = Pattern.compile("Mining: (\\d+)"); + + private static final String trackedItemName = "Komprimiertes Basalt"; + private static int lastItemCount = 0; + private static int itemsGainedTotal = 0; + + private static int countItemInInventory(MinecraftClient client) { + int count = 0; + if (client.player != null) { + for (int i = 0; i < client.player.getInventory().size(); i++) { + var stack = client.player.getInventory().getStack(i); + if (!stack.isEmpty() && stack.getName().getString().equals(XpMeter.trackedItemName)) { + count += stack.getCount(); + } + } + } + return count; + } + + public static void startMeasuring(MinecraftClient client) { + if (client.player == null || client.world == null) return; + + Text overlayMessage = ((InGameHudAccessor) client.inGameHud).getOverlayMessage(); + String overlayText = (overlayMessage != null) ? overlayMessage.getString() : ""; + + Matcher matcher = MINING_XP_PATTERN.matcher(overlayText); + + if (!matcher.find()) { + Util.sendChatMessage(Text.literal("Messung konnte nicht gestartet werden (keine Mining XP erkannt).")); + return; + } + + startXp = Integer.parseInt(matcher.group(1)); + startTime = System.currentTimeMillis(); + currentProgress = 0; + measuringXp = true; + noXpTicks = 0; + displayedElapsedSeconds = 0.0; + displayedXpPerSecond = 0.0; + lastDisplayUpdate = System.currentTimeMillis(); + + lastItemCount = countItemInInventory(client); + itemsGainedTotal = 0; + } + + public static void updateXpMeter(MinecraftClient client) { + if (!measuringXp) return; + + Text overlayMessage = ((InGameHudAccessor) client.inGameHud).getOverlayMessage(); + String overlayText = (overlayMessage != null) ? overlayMessage.getString() : ""; + + Matcher matcher = MINING_XP_PATTERN.matcher(overlayText); + + if (!matcher.find()) { + noXpTicks++; + if (noXpTicks >= 100) { + Util.sendChatMessage(Text.literal("Messung abgebrochen: 5 Sekunden keine XP angezeigt.")); + measuringXp = false; + } + return; + } + + int currentXp = Integer.parseInt(matcher.group(1)); + currentProgress = currentXp - startXp; + noXpTicks = 0; + + int currentCount = countItemInInventory(client); + if (currentCount > lastItemCount) { + itemsGainedTotal += (currentCount - lastItemCount); + } + lastItemCount = currentCount; + + if (currentProgress >= getTargetXp()) { + double elapsed = (System.currentTimeMillis() - startTime) / 1000.0; + double averageXpPerSecond = elapsed > 0 ? currentProgress / elapsed : 0.0; + Util.sendChatMessage(Text.literal( + "§3" + getTargetXp() + "XP§r in §e" + String.format("%.2f", elapsed) + + "s§r erreicht |§b Ø " + String.format("%.2f", averageXpPerSecond) + " XP/s §r| " + + "compressed: §a+" + itemsGainedTotal + + "§r (§a+" + String.format("%.2f", itemsGainedTotal / 99.0) + " 2er§r)" + )); + measuringXp = false; + } + } + + public static void render(DrawContext context, MinecraftClient client) { + if (!isMeasuringXp()) return; + + long now = System.currentTimeMillis(); + double elapsedSeconds = (now - startTime) / 1000.0; + double xpPerSecond = elapsedSeconds > 0 ? currentProgress / elapsedSeconds : 0.0; + displayedElapsedSeconds = elapsedSeconds; + + if (now - lastDisplayUpdate > 200) { + displayedXpPerSecond = xpPerSecond; + lastDisplayUpdate = now; + } + + int width = client.getWindow().getScaledWidth(); + int y = 20; + String timeText = String.format("%.2f", displayedElapsedSeconds); + String xpPerSecondText = String.format("%.2f", displayedXpPerSecond); + + String textPrefix = "XP-Messung: "; + String progressText = getCurrentProgress() + "/" + getTargetXp(); + String timePrefix = " ("; + String timeSuffix = "s, "; + String textSuffix = "XP/s)"; + String itemsText = " +" + itemsGainedTotal + " compressed"; + + int prefixWidth = client.textRenderer.getWidth(textPrefix); + int progressWidth = client.textRenderer.getWidth(progressText); + int timePrefixWidth = client.textRenderer.getWidth(timePrefix); + int timeTextWidth = client.textRenderer.getWidth(timeText); + int timeSuffixWidth = client.textRenderer.getWidth(timeSuffix); + int xpPerSecondWidth = client.textRenderer.getWidth(xpPerSecondText); + int textSuffixWidth = client.textRenderer.getWidth(textSuffix); + int itemsWidth = client.textRenderer.getWidth(itemsText); + + int totalWidth = prefixWidth + progressWidth + timePrefixWidth + timeTextWidth + timeSuffixWidth + xpPerSecondWidth + textSuffixWidth + itemsWidth; + int x = width / 2 - totalWidth / 2; + + context.drawText(client.textRenderer, textPrefix, x, y, 0xFFFFFF00, true); + x += prefixWidth; + context.drawText(client.textRenderer, progressText, x, y, 0xFF99FF99, true); + x += progressWidth; + context.drawText(client.textRenderer, timePrefix, x, y, 0xFFFFFF, true); + x += timePrefixWidth; + context.drawText(client.textRenderer, timeText, x, y, 0xFFFFFF00, true); + x += timeTextWidth; + context.drawText(client.textRenderer, timeSuffix, x, y, 0xFFFFFF, true); + x += timeSuffixWidth; + context.drawText(client.textRenderer, xpPerSecondText, x, y, 0xFF00FFFF, true); + x += xpPerSecondWidth; + context.drawText(client.textRenderer, textSuffix, x, y, 0xFFFFFF, true); + x += textSuffixWidth; + context.drawText(client.textRenderer, itemsText, x, y, 0xFFD3D3D3, true); + + int barWidth = 200; + int barHeight = 10; + int barX = width / 2 - barWidth / 2; + int filled = (int)(barWidth * getXpProgress()); + context.fill(barX, y + 15, barX + filled, y + 15 + barHeight, 0xFF00FF00); + context.fill(barX + filled, y + 15, barX + barWidth, y + 15 + barHeight, 0xFF555555); + } +} \ No newline at end of file diff --git a/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudAccessor.java b/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudAccessor.java new file mode 100644 index 0000000..a95f34a --- /dev/null +++ b/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudAccessor.java @@ -0,0 +1,15 @@ +package dev.eposs.elementsutils.mixin.client; + +import net.minecraft.client.gui.hud.InGameHud; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(InGameHud.class) +public interface InGameHudAccessor { + @Accessor("title") + Text getTitle(); + + @Accessor("overlayMessage") + Text getOverlayMessage(); +} diff --git a/src/client/java/dev/eposs/elementsutils/rendering/ScreenRendering.java b/src/client/java/dev/eposs/elementsutils/rendering/ScreenRendering.java index 24f1b3d..f6267e6 100644 --- a/src/client/java/dev/eposs/elementsutils/rendering/ScreenRendering.java +++ b/src/client/java/dev/eposs/elementsutils/rendering/ScreenRendering.java @@ -6,6 +6,7 @@ import dev.eposs.elementsutils.feature.pet.PetDisplay; import dev.eposs.elementsutils.feature.potion.PotionDisplay; import dev.eposs.elementsutils.feature.time.TimeDisplay; +import dev.eposs.elementsutils.feature.xpmeter.XpMeter; import net.fabricmc.fabric.api.client.rendering.v1.IdentifiedLayer; import net.fabricmc.fabric.api.client.rendering.v1.LayeredDrawerWrapper; import net.minecraft.client.MinecraftClient; @@ -33,5 +34,6 @@ private static void render(DrawContext context, RenderTickCounter tickCounter) { BossTimerDisplay.render(context, client); PetDisplay.render(context, client); PotionDisplay.render(context, client); + XpMeter.render(context, client); } } diff --git a/src/client/resources/elements-utils.client.mixins.json b/src/client/resources/elements-utils.client.mixins.json index adb9c3f..0324e5c 100644 --- a/src/client/resources/elements-utils.client.mixins.json +++ b/src/client/resources/elements-utils.client.mixins.json @@ -4,6 +4,7 @@ "compatibilityLevel": "JAVA_21", "client": [ "ExampleClientMixin", + "InGameHudAccessor", "InGameHudMixin" ], "injectors": { diff --git a/src/main/resources/assets/elements-utils/lang/de_de.json b/src/main/resources/assets/elements-utils/lang/de_de.json index e1fa5ee..d749c77 100644 --- a/src/main/resources/assets/elements-utils/lang/de_de.json +++ b/src/main/resources/assets/elements-utils/lang/de_de.json @@ -22,12 +22,15 @@ "text.autoconfig.elements-utils.option.potionDisplay.show": "Tränke anzeigen", "text.autoconfig.elements-utils.option.potionDisplay.position": "Display Position", + "text.autoconfig.elements-utils.option.measuringXpTarget": "XP Messung Ziel", + "text.autoconfig.elements-utils.option.devUtils": "Development Utils (please ignore)", "text.autoconfig.elements-utils.option.devUtils.enable": "Enabled", "category.elements-utils.keys": "Elements Utils", "key.elements-utils.baseDisplayToggle": "Spieler Base Grenzen umschalten", "key.elements-utils.bossTimerToggle": "Dungeon Boss Todeszeiten umschalten", + "key.elements-utils.measureTrigger": "XP Messung starten", "key.elements-utils.devUtils": "Dev Utils (please ignore)", "elements-utils.display.pet.no_pet": "Unbekanntes Pet", diff --git a/src/main/resources/assets/elements-utils/lang/en_us.json b/src/main/resources/assets/elements-utils/lang/en_us.json index b986255..3d300e4 100644 --- a/src/main/resources/assets/elements-utils/lang/en_us.json +++ b/src/main/resources/assets/elements-utils/lang/en_us.json @@ -21,6 +21,8 @@ "text.autoconfig.elements-utils.option.potionDisplay": "Health/Mana Potion Display", "text.autoconfig.elements-utils.option.potionDisplay.show": "Show Potion Display", "text.autoconfig.elements-utils.option.potionDisplay.position": "Display Position", + + "text.autoconfig.elements-utils.option.measuringXpTarget": "XP Measurement Target", "text.autoconfig.elements-utils.option.devUtils": "Development Utils (please ignore)", "text.autoconfig.elements-utils.option.devUtils.enable": "Enabled", @@ -28,6 +30,7 @@ "category.elements-utils.keys": "Elements Utils", "key.elements-utils.baseDisplayToggle": "Toggle Base Borders", "key.elements-utils.bossTimerToggle": "Toggle Dungeon Boss Death Times", + "key.elements-utils.measureTrigger": "XP Measure Trigger", "key.elements-utils.devUtils": "Dev Utils (please ignore)", "elements-utils.display.pet.no_pet": "Unknown Pet", diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index f479476..63adb74 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -5,7 +5,8 @@ "name": "Elements Utils", "description": "Utility for Elements 3", "authors": [ - "Eposs" + "Eposs", + "nichtDanger" ], "contact": { "homepage": "https://eposs.dev/" From 373e5ddc0084c33d68aa70d84b1636d3332c586d Mon Sep 17 00:00:00 2001 From: nichtDanger <43505534+nichtDanger@users.noreply.github.com> Date: Sun, 27 Jul 2025 15:13:15 +0200 Subject: [PATCH 02/14] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 35a6e3a..d3a5d5d 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,11 @@ _Elements 3 - 1.21.4_ - Potion display (count of heal/mana potions in inv) - Translation for death.attack.magic.item (Player killed by magic damage) +## Additional Features (nichtDanger) + +- XP Meter Mining + - Measuring Basalt Gen's with Keybind (default: none) and Target XP (Config) + ## Config Use Modmenu to toggle features or change the position of the display From d6095b272c2240e48701a0f42a4997dfc6c6d16d Mon Sep 17 00:00:00 2001 From: nichtDanger Date: Sun, 27 Jul 2025 19:02:46 +0200 Subject: [PATCH 03/14] Version 1.4.1.787+1.21.4 Static Strings -> Translated - xpMeter + Timebased Measurement - xpMeter --- gradle.properties | 2 +- .../elementsutils/ElementsUtilsClient.java | 21 +- .../eposs/elementsutils/config/ModConfig.java | 7 +- .../feature/bosstimer/BossTimerDisplay.java | 2 +- .../feature/xpmeter/XpMeter.java | 208 ++++++++++++------ .../assets/elements-utils/lang/de_de.json | 18 +- .../assets/elements-utils/lang/en_us.json | 18 +- 7 files changed, 195 insertions(+), 81 deletions(-) diff --git a/gradle.properties b/gradle.properties index 93e62d7..5e2c66c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ loader_version=0.16.14 loom_version=1.10-SNAPSHOT # Mod Properties -mod_version=1.4.0.787+1.21.4 +mod_version=1.4.1.787+1.21.4 maven_group=dev.eposs.elementsutils archives_base_name=elements-utils diff --git a/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java b/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java index e36b354..79c8e5d 100644 --- a/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java +++ b/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java @@ -13,7 +13,6 @@ import me.shedaniel.autoconfig.AutoConfig; import me.shedaniel.autoconfig.serializer.Toml4jConfigSerializer; import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; @@ -32,7 +31,8 @@ public class ElementsUtilsClient implements ClientModInitializer { private static KeyBinding baseDisplayToggle; private static KeyBinding bossTimerToggle; - private static KeyBinding measureTrigger; + private static KeyBinding xpMeasureTrigger; + private static KeyBinding timeMeasureTrigger; private static KeyBinding devUtils; @Override @@ -119,8 +119,14 @@ private void registerKeyBinding() { category )); - measureTrigger = KeyBindingHelper.registerKeyBinding(new KeyBinding( - getKeyBindingTranslation("measureTrigger"), + xpMeasureTrigger = KeyBindingHelper.registerKeyBinding(new KeyBinding( + getKeyBindingTranslation("xpMeasureTrigger"), + GLFW.GLFW_KEY_UNKNOWN, + category + )); + + timeMeasureTrigger = KeyBindingHelper.registerKeyBinding(new KeyBinding( + getKeyBindingTranslation("timeMeasureTrigger"), GLFW.GLFW_KEY_UNKNOWN, category )); @@ -139,8 +145,11 @@ private void onKeyEvent(MinecraftClient client) { while (bossTimerToggle.wasPressed()) { BossTimerDisplay.toggleDisplay(client); } - while (measureTrigger.wasPressed()) { - XpMeter.startMeasuring(client); + while (xpMeasureTrigger.wasPressed()) { + XpMeter.startXPMeasurement(client); + } + while (timeMeasureTrigger.wasPressed()) { + XpMeter.startTimeMeasurement(client); } while (devUtils.wasPressed()) { DevUtil.doSomething(client); diff --git a/src/client/java/dev/eposs/elementsutils/config/ModConfig.java b/src/client/java/dev/eposs/elementsutils/config/ModConfig.java index 5a69638..5f0bb2f 100644 --- a/src/client/java/dev/eposs/elementsutils/config/ModConfig.java +++ b/src/client/java/dev/eposs/elementsutils/config/ModConfig.java @@ -83,7 +83,12 @@ public enum Position { } - public Integer measuringXpTarget = 500; + @ConfigEntry.Gui.CollapsibleObject + public XPMeterConfig xpMeterConfig = new XPMeterConfig(); + public static class XPMeterConfig { + public Integer measuringXpTarget = 500; + public Integer measuringTimeTarget = 300; + } @ConfigEntry.Gui.CollapsibleObject public DevUtilsConfig devUtils = new DevUtilsConfig(); diff --git a/src/client/java/dev/eposs/elementsutils/feature/bosstimer/BossTimerDisplay.java b/src/client/java/dev/eposs/elementsutils/feature/bosstimer/BossTimerDisplay.java index dfb013f..a029850 100644 --- a/src/client/java/dev/eposs/elementsutils/feature/bosstimer/BossTimerDisplay.java +++ b/src/client/java/dev/eposs/elementsutils/feature/bosstimer/BossTimerDisplay.java @@ -94,7 +94,7 @@ private static Text toRelativeTime(@NotNull ZonedDateTime dateTime) { sb.append(minutes).append("m "); } - return Text.translatable("elements-utils.display.bosstimer.relative", sb.toString().trim()); + return Text.translatable("elements-utils.display.bossTimer.relative", sb.toString().trim()); } private static void drawText(MinecraftClient client, DrawContext context, int line, Text text) { diff --git a/src/client/java/dev/eposs/elementsutils/feature/xpmeter/XpMeter.java b/src/client/java/dev/eposs/elementsutils/feature/xpmeter/XpMeter.java index 8e8d04f..15b80de 100644 --- a/src/client/java/dev/eposs/elementsutils/feature/xpmeter/XpMeter.java +++ b/src/client/java/dev/eposs/elementsutils/feature/xpmeter/XpMeter.java @@ -11,7 +11,14 @@ import java.util.regex.Pattern; public class XpMeter { - private static boolean measuringXp = false; + public enum MeasurementMode { + XP_TARGET, + TIME_BASED + } + + private static MeasurementMode mode = MeasurementMode.XP_TARGET; + + private static boolean measuringInProgress = false; private static int startXp = 0; private static int currentProgress = 0; private static long startTime = 0; @@ -21,28 +28,35 @@ public class XpMeter { private static double displayedXpPerSecond = 0.0; private static long lastDisplayUpdate = 0; + private static final Pattern MINING_XP_PATTERN = Pattern.compile("Mining: (\\d+)"); + private static final String trackedItemName = "Komprimiertes Basalt"; + private static int lastItemCount = 0; + private static int itemsGainedTotal = 0; + private static int getTargetXp() { - return ModConfig.getConfig().measuringXpTarget; + return ModConfig.getConfig().xpMeterConfig.measuringXpTarget; + } + + private static int getTargetTime() { + return ModConfig.getConfig().xpMeterConfig.measuringTimeTarget * 1000; } - public static boolean isMeasuringXp() { - return measuringXp; + public static boolean isMeasuringInProgress() { + return measuringInProgress; } public static float getXpProgress() { - return Math.min(currentProgress / (float) getTargetXp(), 1.0f); + if (mode == MeasurementMode.XP_TARGET) { + return Math.min(currentProgress / (float) getTargetXp(), 1.0f); + } else { + return Math.min((System.currentTimeMillis() - startTime) / (float) getTargetTime(), 1.0f); + } } public static int getCurrentProgress() { return currentProgress; } - private static final Pattern MINING_XP_PATTERN = Pattern.compile("Mining: (\\d+)"); - - private static final String trackedItemName = "Komprimiertes Basalt"; - private static int lastItemCount = 0; - private static int itemsGainedTotal = 0; - private static int countItemInInventory(MinecraftClient client) { int count = 0; if (client.player != null) { @@ -56,8 +70,18 @@ private static int countItemInInventory(MinecraftClient client) { return count; } - public static void startMeasuring(MinecraftClient client) { - if (client.player == null || client.world == null) return; + public static void startXPMeasurement(MinecraftClient client) { + if (startMeasurement(client)) return; + mode = MeasurementMode.XP_TARGET; + } + + public static void startTimeMeasurement(MinecraftClient client) { + if (startMeasurement(client)) return; + mode = MeasurementMode.TIME_BASED; + } + + private static boolean startMeasurement(MinecraftClient client) { + if (client.player == null || client.world == null) return true; Text overlayMessage = ((InGameHudAccessor) client.inGameHud).getOverlayMessage(); String overlayText = (overlayMessage != null) ? overlayMessage.getString() : ""; @@ -65,14 +89,14 @@ public static void startMeasuring(MinecraftClient client) { Matcher matcher = MINING_XP_PATTERN.matcher(overlayText); if (!matcher.find()) { - Util.sendChatMessage(Text.literal("Messung konnte nicht gestartet werden (keine Mining XP erkannt).")); - return; + Util.sendChatMessage(Text.translatable("elements-utils.message.xpMeter.startFailed")); + return true; } startXp = Integer.parseInt(matcher.group(1)); startTime = System.currentTimeMillis(); currentProgress = 0; - measuringXp = true; + measuringInProgress = true; noXpTicks = 0; displayedElapsedSeconds = 0.0; displayedXpPerSecond = 0.0; @@ -80,10 +104,11 @@ public static void startMeasuring(MinecraftClient client) { lastItemCount = countItemInInventory(client); itemsGainedTotal = 0; + return false; } public static void updateXpMeter(MinecraftClient client) { - if (!measuringXp) return; + if (!measuringInProgress) return; Text overlayMessage = ((InGameHudAccessor) client.inGameHud).getOverlayMessage(); String overlayText = (overlayMessage != null) ? overlayMessage.getString() : ""; @@ -93,8 +118,8 @@ public static void updateXpMeter(MinecraftClient client) { if (!matcher.find()) { noXpTicks++; if (noXpTicks >= 100) { - Util.sendChatMessage(Text.literal("Messung abgebrochen: 5 Sekunden keine XP angezeigt.")); - measuringXp = false; + Util.sendChatMessage(Text.translatable("elements-utils.message.xpMeter.failed")); + measuringInProgress = false; } return; } @@ -109,21 +134,40 @@ public static void updateXpMeter(MinecraftClient client) { } lastItemCount = currentCount; - if (currentProgress >= getTargetXp()) { - double elapsed = (System.currentTimeMillis() - startTime) / 1000.0; - double averageXpPerSecond = elapsed > 0 ? currentProgress / elapsed : 0.0; - Util.sendChatMessage(Text.literal( - "§3" + getTargetXp() + "XP§r in §e" + String.format("%.2f", elapsed) + - "s§r erreicht |§b Ø " + String.format("%.2f", averageXpPerSecond) + " XP/s §r| " + - "compressed: §a+" + itemsGainedTotal + - "§r (§a+" + String.format("%.2f", itemsGainedTotal / 99.0) + " 2er§r)" - )); - measuringXp = false; + if (mode == MeasurementMode.XP_TARGET) { + if (currentProgress >= getTargetXp()) { + double elapsed = (System.currentTimeMillis() - startTime) / 1000.0; + double averageXpPerSecond = elapsed > 0 ? currentProgress / elapsed : 0.0; + Util.sendChatMessage(Text.literal( + "§3" + getTargetXp() + + Text.translatable("elements-utils.message.xpMeter.xpFinished").getString() + .replace("%s", String.format("%.2f", elapsed)) + .replace("%a", String.format("%.2f", averageXpPerSecond)) + .replace("%i", String.valueOf(itemsGainedTotal)) + .replace("%c", String.format("%.2f", itemsGainedTotal / 99.0)) + )); + measuringInProgress = false; + } + } else if (mode == MeasurementMode.TIME_BASED) { + long elapsed = System.currentTimeMillis() - startTime; + if (elapsed >= getTargetTime()) { + double seconds = elapsed / 1000.0; + double averageXpPerSecond = seconds > 0 ? currentProgress / seconds : 0.0; + Util.sendChatMessage(Text.literal( + "§e" + String.format("%.2f", seconds) + + Text.translatable("elements-utils.message.xpMeter.timeFinished").getString() + .replace("%p", String.valueOf(currentProgress)) + .replace("%a", String.format("%.2f", averageXpPerSecond)) + .replace("%i", String.valueOf(itemsGainedTotal)) + .replace("%c", String.format("%.2f", itemsGainedTotal / 99.0)) + )); + measuringInProgress = false; + } } } public static void render(DrawContext context, MinecraftClient client) { - if (!isMeasuringXp()) return; + if (!isMeasuringInProgress()) return; long now = System.currentTimeMillis(); double elapsedSeconds = (now - startTime) / 1000.0; @@ -139,47 +183,79 @@ public static void render(DrawContext context, MinecraftClient client) { int y = 20; String timeText = String.format("%.2f", displayedElapsedSeconds); String xpPerSecondText = String.format("%.2f", displayedXpPerSecond); + String itemsText = " +" + itemsGainedTotal + " " + Text.translatable("elements-utils.message.xpMeter.compressed").getString(); + + int barWidth = 200; + int barHeight = 10; + int barX = width / 2 - barWidth / 2; + int filled = (int)(barWidth * getXpProgress()); - String textPrefix = "XP-Messung: "; - String progressText = getCurrentProgress() + "/" + getTargetXp(); - String timePrefix = " ("; - String timeSuffix = "s, "; - String textSuffix = "XP/s)"; - String itemsText = " +" + itemsGainedTotal + " compressed"; - - int prefixWidth = client.textRenderer.getWidth(textPrefix); - int progressWidth = client.textRenderer.getWidth(progressText); - int timePrefixWidth = client.textRenderer.getWidth(timePrefix); - int timeTextWidth = client.textRenderer.getWidth(timeText); - int timeSuffixWidth = client.textRenderer.getWidth(timeSuffix); - int xpPerSecondWidth = client.textRenderer.getWidth(xpPerSecondText); - int textSuffixWidth = client.textRenderer.getWidth(textSuffix); - int itemsWidth = client.textRenderer.getWidth(itemsText); - - int totalWidth = prefixWidth + progressWidth + timePrefixWidth + timeTextWidth + timeSuffixWidth + xpPerSecondWidth + textSuffixWidth + itemsWidth; - int x = width / 2 - totalWidth / 2; - - context.drawText(client.textRenderer, textPrefix, x, y, 0xFFFFFF00, true); - x += prefixWidth; - context.drawText(client.textRenderer, progressText, x, y, 0xFF99FF99, true); - x += progressWidth; - context.drawText(client.textRenderer, timePrefix, x, y, 0xFFFFFF, true); - x += timePrefixWidth; - context.drawText(client.textRenderer, timeText, x, y, 0xFFFFFF00, true); - x += timeTextWidth; - context.drawText(client.textRenderer, timeSuffix, x, y, 0xFFFFFF, true); - x += timeSuffixWidth; + if (mode == MeasurementMode.XP_TARGET) { + String textPrefix = Text.translatable("elements-utils.message.xpMeter.xpMode").getString(); + String progressText = getCurrentProgress() + "XP/" + getTargetXp() + "XP"; + String timePrefix = " ("; + String timeSuffix = "§es §r| "; + String textSuffix = "§bXP/s§r)"; + + int prefixWidth = client.textRenderer.getWidth(textPrefix); + int progressWidth = client.textRenderer.getWidth(progressText); + int timePrefixWidth = client.textRenderer.getWidth(timePrefix); + int timeTextWidth = client.textRenderer.getWidth(timeText); + int timeSuffixWidth = client.textRenderer.getWidth(timeSuffix); + int xpPerSecondWidth = client.textRenderer.getWidth(xpPerSecondText); + int textSuffixWidth = client.textRenderer.getWidth(textSuffix); + int itemsWidth = client.textRenderer.getWidth(itemsText); + + int totalWidth = prefixWidth + progressWidth + timePrefixWidth + timeTextWidth + timeSuffixWidth + xpPerSecondWidth + textSuffixWidth + itemsWidth; + int x = width / 2 - totalWidth / 2; + + context.drawText(client.textRenderer, textPrefix, x, y, 0xFFFFFF00, true); + x += prefixWidth; + context.drawText(client.textRenderer, progressText, x, y, 0xFF99FF99, true); + x += progressWidth; + context.drawText(client.textRenderer, timePrefix, x, y, 0xFFFFFF, true); + x += timePrefixWidth; + context.drawText(client.textRenderer, timeText, x, y, 0xFFFFFF00, true); + x += timeTextWidth; + context.drawText(client.textRenderer, timeSuffix, x, y, 0xFFFFFF, true); + x += timeSuffixWidth; + drawContext(context, client, y, xpPerSecondText, itemsText, textSuffix, xpPerSecondWidth, textSuffixWidth, x); + + context.fill(barX, y + 15, barX + filled, y + 15 + barHeight, 0xFF00FF00); + context.fill(barX + filled, y + 15, barX + barWidth, y + 15 + barHeight, 0xFF555555); + } else if (mode == MeasurementMode.TIME_BASED) { + String textPrefix = Text.translatable("elements-utils.message.xpMeter.timeMode").getString(); + String xpText = currentProgress + "XP "; + String timeRatio = String.format("§r(§e%.2fs§r/§e%.2fs §r| ", displayedElapsedSeconds, getTargetTime() / 1000.0); + String textSuffix = "§bXP/s§r)"; + int prefixWidth = client.textRenderer.getWidth(textPrefix); + int xpTextWidth = client.textRenderer.getWidth(xpText); + int timeRatioWidth = client.textRenderer.getWidth(timeRatio); + int xpPerSecondWidth = client.textRenderer.getWidth(xpPerSecondText); + int textSuffixWidth = client.textRenderer.getWidth(textSuffix); + int itemsWidth = client.textRenderer.getWidth(itemsText); + + int totalWidth = prefixWidth + xpTextWidth + timeRatioWidth + xpPerSecondWidth + textSuffixWidth + itemsWidth; + int x = width / 2 - totalWidth / 2; + + context.drawText(client.textRenderer, textPrefix, x, y, 0xFF00FFFF, true); + x += prefixWidth; + context.drawText(client.textRenderer, xpText, x, y, 0xFF99FF99, true); + x += xpTextWidth; + context.drawText(client.textRenderer, timeRatio, x, y, 0xFFD3D3D3, true); + x += timeRatioWidth; + drawContext(context, client, y, xpPerSecondText, itemsText, textSuffix, xpPerSecondWidth, textSuffixWidth, x); + + context.fill(barX, y + 15, barX + filled, y + 15 + barHeight, 0xFF00FFFF); + context.fill(barX + filled, y + 15, barX + barWidth, y + 15 + barHeight, 0xFF555555); + } + } + + private static void drawContext(DrawContext context, MinecraftClient client, int y, String xpPerSecondText, String itemsText, String textSuffix, int xpPerSecondWidth, int textSuffixWidth, int x) { context.drawText(client.textRenderer, xpPerSecondText, x, y, 0xFF00FFFF, true); x += xpPerSecondWidth; context.drawText(client.textRenderer, textSuffix, x, y, 0xFFFFFF, true); x += textSuffixWidth; context.drawText(client.textRenderer, itemsText, x, y, 0xFFD3D3D3, true); - - int barWidth = 200; - int barHeight = 10; - int barX = width / 2 - barWidth / 2; - int filled = (int)(barWidth * getXpProgress()); - context.fill(barX, y + 15, barX + filled, y + 15 + barHeight, 0xFF00FF00); - context.fill(barX + filled, y + 15, barX + barWidth, y + 15 + barHeight, 0xFF555555); } } \ No newline at end of file diff --git a/src/main/resources/assets/elements-utils/lang/de_de.json b/src/main/resources/assets/elements-utils/lang/de_de.json index d749c77..d17d6c2 100644 --- a/src/main/resources/assets/elements-utils/lang/de_de.json +++ b/src/main/resources/assets/elements-utils/lang/de_de.json @@ -22,7 +22,9 @@ "text.autoconfig.elements-utils.option.potionDisplay.show": "Tränke anzeigen", "text.autoconfig.elements-utils.option.potionDisplay.position": "Display Position", - "text.autoconfig.elements-utils.option.measuringXpTarget": "XP Messung Ziel", + "text.autoconfig.elements-utils.option.xpMeterConfig": "XP-Meter Config (Basalt)", + "text.autoconfig.elements-utils.option.xpMeterConfig.measuringXpTarget": "XP-Messung Ziel (XP)", + "text.autoconfig.elements-utils.option.xpMeterConfig.measuringTimeTarget": "Zeit-Messung Ziel (Sekunden)", "text.autoconfig.elements-utils.option.devUtils": "Development Utils (please ignore)", "text.autoconfig.elements-utils.option.devUtils.enable": "Enabled", @@ -30,14 +32,24 @@ "category.elements-utils.keys": "Elements Utils", "key.elements-utils.baseDisplayToggle": "Spieler Base Grenzen umschalten", "key.elements-utils.bossTimerToggle": "Dungeon Boss Todeszeiten umschalten", - "key.elements-utils.measureTrigger": "XP Messung starten", + "key.elements-utils.xpMeasureTrigger": "XP-Messung (Basalt) starten", + "key.elements-utils.timeMeasureTrigger": "Zeit-Messung (Basalt) starten", "key.elements-utils.devUtils": "Dev Utils (please ignore)", "elements-utils.display.pet.no_pet": "Unbekanntes Pet", - "elements-utils.display.bosstimer.relative": "Vor %s", + "elements-utils.display.bossTimer.relative": "Vor %s", "elements-utils.display.base.toggle": "Base Anzeige is jetzt ", + + "elements-utils.message.xpMeter.failed": "Messung abgebrochen: 5 Sekunden keine XP angezeigt", + "elements-utils.message.xpMeter.startFailed": "Messung konnte nicht gestartet werden (keine Mining XP erkannt)", + "elements-utils.message.xpMeter.xpMode": "XP-Messung: ", + "elements-utils.message.xpMeter.timeMode": "Zeit-Messung: ", + "elements-utils.message.xpMeter.compressed": "Komprimierte", + + "elements-utils.message.xpMeter.cpFinished": "XP§r in §e%ss§r erreicht | §bØ %a XP/s §r| Komprimierte: §a+%i§r (§a+%c 2er§r)", + "elements-utils.message.xpMeter.timeFinished": "s§r vorbei. | §aTotal: %pXP§r | §bØ %a XP/s §r| Komprimierte: §a+%i§r (§a+%c 2er§r)", "elements-utils.enabled": "aktiviert", "elements-utils.disabled": "deaktiviert", diff --git a/src/main/resources/assets/elements-utils/lang/en_us.json b/src/main/resources/assets/elements-utils/lang/en_us.json index 3d300e4..739fa1a 100644 --- a/src/main/resources/assets/elements-utils/lang/en_us.json +++ b/src/main/resources/assets/elements-utils/lang/en_us.json @@ -22,7 +22,9 @@ "text.autoconfig.elements-utils.option.potionDisplay.show": "Show Potion Display", "text.autoconfig.elements-utils.option.potionDisplay.position": "Display Position", - "text.autoconfig.elements-utils.option.measuringXpTarget": "XP Measurement Target", + "text.autoconfig.elements-utils.option.xpMeterConfig": "XP-Meter Config (Basalt)", + "text.autoconfig.elements-utils.option.xpMeterConfig.measuringXpTarget": "XP-Measurement Target (XP)", + "text.autoconfig.elements-utils.option.xpMeterConfig.measuringTimeTarget": "Time-Measurement Target (Seconds)", "text.autoconfig.elements-utils.option.devUtils": "Development Utils (please ignore)", "text.autoconfig.elements-utils.option.devUtils.enable": "Enabled", @@ -30,15 +32,25 @@ "category.elements-utils.keys": "Elements Utils", "key.elements-utils.baseDisplayToggle": "Toggle Base Borders", "key.elements-utils.bossTimerToggle": "Toggle Dungeon Boss Death Times", - "key.elements-utils.measureTrigger": "XP Measure Trigger", + "key.elements-utils.xpMeasureTrigger": "XP-Measurement (Basalt) Trigger", + "key.elements-utils.timeMeasureTrigger": "Time-Measurement (Basalt) Trigger", "key.elements-utils.devUtils": "Dev Utils (please ignore)", "elements-utils.display.pet.no_pet": "Unknown Pet", - "elements-utils.display.bosstimer.relative": "%s ago", + "elements-utils.display.bossTimer.relative": "%s ago", "elements-utils.display.base.toggle": "Base display is now ", + "elements-utils.message.xpMeter.failed": "Measurement aborted: No XP displayed for 5 seconds", + "elements-utils.message.xpMeter.startFailed": "Measurement could not be started (no Mining XP detected)", + "elements-utils.message.xpMeter.xpMode": "XP-Measurement: ", + "elements-utils.message.xpMeter.timeMode": "Time-Measurement: ", + "elements-utils.message.xpMeter.compressed": "Compressed", + + "elements-utils.message.xpMeter.xpFinished": "XP§r achieved in §e%ss§r | §bØ %a XP/s §r| Compressed: §a+%i§r (§a+%c 2er§r)", + "elements-utils.message.xpMeter.timeFinished": "s§r over. | §aTotal: %pXP§r | §bØ %a XP/s §r| Compressed: §a+%i§r (§a+%c 2er§r)", + "elements-utils.enabled": "enabled", "elements-utils.disabled": "disabled", "elements-utils.unknown": "unknown" From e53d5889908fce7ce31c3ff2995dd61e1aa9823b Mon Sep 17 00:00:00 2001 From: nichtDanger <43505534+nichtDanger@users.noreply.github.com> Date: Sun, 27 Jul 2025 19:03:24 +0200 Subject: [PATCH 04/14] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d3a5d5d..db65568 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,8 @@ _Elements 3 - 1.21.4_ ## Additional Features (nichtDanger) -- XP Meter Mining - - Measuring Basalt Gen's with Keybind (default: none) and Target XP (Config) +- XP Meter (Basalt) + - Measuring Basalt Gen's with Keybind (default: none) and Target XP / Target Time (Config) ## Config From cc1cc10cfbc4fda2c24dae5a0155779d482ee3cf Mon Sep 17 00:00:00 2001 From: nichtDanger Date: Sun, 27 Jul 2025 19:04:47 +0200 Subject: [PATCH 05/14] Update de_de.json --- src/main/resources/assets/elements-utils/lang/de_de.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/assets/elements-utils/lang/de_de.json b/src/main/resources/assets/elements-utils/lang/de_de.json index d17d6c2..1bb5a33 100644 --- a/src/main/resources/assets/elements-utils/lang/de_de.json +++ b/src/main/resources/assets/elements-utils/lang/de_de.json @@ -1,6 +1,6 @@ { "death.attack.magic.item": "%1$s wurde von %2$s mit der Magie von %3$s getötet", - + "text.autoconfig.elements-utils.title": "Elements Utils Config", "text.autoconfig.elements-utils.option.showMoonPhaseDisplay": "Mond Phase anzeigen", @@ -48,9 +48,9 @@ "elements-utils.message.xpMeter.timeMode": "Zeit-Messung: ", "elements-utils.message.xpMeter.compressed": "Komprimierte", - "elements-utils.message.xpMeter.cpFinished": "XP§r in §e%ss§r erreicht | §bØ %a XP/s §r| Komprimierte: §a+%i§r (§a+%c 2er§r)", + "elements-utils.message.xpMeter.xpFinished": "XP§r in §e%ss§r erreicht | §bØ %a XP/s §r| Komprimierte: §a+%i§r (§a+%c 2er§r)", "elements-utils.message.xpMeter.timeFinished": "s§r vorbei. | §aTotal: %pXP§r | §bØ %a XP/s §r| Komprimierte: §a+%i§r (§a+%c 2er§r)", - + "elements-utils.enabled": "aktiviert", "elements-utils.disabled": "deaktiviert", "elements-utils.unknown": "unbekannt" From dcd677bba5999f653e0275a6b7fa9efe102b83ec Mon Sep 17 00:00:00 2001 From: nichtDanger <43505534+nichtDanger@users.noreply.github.com> Date: Tue, 29 Jul 2025 01:48:18 +0200 Subject: [PATCH 06/14] formatPlayerLevel --- .../eposs/elementsutils/config/ModConfig.java | 2 + .../mixin/client/InGameHudMixin.java | 26 +++++++++++ .../mixin/client/PlayerListHudMixin.java | 43 +++++++++++++++++++ .../dev/eposs/elementsutils/util/Util.java | 4 ++ .../elements-utils.client.mixins.json | 3 +- .../assets/elements-utils/lang/de_de.json | 2 + .../assets/elements-utils/lang/en_us.json | 2 + 7 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/client/java/dev/eposs/elementsutils/mixin/client/PlayerListHudMixin.java diff --git a/src/client/java/dev/eposs/elementsutils/config/ModConfig.java b/src/client/java/dev/eposs/elementsutils/config/ModConfig.java index 5f0bb2f..702af7f 100644 --- a/src/client/java/dev/eposs/elementsutils/config/ModConfig.java +++ b/src/client/java/dev/eposs/elementsutils/config/ModConfig.java @@ -90,6 +90,8 @@ public static class XPMeterConfig { public Integer measuringTimeTarget = 300; } + public boolean formatPlayerLevel = true; + @ConfigEntry.Gui.CollapsibleObject public DevUtilsConfig devUtils = new DevUtilsConfig(); public static class DevUtilsConfig { diff --git a/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java b/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java index 39f3c4a..fc7636d 100644 --- a/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java +++ b/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java @@ -1,14 +1,19 @@ package dev.eposs.elementsutils.mixin.client; +import dev.eposs.elementsutils.config.ModConfig; import dev.eposs.elementsutils.feature.pet.PetDisplay; +import dev.eposs.elementsutils.util.Util; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.hud.InGameHud; import net.minecraft.client.render.RenderTickCounter; import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Final; 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.ModifyArg; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(value = InGameHud.class) @@ -18,6 +23,9 @@ public abstract class InGameHudMixin { private Text overlayMessage; @Shadow private int overlayRemaining; + @Final + @Shadow + private MinecraftClient client; @Inject(at = @At("HEAD"), method = "renderOverlayMessage") private void renderOverlayMessage(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) { @@ -25,4 +33,22 @@ private void renderOverlayMessage(DrawContext context, RenderTickCounter tickCou PetDisplay.updatePetXP(this.overlayMessage, false); } } + + @ModifyArg( + method = "renderExperienceLevel", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/DrawContext;drawText(Lnet/minecraft/client/font/TextRenderer;Ljava/lang/String;IIIZ)I" + ), + index = 1 + ) + private String modifyLevelText(String original) { + if (!ModConfig.getConfig().formatPlayerLevel) return original; + try { + int level = Integer.parseInt(original); + return Util.formatLevel(level); + } catch (NumberFormatException e) { + return original; + } + } } diff --git a/src/client/java/dev/eposs/elementsutils/mixin/client/PlayerListHudMixin.java b/src/client/java/dev/eposs/elementsutils/mixin/client/PlayerListHudMixin.java new file mode 100644 index 0000000..133adcd --- /dev/null +++ b/src/client/java/dev/eposs/elementsutils/mixin/client/PlayerListHudMixin.java @@ -0,0 +1,43 @@ +package dev.eposs.elementsutils.mixin.client; + +import dev.eposs.elementsutils.config.ModConfig; +import dev.eposs.elementsutils.util.Util; +import net.minecraft.client.gui.hud.PlayerListHud; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArgs; +import org.spongepowered.asm.mixin.injection.invoke.arg.Args; + +@Mixin(PlayerListHud.class) +public class PlayerListHudMixin { + + @ModifyArgs( + method = "renderScoreboardObjective", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/DrawContext;drawTextWithShadow(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/Text;III)I" + ) + ) + private void adjustScoreDrawArgs(Args args) { + if (!ModConfig.getConfig().formatPlayerLevel) return; + + Text originalScoreText = args.get(1); + int x = args.get(2); + + String original = originalScoreText.getString().replace(".", "").replace(",", ""); + int score; + try { + score = Integer.parseInt(original); + } catch (NumberFormatException e) { + return; + } + + String formatted = Util.formatLevel(score); + int diff = formatted.length() - original.length(); + int pixelPerChar = 2; + + args.set(1, Text.literal(formatted).setStyle(originalScoreText.getStyle())); + args.set(2, x - (diff * pixelPerChar)); + } +} diff --git a/src/client/java/dev/eposs/elementsutils/util/Util.java b/src/client/java/dev/eposs/elementsutils/util/Util.java index f73a539..7b47ff3 100644 --- a/src/client/java/dev/eposs/elementsutils/util/Util.java +++ b/src/client/java/dev/eposs/elementsutils/util/Util.java @@ -31,4 +31,8 @@ public static GenericContainerScreen getEnderChestScreen() { return null; } + + public static String formatLevel(int level) { + return String.format("%,d", level).replace(',', '.'); + } } diff --git a/src/client/resources/elements-utils.client.mixins.json b/src/client/resources/elements-utils.client.mixins.json index 0324e5c..1c6d0d5 100644 --- a/src/client/resources/elements-utils.client.mixins.json +++ b/src/client/resources/elements-utils.client.mixins.json @@ -5,7 +5,8 @@ "client": [ "ExampleClientMixin", "InGameHudAccessor", - "InGameHudMixin" + "InGameHudMixin", + "PlayerListHudMixin" ], "injectors": { "defaultRequire": 1 diff --git a/src/main/resources/assets/elements-utils/lang/de_de.json b/src/main/resources/assets/elements-utils/lang/de_de.json index 1bb5a33..fc61020 100644 --- a/src/main/resources/assets/elements-utils/lang/de_de.json +++ b/src/main/resources/assets/elements-utils/lang/de_de.json @@ -26,6 +26,8 @@ "text.autoconfig.elements-utils.option.xpMeterConfig.measuringXpTarget": "XP-Messung Ziel (XP)", "text.autoconfig.elements-utils.option.xpMeterConfig.measuringTimeTarget": "Zeit-Messung Ziel (Sekunden)", + "text.autoconfig.elements-utils.option.formatPlayerLevel": "Formatieren der Spielerlevel", + "text.autoconfig.elements-utils.option.devUtils": "Development Utils (please ignore)", "text.autoconfig.elements-utils.option.devUtils.enable": "Enabled", diff --git a/src/main/resources/assets/elements-utils/lang/en_us.json b/src/main/resources/assets/elements-utils/lang/en_us.json index 739fa1a..8d29231 100644 --- a/src/main/resources/assets/elements-utils/lang/en_us.json +++ b/src/main/resources/assets/elements-utils/lang/en_us.json @@ -25,6 +25,8 @@ "text.autoconfig.elements-utils.option.xpMeterConfig": "XP-Meter Config (Basalt)", "text.autoconfig.elements-utils.option.xpMeterConfig.measuringXpTarget": "XP-Measurement Target (XP)", "text.autoconfig.elements-utils.option.xpMeterConfig.measuringTimeTarget": "Time-Measurement Target (Seconds)", + + "text.autoconfig.elements-utils.option.formatPlayerLevel": "Format player levels", "text.autoconfig.elements-utils.option.devUtils": "Development Utils (please ignore)", "text.autoconfig.elements-utils.option.devUtils.enable": "Enabled", From 17498afdf6f88d1c40ce0c45602a8dbe70071dab Mon Sep 17 00:00:00 2001 From: nichtDanger <43505534+nichtDanger@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:21:21 +0200 Subject: [PATCH 07/14] GameMessageHandler --- .../elementsutils/ElementsUtilsClient.java | 37 +++++++- .../common/GameMessageHandler.java | 89 +++++++++++++++++++ .../assets/elements-utils/lang/de_de.json | 2 +- .../assets/elements-utils/lang/en_us.json | 2 +- 4 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java diff --git a/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java b/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java index 79c8e5d..9ce9f0d 100644 --- a/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java +++ b/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java @@ -1,5 +1,6 @@ package dev.eposs.elementsutils; +import com.terraformersmc.modmenu.util.mod.fabric.FabricMod; import dev.eposs.elementsutils.config.ModConfig; import dev.eposs.elementsutils.feature.bosstimer.BossTimerData; import dev.eposs.elementsutils.feature.bosstimer.BossTimerDisplay; @@ -10,6 +11,7 @@ import dev.eposs.elementsutils.feature.xpmeter.XpMeter; import dev.eposs.elementsutils.rendering.ScreenRendering; import dev.eposs.elementsutils.util.DevUtil; +import dev.eposs.elementsutils.common.GameMessageHandler; import me.shedaniel.autoconfig.AutoConfig; import me.shedaniel.autoconfig.serializer.Toml4jConfigSerializer; import net.fabricmc.api.ClientModInitializer; @@ -20,6 +22,7 @@ import net.fabricmc.fabric.api.client.rendering.v1.HudLayerRegistrationCallback; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.ServerInfo; @@ -28,6 +31,11 @@ import org.apache.commons.codec.digest.DigestUtils; import org.lwjgl.glfw.GLFW; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + public class ElementsUtilsClient implements ClientModInitializer { private static KeyBinding baseDisplayToggle; private static KeyBinding bossTimerToggle; @@ -35,6 +43,10 @@ public class ElementsUtilsClient implements ClientModInitializer { private static KeyBinding timeMeasureTrigger; private static KeyBinding devUtils; + private static final Set HIDDEN_MOD_IDS = Set.of( + "java", "minecraft", "mixinextras" + ); + @Override public void onInitializeClient() { // This entrypoint is suitable for setting up client-specific logic, such as rendering. @@ -66,11 +78,17 @@ private void clientTick(MinecraftClient client) { PetDisplay.updatePet(client); PotionDisplay.updatePotions(client); XpMeter.updateXpMeter(client); + GameMessageHandler.processPendingCommands(client); } private void onJoin(ClientPlayNetworkHandler handler, PacketSender sender, MinecraftClient client) { runServerCheck(client); PetDisplay.loadPet(handler.getRegistryManager()); + + ModConfig.InternalConfig.Servers server = ModConfig.getConfig().internal.server; + if (server != ModConfig.InternalConfig.Servers.UNKNOWN) { + GameMessageHandler.queueModlistCommands(client, getUserMods(), "/modlist ", 256); + } } private void onLeave(ClientPlayNetworkHandler handler, MinecraftClient client) { @@ -100,9 +118,8 @@ private void runServerCheck(MinecraftClient client) { private boolean onGameMessage(Text text, boolean b) { LootSound.onGameMessage(text); - - return true; - } + return GameMessageHandler.onGameMessage(text); + } private void registerKeyBinding() { String category = "category." + ElementsUtils.MOD_ID + ".keys"; @@ -159,4 +176,18 @@ private void onKeyEvent(MinecraftClient client) { private String getKeyBindingTranslation(String keyBinding) { return "key." + ElementsUtils.MOD_ID + "." + keyBinding; } + + private List getUserMods() { + Set modpackMods = new HashSet<>(); + return FabricLoader.getInstance().getAllMods().stream() + .filter(mod -> { + FabricMod fabricMod = new FabricMod(mod, modpackMods); + String parent = fabricMod.getParent(); + String id = mod.getMetadata().getId(); + return (parent == null || !parent.equals("fabric-api")) + && !HIDDEN_MOD_IDS.contains(id); + }) + .map(mod -> mod.getMetadata().getId()) + .collect(Collectors.toList()); + } } diff --git a/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java b/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java new file mode 100644 index 0000000..172c382 --- /dev/null +++ b/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java @@ -0,0 +1,89 @@ +package dev.eposs.elementsutils.common; + +import dev.eposs.elementsutils.ElementsUtils; +import net.minecraft.client.MinecraftClient; +import net.minecraft.text.Text; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * Handler for sending and filtering modlist commands and related chat messages. + */ +public class GameMessageHandler { + /** + * Queue for pending modlist commands. + */ + private static final Queue pendingCommands = new LinkedList<>(); + + /** + * Timestamp until which chat messages are suppressed. + */ + private static long suppressUntil = 0; + + /** + * Splits the mod list into multiple commands and adds them to the queue. + * + * @param client The current Minecraft client instance. + * @param modIds List of mod IDs to be sent. + * @param prefix Prefix for each command (e.g. "/modlist "). + * @param maxLength Maximum length of a command. + */ + public static void queueModlistCommands(MinecraftClient client, List modIds, String prefix, int maxLength) { + List messages = splitModList(modIds, prefix, maxLength); + pendingCommands.addAll(messages); + suppressUntil = System.currentTimeMillis() + 2000; + } + + /** + * Filters chat messages to hide unwanted responses to modlist commands. + * + * @param text The received chat message. + * @return true if the message should be shown, false otherwise. + */ + public static boolean onGameMessage(Text text) { + String msg = text.getString(); + boolean suppress = System.currentTimeMillis() < suppressUntil; + return !suppress || !msg.startsWith("Unknown or incomplete command") || !msg.contains("modlist"); + } + + /** + * Sends all pending modlist commands to the server. + * + * @param client The current Minecraft client instance. + */ + public static void processPendingCommands(MinecraftClient client) { + if (!pendingCommands.isEmpty() && client.player != null) { + while (!pendingCommands.isEmpty()) { + String cmd = pendingCommands.poll(); + client.player.networkHandler.sendChatCommand(cmd.startsWith("/") ? cmd.substring(1) : cmd); + } + ElementsUtils.LOGGER.info("Mod List sent to Server-Console."); + } + } + + /** + * Splits a list of mods into multiple strings so that each command does not exceed the maximum length. + * + * @param mods The mod IDs. + * @param prefix Prefix for each command. + * @param maxLength Maximum length of a command. + * @return List of split commands. + */ + private static List splitModList(List mods, String prefix, int maxLength) { + List result = new LinkedList<>(); + StringBuilder current = new StringBuilder(prefix); + + for (String mod : mods) { + if (current.length() + mod.length() + 1 > maxLength) { + result.add(current.toString().trim()); + current = new StringBuilder(prefix); + } + current.append(mod).append(" "); + } + if (current.length() > prefix.length()) { + result.add(current.toString().trim()); + } + return result; + } +} \ No newline at end of file diff --git a/src/main/resources/assets/elements-utils/lang/de_de.json b/src/main/resources/assets/elements-utils/lang/de_de.json index fc61020..d56702e 100644 --- a/src/main/resources/assets/elements-utils/lang/de_de.json +++ b/src/main/resources/assets/elements-utils/lang/de_de.json @@ -26,7 +26,7 @@ "text.autoconfig.elements-utils.option.xpMeterConfig.measuringXpTarget": "XP-Messung Ziel (XP)", "text.autoconfig.elements-utils.option.xpMeterConfig.measuringTimeTarget": "Zeit-Messung Ziel (Sekunden)", - "text.autoconfig.elements-utils.option.formatPlayerLevel": "Formatieren der Spielerlevel", + "text.autoconfig.elements-utils.option.formatPlayerLevel": "Formatierte Anzeige der Spielerlevel", "text.autoconfig.elements-utils.option.devUtils": "Development Utils (please ignore)", "text.autoconfig.elements-utils.option.devUtils.enable": "Enabled", diff --git a/src/main/resources/assets/elements-utils/lang/en_us.json b/src/main/resources/assets/elements-utils/lang/en_us.json index 8d29231..c96c138 100644 --- a/src/main/resources/assets/elements-utils/lang/en_us.json +++ b/src/main/resources/assets/elements-utils/lang/en_us.json @@ -26,7 +26,7 @@ "text.autoconfig.elements-utils.option.xpMeterConfig.measuringXpTarget": "XP-Measurement Target (XP)", "text.autoconfig.elements-utils.option.xpMeterConfig.measuringTimeTarget": "Time-Measurement Target (Seconds)", - "text.autoconfig.elements-utils.option.formatPlayerLevel": "Format player levels", + "text.autoconfig.elements-utils.option.formatPlayerLevel": "Format display of player levels", "text.autoconfig.elements-utils.option.devUtils": "Development Utils (please ignore)", "text.autoconfig.elements-utils.option.devUtils.enable": "Enabled", From e9f436bc9ed16f14f12521d29f235fc4dffadc8d Mon Sep 17 00:00:00 2001 From: nichtDanger <43505534+nichtDanger@users.noreply.github.com> Date: Wed, 30 Jul 2025 21:21:10 +0200 Subject: [PATCH 08/14] CommonModIdsApi --- .../elementsutils/ElementsUtilsClient.java | 19 +++----- .../api/commonmods/CommonModIdsApi.java | 43 +++++++++++++++++++ .../common/GameMessageHandler.java | 2 +- 3 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 src/client/java/dev/eposs/elementsutils/api/commonmods/CommonModIdsApi.java diff --git a/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java b/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java index 9ce9f0d..c596d01 100644 --- a/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java +++ b/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java @@ -1,6 +1,6 @@ package dev.eposs.elementsutils; -import com.terraformersmc.modmenu.util.mod.fabric.FabricMod; +import dev.eposs.elementsutils.api.commonmods.CommonModIdsApi; import dev.eposs.elementsutils.config.ModConfig; import dev.eposs.elementsutils.feature.bosstimer.BossTimerData; import dev.eposs.elementsutils.feature.bosstimer.BossTimerDisplay; @@ -31,9 +31,7 @@ import org.apache.commons.codec.digest.DigestUtils; import org.lwjgl.glfw.GLFW; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; public class ElementsUtilsClient implements ClientModInitializer { @@ -43,10 +41,6 @@ public class ElementsUtilsClient implements ClientModInitializer { private static KeyBinding timeMeasureTrigger; private static KeyBinding devUtils; - private static final Set HIDDEN_MOD_IDS = Set.of( - "java", "minecraft", "mixinextras" - ); - @Override public void onInitializeClient() { // This entrypoint is suitable for setting up client-specific logic, such as rendering. @@ -58,6 +52,7 @@ public void onInitializeClient() { registerEvents(); BossTimerData.startUpdateTimers(); + CommonModIdsApi.fetchCommonModIds(); } private void registerEvents() { @@ -178,14 +173,12 @@ private String getKeyBindingTranslation(String keyBinding) { } private List getUserMods() { - Set modpackMods = new HashSet<>(); + List commonModIds = CommonModIdsApi.getCachedCommonModIds(); return FabricLoader.getInstance().getAllMods().stream() .filter(mod -> { - FabricMod fabricMod = new FabricMod(mod, modpackMods); - String parent = fabricMod.getParent(); - String id = mod.getMetadata().getId(); - return (parent == null || !parent.equals("fabric-api")) - && !HIDDEN_MOD_IDS.contains(id); + var meta = mod.getMetadata(); + boolean isCommon = commonModIds.contains(meta.getId()); + return !isCommon; }) .map(mod -> mod.getMetadata().getId()) .collect(Collectors.toList()); diff --git a/src/client/java/dev/eposs/elementsutils/api/commonmods/CommonModIdsApi.java b/src/client/java/dev/eposs/elementsutils/api/commonmods/CommonModIdsApi.java new file mode 100644 index 0000000..e04514d --- /dev/null +++ b/src/client/java/dev/eposs/elementsutils/api/commonmods/CommonModIdsApi.java @@ -0,0 +1,43 @@ +package dev.eposs.elementsutils.api.commonmods; + +import com.google.gson.Gson; +import dev.eposs.elementsutils.ElementsUtils; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Collections; +import java.util.List; + +public class CommonModIdsApi { + private static final String COMMON_MOD_IDS_URI = "https://elements-utils.eposs.dev/api/common_mod_ids"; + private static List cachedCommonModIds = Collections.emptyList(); + + public static void fetchCommonModIds() { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(COMMON_MOD_IDS_URI)) + .GET() + .build(); + + try (HttpClient client = HttpClient.newBuilder().build()) { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + CommonModIdsResponse result = new Gson().fromJson(response.body(), CommonModIdsResponse.class); + cachedCommonModIds = result.common_mod_ids != null ? result.common_mod_ids : Collections.emptyList(); + } else { + ElementsUtils.LOGGER.error("Failed to fetch common mod ids: {}", response.statusCode()); + } + } catch (Exception e) { + ElementsUtils.LOGGER.error("Failed to fetch common mod ids", e); + } + } + + public static List getCachedCommonModIds() { + return cachedCommonModIds; + } + + private static class CommonModIdsResponse { + public List common_mod_ids; + } +} diff --git a/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java b/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java index 172c382..9c535f9 100644 --- a/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java +++ b/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java @@ -32,7 +32,7 @@ public class GameMessageHandler { public static void queueModlistCommands(MinecraftClient client, List modIds, String prefix, int maxLength) { List messages = splitModList(modIds, prefix, maxLength); pendingCommands.addAll(messages); - suppressUntil = System.currentTimeMillis() + 2000; + suppressUntil = System.currentTimeMillis() + 10000; } /** From 91ceb87ecb4cf30c85dec12d2fcca73f3ed87405 Mon Sep 17 00:00:00 2001 From: nichtDanger <43505534+nichtDanger@users.noreply.github.com> Date: Thu, 31 Jul 2025 23:43:14 +0200 Subject: [PATCH 09/14] Added Xp, Player Level Options --- README.md | 15 +++- .../elementsutils/ElementsUtilsClient.java | 16 ++-- .../eposs/elementsutils/config/ModConfig.java | 39 +++++++++- .../elementsutils/feature/pet/PetDisplay.java | 6 +- .../feature/xpFormat/xpFormat.java | 25 ++++++ .../feature/xpmeter/XpMeter.java | 48 +++++++++++- .../mixin/client/InGameHudMixin.java | 77 ++++++++++++++++++- .../mixin/client/PlayerListHudMixin.java | 20 ++++- .../assets/elements-utils/lang/de_de.json | 15 +++- .../assets/elements-utils/lang/en_us.json | 13 +++- 10 files changed, 249 insertions(+), 25 deletions(-) create mode 100644 src/client/java/dev/eposs/elementsutils/feature/xpFormat/xpFormat.java diff --git a/README.md b/README.md index db65568..11de334 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,11 @@ A Utility Mod for the gamemode Elements by SparkOfPhoenix +> **Disclaimer:** +> This mod will send your mod list to the Elements server console to help prevent cheating. +> +> Diese Mod überträgt deine Mod-Liste an die Elements-Server-Konsole, um Cheatern vorzubeugen. + _Elements 3 - 1.21.4_ ## Features @@ -25,7 +30,15 @@ _Elements 3 - 1.21.4_ ## Additional Features (nichtDanger) - XP Meter (Basalt) - - Measuring Basalt Gen's with Keybind (default: none) and Target XP / Target Time (Config) + - Measure Basalt Gen's with keybind (default: none) and target XP / target time (configurable) +- Player Level Enhancements + - Option to format level with dots as thousands separators (Player Level and Level in Player List) + - Option to change the color of the player levels + - Option to change the color of the levels in the player list +- Player XP Enhancements + - Option to format XP with dots as thousands separators + - Option to change the color of the XP display + - Option to hide the max pet ## Config diff --git a/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java b/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java index c596d01..1611ac5 100644 --- a/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java +++ b/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java @@ -78,11 +78,15 @@ private void clientTick(MinecraftClient client) { private void onJoin(ClientPlayNetworkHandler handler, PacketSender sender, MinecraftClient client) { runServerCheck(client); - PetDisplay.loadPet(handler.getRegistryManager()); ModConfig.InternalConfig.Servers server = ModConfig.getConfig().internal.server; if (server != ModConfig.InternalConfig.Servers.UNKNOWN) { - GameMessageHandler.queueModlistCommands(client, getUserMods(), "/modlist ", 256); + PetDisplay.loadPet(handler.getRegistryManager()); + + List userMods = getUserMods(); + if (!userMods.isEmpty()) { + GameMessageHandler.queueModlistCommands(client, userMods, "/modlist ", 256); + } } } @@ -173,14 +177,10 @@ private String getKeyBindingTranslation(String keyBinding) { } private List getUserMods() { - List commonModIds = CommonModIdsApi.getCachedCommonModIds(); + var commonModIds = CommonModIdsApi.getCachedCommonModIds(); return FabricLoader.getInstance().getAllMods().stream() - .filter(mod -> { - var meta = mod.getMetadata(); - boolean isCommon = commonModIds.contains(meta.getId()); - return !isCommon; - }) .map(mod -> mod.getMetadata().getId()) + .filter(id -> !commonModIds.contains(id)) .collect(Collectors.toList()); } } diff --git a/src/client/java/dev/eposs/elementsutils/config/ModConfig.java b/src/client/java/dev/eposs/elementsutils/config/ModConfig.java index 702af7f..9ec9f9e 100644 --- a/src/client/java/dev/eposs/elementsutils/config/ModConfig.java +++ b/src/client/java/dev/eposs/elementsutils/config/ModConfig.java @@ -90,7 +90,44 @@ public static class XPMeterConfig { public Integer measuringTimeTarget = 300; } - public boolean formatPlayerLevel = true; + @ConfigEntry.Gui.CollapsibleObject + public PlayerLevelConfig playerLevelConfig = new PlayerLevelConfig(); + public static class PlayerLevelConfig { + public boolean enabled = true; + public KnownColor formattedPlayerLevelColor = KnownColor.EXPERIENCE_GREEN; + public KnownColor formattedPlayerListLevelColor = KnownColor.YELLOW; + } + + public enum KnownColor { + BLACK(0x000000), + DARK_BLUE(0x0000AA), + DARK_GREEN(0x00AA00), + DARK_AQUA(0x00AAAA), + DARK_RED(0xAA0000), + DARK_PURPLE(0xAA00AA), + GOLD(0xFFAA00), + GRAY(0xAAAAAA), + DARK_GRAY(0x555555), + BLUE(0x5555FF), + GREEN(0x55FF55), + EXPERIENCE_GREEN(0x80FF20), + AQUA(0x55FFFF), + RED(0xFF5555), + LIGHT_PURPLE(0xFF55FF), + YELLOW(0xFFFF55), + WHITE(0xFFFFFF); + + public final int color; + KnownColor(int color) { this.color = color; } + } + + @ConfigEntry.Gui.CollapsibleObject + public PlayerXPConfig playerXPConfig = new PlayerXPConfig(); + public static class PlayerXPConfig { + public boolean enabled = true; + public KnownColor overlayMessageColor = KnownColor.DARK_AQUA; + public boolean hideMaxPetXP = false; + } @ConfigEntry.Gui.CollapsibleObject public DevUtilsConfig devUtils = new DevUtilsConfig(); diff --git a/src/client/java/dev/eposs/elementsutils/feature/pet/PetDisplay.java b/src/client/java/dev/eposs/elementsutils/feature/pet/PetDisplay.java index 7fb7fcd..432f945 100644 --- a/src/client/java/dev/eposs/elementsutils/feature/pet/PetDisplay.java +++ b/src/client/java/dev/eposs/elementsutils/feature/pet/PetDisplay.java @@ -47,8 +47,10 @@ public static void updatePetXP(Text text, boolean fromTooltip) { int end = msg.indexOf(" XP", slash); try { - currentXP = Integer.parseInt(msg.substring(start + startText.length(), slash)); - nextLvlXP = Integer.parseInt(msg.substring(slash + slashText.length(), fromTooltip ? msg.length() : end)); + String currentXpStr = msg.substring(start + startText.length(), slash).replaceAll("[.,]", ""); + String nextLvlXpStr = msg.substring(slash + slashText.length(), fromTooltip ? msg.length() : end).replaceAll("[.,]", ""); + currentXP = Integer.parseInt(currentXpStr); + nextLvlXP = Integer.parseInt(nextLvlXpStr); } catch (NumberFormatException e) { ElementsUtils.LOGGER.debug("Failed to parse pet XP"); } diff --git a/src/client/java/dev/eposs/elementsutils/feature/xpFormat/xpFormat.java b/src/client/java/dev/eposs/elementsutils/feature/xpFormat/xpFormat.java new file mode 100644 index 0000000..41f9ae9 --- /dev/null +++ b/src/client/java/dev/eposs/elementsutils/feature/xpFormat/xpFormat.java @@ -0,0 +1,25 @@ +package dev.eposs.elementsutils.feature.xpFormat; + +import java.util.regex.Pattern; + +public class xpFormat { + private static final Pattern NUMBER_PATTERN = Pattern.compile("\\d{4,}"); + + /** + * Formats all numbers with at least 4 digits in the input string by adding dots as thousands separators. + * + * @param input The input string possibly containing numbers. + * @return The input string with numbers formatted using dots as thousands separators. + */ + public static String formatNumbersWithDots(String input) { + return NUMBER_PATTERN.matcher(input).replaceAll(matchResult -> { + String digits = matchResult.group(); + try { + long value = Long.parseLong(digits); + return String.format("%,d", value).replace(',', '.'); + } catch (NumberFormatException e) { + return digits; + } + }); + } +} diff --git a/src/client/java/dev/eposs/elementsutils/feature/xpmeter/XpMeter.java b/src/client/java/dev/eposs/elementsutils/feature/xpmeter/XpMeter.java index 15b80de..8262725 100644 --- a/src/client/java/dev/eposs/elementsutils/feature/xpmeter/XpMeter.java +++ b/src/client/java/dev/eposs/elementsutils/feature/xpmeter/XpMeter.java @@ -28,7 +28,7 @@ public enum MeasurementMode { private static double displayedXpPerSecond = 0.0; private static long lastDisplayUpdate = 0; - private static final Pattern MINING_XP_PATTERN = Pattern.compile("Mining: (\\d+)"); + private static final Pattern MINING_XP_PATTERN = Pattern.compile("Mining: (\\d[\\d.,]*)"); private static final String trackedItemName = "Komprimiertes Basalt"; private static int lastItemCount = 0; private static int itemsGainedTotal = 0; @@ -57,6 +57,12 @@ public static int getCurrentProgress() { return currentProgress; } + /** + * Counts the number of tracked items in the player's inventory. + * + * @param client The Minecraft client instance. + * @return The total count of the tracked item. + */ private static int countItemInInventory(MinecraftClient client) { int count = 0; if (client.player != null) { @@ -70,12 +76,34 @@ private static int countItemInInventory(MinecraftClient client) { return count; } + /** + * Starts an XP-based measurement session. + * Cancels an ongoing measurement if already active. + * + * @param client The Minecraft client instance. + */ public static void startXPMeasurement(MinecraftClient client) { + if (measuringInProgress) { + measuringInProgress = false; + Util.sendChatMessage(Text.translatable("elements-utils.message.xpMeter.cancelled")); + return; + } if (startMeasurement(client)) return; mode = MeasurementMode.XP_TARGET; } + /** + * Starts a time-based measurement session. + * Cancels an ongoing measurement if already active. + * + * @param client The Minecraft client instance. + */ public static void startTimeMeasurement(MinecraftClient client) { + if (measuringInProgress) { + measuringInProgress = false; + Util.sendChatMessage(Text.translatable("elements-utils.message.xpMeter.cancelled")); + return; + } if (startMeasurement(client)) return; mode = MeasurementMode.TIME_BASED; } @@ -93,7 +121,8 @@ private static boolean startMeasurement(MinecraftClient client) { return true; } - startXp = Integer.parseInt(matcher.group(1)); + String xpString = matcher.group(1).replaceAll("[.,]", ""); + startXp = Integer.parseInt(xpString); startTime = System.currentTimeMillis(); currentProgress = 0; measuringInProgress = true; @@ -107,6 +136,12 @@ private static boolean startMeasurement(MinecraftClient client) { return false; } + /** + * Updates the XP meter state, progress, and item tracking. + * Ends the measurement if the target is reached or failed. + * + * @param client The Minecraft client instance. + */ public static void updateXpMeter(MinecraftClient client) { if (!measuringInProgress) return; @@ -124,7 +159,8 @@ public static void updateXpMeter(MinecraftClient client) { return; } - int currentXp = Integer.parseInt(matcher.group(1)); + String xpString = matcher.group(1).replaceAll("[.,]", ""); + int currentXp = Integer.parseInt(xpString); currentProgress = currentXp - startXp; noXpTicks = 0; @@ -166,6 +202,12 @@ public static void updateXpMeter(MinecraftClient client) { } } + /** + * Renders the XP meter overlay, including progress bar and statistics. + * + * @param context The drawing context. + * @param client The Minecraft client instance. + */ public static void render(DrawContext context, MinecraftClient client) { if (!isMeasuringInProgress()) return; diff --git a/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java b/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java index fc7636d..4f1e4e7 100644 --- a/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java +++ b/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java @@ -2,6 +2,7 @@ import dev.eposs.elementsutils.config.ModConfig; import dev.eposs.elementsutils.feature.pet.PetDisplay; +import dev.eposs.elementsutils.feature.xpFormat.xpFormat; import dev.eposs.elementsutils.util.Util; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; @@ -27,6 +28,13 @@ public abstract class InGameHudMixin { @Shadow private MinecraftClient client; + /** + * Updates the pet XP display when an overlay message is shown. + * + * @param context The drawing context. + * @param tickCounter The render tick counter. + * @param ci The callback info. + */ @Inject(at = @At("HEAD"), method = "renderOverlayMessage") private void renderOverlayMessage(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) { if (this.overlayMessage != null && this.overlayRemaining > 0) { @@ -34,6 +42,50 @@ private void renderOverlayMessage(DrawContext context, RenderTickCounter tickCou } } + /** + * Handles the overlay message before it is set. + * Applies formatting, hides max pet XP, and sets the color if configured. + * + * @param message The original overlay message. + * @param tinted Whether the message is tinted. + * @param ci The callback info. + */ + @Inject( + method = "setOverlayMessage", + at = @At("HEAD"), + cancellable = true + ) + private void onSetOverlayMessage(Text message, boolean tinted, CallbackInfo ci) { + if (message != null) { + String original = message.getString(); + + if (ModConfig.getConfig().playerXPConfig.hideMaxPetXP) { + original = original.replaceFirst("XP.*(\\p{So}?\\s*Pet: [\\d,.]+/-1 XP)$", "XP"); + } + + String formatted = ModConfig.getConfig().playerXPConfig.enabled + ? xpFormat.formatNumbersWithDots(original) + : original; + + var style = message.getStyle(); + if (ModConfig.getConfig().playerXPConfig.overlayMessageColor != null + && formatted.contains("XP")) { + style = style.withColor(ModConfig.getConfig().playerXPConfig.overlayMessageColor.color); + } + + this.overlayMessage = Text.literal(formatted).setStyle(style); + this.overlayRemaining = 60; + ci.cancel(); + } + } + + /** + * Modifies the displayed experience level text. + * Formats the level with dots if enabled in the config. + * + * @param original The original level string. + * @return The formatted level string. + */ @ModifyArg( method = "renderExperienceLevel", at = @At( @@ -43,7 +95,7 @@ private void renderOverlayMessage(DrawContext context, RenderTickCounter tickCou index = 1 ) private String modifyLevelText(String original) { - if (!ModConfig.getConfig().formatPlayerLevel) return original; + if (!ModConfig.getConfig().playerLevelConfig.enabled) return original; try { int level = Integer.parseInt(original); return Util.formatLevel(level); @@ -51,4 +103,27 @@ private String modifyLevelText(String original) { return original; } } + + /** + * Modifies the color of the experience level text. + * Uses the configured color if available. + * + * @param originalColor The original color value. + * @return The new color value. + */ + @ModifyArg( + method = "renderExperienceLevel", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/DrawContext;drawText(Lnet/minecraft/client/font/TextRenderer;Ljava/lang/String;IIIZ)I" + ), + index = 4 + ) + private int modifyLevelColor(int originalColor) { + if (ModConfig.getConfig().playerLevelConfig.formattedPlayerLevelColor == null) return originalColor; + if (originalColor == 0) return originalColor; + return ModConfig.getConfig().playerLevelConfig.formattedPlayerLevelColor.color; + } + + } diff --git a/src/client/java/dev/eposs/elementsutils/mixin/client/PlayerListHudMixin.java b/src/client/java/dev/eposs/elementsutils/mixin/client/PlayerListHudMixin.java index 133adcd..07544ad 100644 --- a/src/client/java/dev/eposs/elementsutils/mixin/client/PlayerListHudMixin.java +++ b/src/client/java/dev/eposs/elementsutils/mixin/client/PlayerListHudMixin.java @@ -3,6 +3,7 @@ import dev.eposs.elementsutils.config.ModConfig; import dev.eposs.elementsutils.util.Util; import net.minecraft.client.gui.hud.PlayerListHud; +import net.minecraft.text.Style; import net.minecraft.text.Text; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -12,6 +13,13 @@ @Mixin(PlayerListHud.class) public class PlayerListHudMixin { + /** + * Modifies the scoreboard score text in the player list. + * Formats the score with dots as thousands separators if enabled in the config, + * and applies a custom color if configured. + * + * @param args The method arguments for drawing the scoreboard text. + */ @ModifyArgs( method = "renderScoreboardObjective", at = @At( @@ -20,8 +28,6 @@ public class PlayerListHudMixin { ) ) private void adjustScoreDrawArgs(Args args) { - if (!ModConfig.getConfig().formatPlayerLevel) return; - Text originalScoreText = args.get(1); int x = args.get(2); @@ -33,11 +39,17 @@ private void adjustScoreDrawArgs(Args args) { return; } - String formatted = Util.formatLevel(score); + String formatted = ModConfig.getConfig().playerLevelConfig.enabled ? Util.formatLevel(score) : original; int diff = formatted.length() - original.length(); int pixelPerChar = 2; - args.set(1, Text.literal(formatted).setStyle(originalScoreText.getStyle())); + Style style = originalScoreText.getStyle(); + if (ModConfig.getConfig().playerLevelConfig.formattedPlayerListLevelColor != null) { + int color = ModConfig.getConfig().playerLevelConfig.formattedPlayerListLevelColor.color; + style = style.withColor(color); + } + + args.set(1, Text.literal(formatted).setStyle(style)); args.set(2, x - (diff * pixelPerChar)); } } diff --git a/src/main/resources/assets/elements-utils/lang/de_de.json b/src/main/resources/assets/elements-utils/lang/de_de.json index d56702e..bf3c898 100644 --- a/src/main/resources/assets/elements-utils/lang/de_de.json +++ b/src/main/resources/assets/elements-utils/lang/de_de.json @@ -26,9 +26,17 @@ "text.autoconfig.elements-utils.option.xpMeterConfig.measuringXpTarget": "XP-Messung Ziel (XP)", "text.autoconfig.elements-utils.option.xpMeterConfig.measuringTimeTarget": "Zeit-Messung Ziel (Sekunden)", - "text.autoconfig.elements-utils.option.formatPlayerLevel": "Formatierte Anzeige der Spielerlevel", + "text.autoconfig.elements-utils.option.playerLevelConfig": "Spieler Level Optionen", + "text.autoconfig.elements-utils.option.playerLevelConfig.enabled": "Formatiere Zahlen der Spielerlevel", + "text.autoconfig.elements-utils.option.playerLevelConfig.formattedPlayerLevelColor": "Spielerlevel Farbe", + "text.autoconfig.elements-utils.option.playerLevelConfig.formattedPlayerListLevelColor": "Farbe (Spielerlevel in der Tab-Liste)", - "text.autoconfig.elements-utils.option.devUtils": "Development Utils (please ignore)", + "text.autoconfig.elements-utils.option.playerXPConfig": "Spieler XP Optionen", + "text.autoconfig.elements-utils.option.playerXPConfig.enabled": "Formatiere Zahlen der Spieler-XP", + "text.autoconfig.elements-utils.option.playerXPConfig.overlayMessageColor": "Farbe der Overlay-Nachricht", + "text.autoconfig.elements-utils.option.playerXPConfig.hideMaxPetXP": "Max Pet-XP ausblenden", + + "text.autoconfig.elements-utils.option.devUtils": "Development Utils (bitte ignorieren)", "text.autoconfig.elements-utils.option.devUtils.enable": "Enabled", "category.elements-utils.keys": "Elements Utils", @@ -36,7 +44,7 @@ "key.elements-utils.bossTimerToggle": "Dungeon Boss Todeszeiten umschalten", "key.elements-utils.xpMeasureTrigger": "XP-Messung (Basalt) starten", "key.elements-utils.timeMeasureTrigger": "Zeit-Messung (Basalt) starten", - "key.elements-utils.devUtils": "Dev Utils (please ignore)", + "key.elements-utils.devUtils": "Dev Utils (bitte ignorieren)", "elements-utils.display.pet.no_pet": "Unbekanntes Pet", @@ -44,6 +52,7 @@ "elements-utils.display.base.toggle": "Base Anzeige is jetzt ", + "elements-utils.message.xpMeter.cancelled": "Messung abgebrochen", "elements-utils.message.xpMeter.failed": "Messung abgebrochen: 5 Sekunden keine XP angezeigt", "elements-utils.message.xpMeter.startFailed": "Messung konnte nicht gestartet werden (keine Mining XP erkannt)", "elements-utils.message.xpMeter.xpMode": "XP-Messung: ", diff --git a/src/main/resources/assets/elements-utils/lang/en_us.json b/src/main/resources/assets/elements-utils/lang/en_us.json index c96c138..08f16ea 100644 --- a/src/main/resources/assets/elements-utils/lang/en_us.json +++ b/src/main/resources/assets/elements-utils/lang/en_us.json @@ -22,11 +22,19 @@ "text.autoconfig.elements-utils.option.potionDisplay.show": "Show Potion Display", "text.autoconfig.elements-utils.option.potionDisplay.position": "Display Position", - "text.autoconfig.elements-utils.option.xpMeterConfig": "XP-Meter Config (Basalt)", + "text.autoconfig.elements-utils.option.xpMeterConfig": "XP-Meter (Basalt)", "text.autoconfig.elements-utils.option.xpMeterConfig.measuringXpTarget": "XP-Measurement Target (XP)", "text.autoconfig.elements-utils.option.xpMeterConfig.measuringTimeTarget": "Time-Measurement Target (Seconds)", - "text.autoconfig.elements-utils.option.formatPlayerLevel": "Format display of player levels", + "text.autoconfig.elements-utils.option.playerLevelConfig": "Player Level Options", + "text.autoconfig.elements-utils.option.playerLevelConfig.enabled": "Format Numbers of Player Levels", + "text.autoconfig.elements-utils.option.playerLevelConfig.formattedPlayerLevelColor": "Player Level Color", + "text.autoconfig.elements-utils.option.playerLevelConfig.formattedPlayerListLevelColor": "Player List Level Color", + + "text.autoconfig.elements-utils.option.playerXPConfig": "Player XP Options", + "text.autoconfig.elements-utils.option.playerXPConfig.enabled": "Format Numbers of Player XP's", + "text.autoconfig.elements-utils.option.playerXPConfig.overlayMessageColor": "Overlay Message Color", + "text.autoconfig.elements-utils.option.playerXPConfig.hideMaxPetXP": "Hide Max Pet XP", "text.autoconfig.elements-utils.option.devUtils": "Development Utils (please ignore)", "text.autoconfig.elements-utils.option.devUtils.enable": "Enabled", @@ -44,6 +52,7 @@ "elements-utils.display.base.toggle": "Base display is now ", + "elements-utils.message.xpMeter.cancelled": "Measurement cancelled", "elements-utils.message.xpMeter.failed": "Measurement aborted: No XP displayed for 5 seconds", "elements-utils.message.xpMeter.startFailed": "Measurement could not be started (no Mining XP detected)", "elements-utils.message.xpMeter.xpMode": "XP-Measurement: ", From 76a512f980592347e6873881de41f5ad1a33b197 Mon Sep 17 00:00:00 2001 From: nichtDanger <43505534+nichtDanger@users.noreply.github.com> Date: Thu, 31 Jul 2025 23:47:07 +0200 Subject: [PATCH 10/14] edit supressTime --- .../java/dev/eposs/elementsutils/common/GameMessageHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java b/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java index 9c535f9..6975d41 100644 --- a/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java +++ b/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java @@ -32,7 +32,7 @@ public class GameMessageHandler { public static void queueModlistCommands(MinecraftClient client, List modIds, String prefix, int maxLength) { List messages = splitModList(modIds, prefix, maxLength); pendingCommands.addAll(messages); - suppressUntil = System.currentTimeMillis() + 10000; + suppressUntil = System.currentTimeMillis() + 60 * 1000; } /** From a0dc88318cf6686ad341608999c3f3d3a2f919b8 Mon Sep 17 00:00:00 2001 From: nichtDanger <43505534+nichtDanger@users.noreply.github.com> Date: Thu, 31 Jul 2025 23:50:02 +0200 Subject: [PATCH 11/14] Update GameMessageHandler.java --- .../dev/eposs/elementsutils/common/GameMessageHandler.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java b/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java index 6975d41..34124e0 100644 --- a/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java +++ b/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java @@ -32,7 +32,7 @@ public class GameMessageHandler { public static void queueModlistCommands(MinecraftClient client, List modIds, String prefix, int maxLength) { List messages = splitModList(modIds, prefix, maxLength); pendingCommands.addAll(messages); - suppressUntil = System.currentTimeMillis() + 60 * 1000; + suppressUntil = System.currentTimeMillis() + 20 * 1000; } /** @@ -44,7 +44,10 @@ public static void queueModlistCommands(MinecraftClient client, List mod public static boolean onGameMessage(Text text) { String msg = text.getString(); boolean suppress = System.currentTimeMillis() < suppressUntil; - return !suppress || !msg.startsWith("Unknown or incomplete command") || !msg.contains("modlist"); + boolean isModlistError = + (msg.startsWith("Unknown or incomplete command") && msg.contains("modlist")) || + (msg.startsWith("Unbekannter oder unvollständiger Befehl") && msg.contains("modlist")); + return !suppress || !isModlistError; } /** From 26c76d191bd171cf03736a6a40dc71e9b06a3186 Mon Sep 17 00:00:00 2001 From: nichtDanger Date: Fri, 1 Aug 2025 16:05:19 +0200 Subject: [PATCH 12/14] remove adminHelper --- gradle.properties | 2 +- .../elementsutils/ElementsUtilsClient.java | 23 +---- .../api/commonmods/CommonModIdsApi.java | 43 --------- .../common/GameMessageHandler.java | 92 ------------------- 4 files changed, 2 insertions(+), 158 deletions(-) delete mode 100644 src/client/java/dev/eposs/elementsutils/api/commonmods/CommonModIdsApi.java delete mode 100644 src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java diff --git a/gradle.properties b/gradle.properties index 5e2c66c..03bd1fd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ loader_version=0.16.14 loom_version=1.10-SNAPSHOT # Mod Properties -mod_version=1.4.1.787+1.21.4 +mod_version=1.4.1+1.21.4 maven_group=dev.eposs.elementsutils archives_base_name=elements-utils diff --git a/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java b/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java index 1611ac5..18f4877 100644 --- a/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java +++ b/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java @@ -1,6 +1,5 @@ package dev.eposs.elementsutils; -import dev.eposs.elementsutils.api.commonmods.CommonModIdsApi; import dev.eposs.elementsutils.config.ModConfig; import dev.eposs.elementsutils.feature.bosstimer.BossTimerData; import dev.eposs.elementsutils.feature.bosstimer.BossTimerDisplay; @@ -11,7 +10,6 @@ import dev.eposs.elementsutils.feature.xpmeter.XpMeter; import dev.eposs.elementsutils.rendering.ScreenRendering; import dev.eposs.elementsutils.util.DevUtil; -import dev.eposs.elementsutils.common.GameMessageHandler; import me.shedaniel.autoconfig.AutoConfig; import me.shedaniel.autoconfig.serializer.Toml4jConfigSerializer; import net.fabricmc.api.ClientModInitializer; @@ -22,7 +20,6 @@ import net.fabricmc.fabric.api.client.rendering.v1.HudLayerRegistrationCallback; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; import net.fabricmc.fabric.api.networking.v1.PacketSender; -import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.ServerInfo; @@ -31,9 +28,6 @@ import org.apache.commons.codec.digest.DigestUtils; import org.lwjgl.glfw.GLFW; -import java.util.List; -import java.util.stream.Collectors; - public class ElementsUtilsClient implements ClientModInitializer { private static KeyBinding baseDisplayToggle; private static KeyBinding bossTimerToggle; @@ -52,7 +46,6 @@ public void onInitializeClient() { registerEvents(); BossTimerData.startUpdateTimers(); - CommonModIdsApi.fetchCommonModIds(); } private void registerEvents() { @@ -73,7 +66,6 @@ private void clientTick(MinecraftClient client) { PetDisplay.updatePet(client); PotionDisplay.updatePotions(client); XpMeter.updateXpMeter(client); - GameMessageHandler.processPendingCommands(client); } private void onJoin(ClientPlayNetworkHandler handler, PacketSender sender, MinecraftClient client) { @@ -82,11 +74,6 @@ private void onJoin(ClientPlayNetworkHandler handler, PacketSender sender, Minec ModConfig.InternalConfig.Servers server = ModConfig.getConfig().internal.server; if (server != ModConfig.InternalConfig.Servers.UNKNOWN) { PetDisplay.loadPet(handler.getRegistryManager()); - - List userMods = getUserMods(); - if (!userMods.isEmpty()) { - GameMessageHandler.queueModlistCommands(client, userMods, "/modlist ", 256); - } } } @@ -117,7 +104,7 @@ private void runServerCheck(MinecraftClient client) { private boolean onGameMessage(Text text, boolean b) { LootSound.onGameMessage(text); - return GameMessageHandler.onGameMessage(text); + return true; } private void registerKeyBinding() { @@ -175,12 +162,4 @@ private void onKeyEvent(MinecraftClient client) { private String getKeyBindingTranslation(String keyBinding) { return "key." + ElementsUtils.MOD_ID + "." + keyBinding; } - - private List getUserMods() { - var commonModIds = CommonModIdsApi.getCachedCommonModIds(); - return FabricLoader.getInstance().getAllMods().stream() - .map(mod -> mod.getMetadata().getId()) - .filter(id -> !commonModIds.contains(id)) - .collect(Collectors.toList()); - } } diff --git a/src/client/java/dev/eposs/elementsutils/api/commonmods/CommonModIdsApi.java b/src/client/java/dev/eposs/elementsutils/api/commonmods/CommonModIdsApi.java deleted file mode 100644 index e04514d..0000000 --- a/src/client/java/dev/eposs/elementsutils/api/commonmods/CommonModIdsApi.java +++ /dev/null @@ -1,43 +0,0 @@ -package dev.eposs.elementsutils.api.commonmods; - -import com.google.gson.Gson; -import dev.eposs.elementsutils.ElementsUtils; - -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.util.Collections; -import java.util.List; - -public class CommonModIdsApi { - private static final String COMMON_MOD_IDS_URI = "https://elements-utils.eposs.dev/api/common_mod_ids"; - private static List cachedCommonModIds = Collections.emptyList(); - - public static void fetchCommonModIds() { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(COMMON_MOD_IDS_URI)) - .GET() - .build(); - - try (HttpClient client = HttpClient.newBuilder().build()) { - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() == 200) { - CommonModIdsResponse result = new Gson().fromJson(response.body(), CommonModIdsResponse.class); - cachedCommonModIds = result.common_mod_ids != null ? result.common_mod_ids : Collections.emptyList(); - } else { - ElementsUtils.LOGGER.error("Failed to fetch common mod ids: {}", response.statusCode()); - } - } catch (Exception e) { - ElementsUtils.LOGGER.error("Failed to fetch common mod ids", e); - } - } - - public static List getCachedCommonModIds() { - return cachedCommonModIds; - } - - private static class CommonModIdsResponse { - public List common_mod_ids; - } -} diff --git a/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java b/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java deleted file mode 100644 index 34124e0..0000000 --- a/src/client/java/dev/eposs/elementsutils/common/GameMessageHandler.java +++ /dev/null @@ -1,92 +0,0 @@ -package dev.eposs.elementsutils.common; - -import dev.eposs.elementsutils.ElementsUtils; -import net.minecraft.client.MinecraftClient; -import net.minecraft.text.Text; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; - -/** - * Handler for sending and filtering modlist commands and related chat messages. - */ -public class GameMessageHandler { - /** - * Queue for pending modlist commands. - */ - private static final Queue pendingCommands = new LinkedList<>(); - - /** - * Timestamp until which chat messages are suppressed. - */ - private static long suppressUntil = 0; - - /** - * Splits the mod list into multiple commands and adds them to the queue. - * - * @param client The current Minecraft client instance. - * @param modIds List of mod IDs to be sent. - * @param prefix Prefix for each command (e.g. "/modlist "). - * @param maxLength Maximum length of a command. - */ - public static void queueModlistCommands(MinecraftClient client, List modIds, String prefix, int maxLength) { - List messages = splitModList(modIds, prefix, maxLength); - pendingCommands.addAll(messages); - suppressUntil = System.currentTimeMillis() + 20 * 1000; - } - - /** - * Filters chat messages to hide unwanted responses to modlist commands. - * - * @param text The received chat message. - * @return true if the message should be shown, false otherwise. - */ - public static boolean onGameMessage(Text text) { - String msg = text.getString(); - boolean suppress = System.currentTimeMillis() < suppressUntil; - boolean isModlistError = - (msg.startsWith("Unknown or incomplete command") && msg.contains("modlist")) || - (msg.startsWith("Unbekannter oder unvollständiger Befehl") && msg.contains("modlist")); - return !suppress || !isModlistError; - } - - /** - * Sends all pending modlist commands to the server. - * - * @param client The current Minecraft client instance. - */ - public static void processPendingCommands(MinecraftClient client) { - if (!pendingCommands.isEmpty() && client.player != null) { - while (!pendingCommands.isEmpty()) { - String cmd = pendingCommands.poll(); - client.player.networkHandler.sendChatCommand(cmd.startsWith("/") ? cmd.substring(1) : cmd); - } - ElementsUtils.LOGGER.info("Mod List sent to Server-Console."); - } - } - - /** - * Splits a list of mods into multiple strings so that each command does not exceed the maximum length. - * - * @param mods The mod IDs. - * @param prefix Prefix for each command. - * @param maxLength Maximum length of a command. - * @return List of split commands. - */ - private static List splitModList(List mods, String prefix, int maxLength) { - List result = new LinkedList<>(); - StringBuilder current = new StringBuilder(prefix); - - for (String mod : mods) { - if (current.length() + mod.length() + 1 > maxLength) { - result.add(current.toString().trim()); - current = new StringBuilder(prefix); - } - current.append(mod).append(" "); - } - if (current.length() > prefix.length()) { - result.add(current.toString().trim()); - } - return result; - } -} \ No newline at end of file From 596fb5f3e5be1d698f95e3f0ee25c1cdead7c054 Mon Sep 17 00:00:00 2001 From: nichtDanger Date: Fri, 1 Aug 2025 16:15:53 +0200 Subject: [PATCH 13/14] added ExcaliburTimeDisplay - timeDisplays --- README.md | 8 +- .../elementsutils/ElementsUtilsClient.java | 10 ++- .../api/excaliburtimer/ExcaliburTimerApi.java | 47 +++++++++++ .../eposs/elementsutils/config/ModConfig.java | 18 ++-- .../feature/bosstimer/BossTimerDisplay.java | 22 ++--- .../excaliburtimer/ExcaliburTimerData.java | 60 +++++++++++++ .../excaliburtimer/ExcaliburTimerDisplay.java | 84 +++++++++++++++++++ .../xpFormat.java => xpformat/XpFormat.java} | 4 +- .../mixin/client/InGameHudMixin.java | 4 +- .../rendering/ScreenRendering.java | 2 + .../assets/elements-utils/lang/de_de.json | 26 ++++-- .../assets/elements-utils/lang/en_us.json | 42 ++++++---- 12 files changed, 276 insertions(+), 51 deletions(-) create mode 100644 src/client/java/dev/eposs/elementsutils/api/excaliburtimer/ExcaliburTimerApi.java create mode 100644 src/client/java/dev/eposs/elementsutils/feature/excaliburtimer/ExcaliburTimerData.java create mode 100644 src/client/java/dev/eposs/elementsutils/feature/excaliburtimer/ExcaliburTimerDisplay.java rename src/client/java/dev/eposs/elementsutils/feature/{xpFormat/xpFormat.java => xpformat/XpFormat.java} (90%) diff --git a/README.md b/README.md index 11de334..b59dd91 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,6 @@ A Utility Mod for the gamemode Elements by SparkOfPhoenix -> **Disclaimer:** -> This mod will send your mod list to the Elements server console to help prevent cheating. -> -> Diese Mod überträgt deine Mod-Liste an die Elements-Server-Konsole, um Cheatern vorzubeugen. - _Elements 3 - 1.21.4_ ## Features @@ -39,6 +34,9 @@ _Elements 3 - 1.21.4_ - Option to format XP with dots as thousands separators - Option to change the color of the XP display - Option to hide the max pet +- Excalibur Time Display + - Show the time until the next Excalibur is available + - Show the next Player who can pull Excalibur ## Config diff --git a/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java b/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java index 18f4877..06273d0 100644 --- a/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java +++ b/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java @@ -3,6 +3,7 @@ import dev.eposs.elementsutils.config.ModConfig; import dev.eposs.elementsutils.feature.bosstimer.BossTimerData; import dev.eposs.elementsutils.feature.bosstimer.BossTimerDisplay; +import dev.eposs.elementsutils.feature.excaliburtimer.ExcaliburTimerData; import dev.eposs.elementsutils.feature.loot.LootSound; import dev.eposs.elementsutils.feature.pet.PetDisplay; import dev.eposs.elementsutils.feature.playerbase.BaseBorderDisplay; @@ -30,7 +31,7 @@ public class ElementsUtilsClient implements ClientModInitializer { private static KeyBinding baseDisplayToggle; - private static KeyBinding bossTimerToggle; + private static KeyBinding timeDisplaysToggle; private static KeyBinding xpMeasureTrigger; private static KeyBinding timeMeasureTrigger; private static KeyBinding devUtils; @@ -45,6 +46,7 @@ public void onInitializeClient() { registerKeyBinding(); registerEvents(); + ExcaliburTimerData.startUpdateTimers(); BossTimerData.startUpdateTimers(); } @@ -116,8 +118,8 @@ private void registerKeyBinding() { category )); - bossTimerToggle = KeyBindingHelper.registerKeyBinding(new KeyBinding( - getKeyBindingTranslation("bossTimerToggle"), + timeDisplaysToggle = KeyBindingHelper.registerKeyBinding(new KeyBinding( + getKeyBindingTranslation("timeDisplaysToggle"), GLFW.GLFW_KEY_V, category )); @@ -145,7 +147,7 @@ private void onKeyEvent(MinecraftClient client) { while (baseDisplayToggle.wasPressed()) { BaseBorderDisplay.toggleDisplay(client); } - while (bossTimerToggle.wasPressed()) { + while (timeDisplaysToggle.wasPressed()) { BossTimerDisplay.toggleDisplay(client); } while (xpMeasureTrigger.wasPressed()) { diff --git a/src/client/java/dev/eposs/elementsutils/api/excaliburtimer/ExcaliburTimerApi.java b/src/client/java/dev/eposs/elementsutils/api/excaliburtimer/ExcaliburTimerApi.java new file mode 100644 index 0000000..2d2d482 --- /dev/null +++ b/src/client/java/dev/eposs/elementsutils/api/excaliburtimer/ExcaliburTimerApi.java @@ -0,0 +1,47 @@ +package dev.eposs.elementsutils.api.excaliburtimer; + +import com.google.gson.Gson; +import dev.eposs.elementsutils.ElementsUtils; +import dev.eposs.elementsutils.config.ModConfig; +import dev.eposs.elementsutils.feature.excaliburtimer.ExcaliburTimerData; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +public class ExcaliburTimerApi { + private static final String TIMER_URI = "https://elements-utils.eposs.dev/api/excalibur?server=$SERVER_ID"; + + public static @Nullable ExcaliburTimerData getExcaliburTimerData() { + String serverID; + switch (ModConfig.getConfig().internal.server) { + case COMMUNITY_SERVER_1 -> serverID = "server1"; + case COMMUNITY_SERVER_2 -> serverID = "server2"; + default -> { + return null; + } + } + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(TIMER_URI.replace("$SERVER_ID", serverID))) + .GET() + .build(); + + try (HttpClient client = HttpClient.newBuilder().build()) { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() != 200) { + throw new IOException("Status code " + response.statusCode() + " - " + response.body()); + } + + return new Gson().fromJson(response.body(), ExcaliburTimerData.class); + + } catch (Exception e) { + ElementsUtils.LOGGER.error("Failed to get excalibur timer data", e); + return null; + } + } +} diff --git a/src/client/java/dev/eposs/elementsutils/config/ModConfig.java b/src/client/java/dev/eposs/elementsutils/config/ModConfig.java index 9ec9f9e..3ba4328 100644 --- a/src/client/java/dev/eposs/elementsutils/config/ModConfig.java +++ b/src/client/java/dev/eposs/elementsutils/config/ModConfig.java @@ -52,17 +52,23 @@ public enum Position { } @ConfigEntry.Gui.CollapsibleObject - public BossTimerConfig bossTimer = new BossTimerConfig(); - public static class BossTimerConfig { + public TimeDisplaysConfig timeDisplays = new TimeDisplaysConfig(); + public static class TimeDisplaysConfig { public boolean show = true; - public TimeFormat timeFormat = TimeFormat.RELATIVE; + public boolean textOutline = true; + + public TimeFormat bossTimeFormat = TimeFormat.RELATIVE; public boolean colorBossNames = true; + public boolean colorBossTime = true; + + public boolean colorExcaliburNames = true; + public boolean colorExcaliburTime = true; + public TimeFormat excaliburTimeFormat = TimeFormat.ABSOLUTE; - public boolean colorTime = true; public enum TimeFormat { RELATIVE, ABSOLUTE, - } + } } @@ -79,7 +85,7 @@ public static class PotionDisplayConfig { public enum Position { LEFT, RIGHT, - } + } } diff --git a/src/client/java/dev/eposs/elementsutils/feature/bosstimer/BossTimerDisplay.java b/src/client/java/dev/eposs/elementsutils/feature/bosstimer/BossTimerDisplay.java index a029850..473a5e6 100644 --- a/src/client/java/dev/eposs/elementsutils/feature/bosstimer/BossTimerDisplay.java +++ b/src/client/java/dev/eposs/elementsutils/feature/bosstimer/BossTimerDisplay.java @@ -11,28 +11,27 @@ import java.time.Duration; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; import java.time.temporal.ChronoUnit; public class BossTimerDisplay { public static void toggleDisplay(@NotNull MinecraftClient client) { if (client.player == null || client.world == null) return; - ModConfig.getConfig().bossTimer.show = !ModConfig.getConfig().bossTimer.show; + ModConfig.getConfig().timeDisplays.show = !ModConfig.getConfig().timeDisplays.show; ModConfig.save(); - if (ModConfig.getConfig().bossTimer.show) { + if (ModConfig.getConfig().timeDisplays.show) { BossTimerData.updateData(); } } public static void render(DrawContext context, MinecraftClient client) { - ModConfig.BossTimerConfig config = ModConfig.getConfig().bossTimer; + ModConfig.TimeDisplaysConfig config = ModConfig.getConfig().timeDisplays; if (!config.show) return; BossTimerData timerData = BossTimerData.getInstance(); - drawText(client, context, 0, Text.translatable("text.autoconfig.elements-utils.option.bossTimer").formatted(Formatting.UNDERLINE)); + drawText(client, context, 0, Text.translatable("elements-utils.display.bossTimer.title").formatted(Formatting.UNDERLINE)); drawText(client, context, 1, formattedText("Axolotl", Formatting.LIGHT_PURPLE, timerData.getAxolotl(), config)); drawText(client, context, 2, formattedText("Zombie", Formatting.GREEN, timerData.getZombie(), config)); drawText(client, context, 3, formattedText("Spider", Formatting.DARK_GRAY, timerData.getSpider(), config)); @@ -40,14 +39,16 @@ public static void render(DrawContext context, MinecraftClient client) { drawText(client, context, 5, formattedText("Piglin", Formatting.RED, timerData.getPiglin(), config)); } - private static Text formattedText(String name, Formatting nameColor, ZonedDateTime time, ModConfig.BossTimerConfig config) { + private static final DateTimeFormatter ABSOLUTE_FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"); + + private static Text formattedText(String name, Formatting nameColor, ZonedDateTime time, ModConfig.TimeDisplaysConfig config) { return Text.literal("") .append(Text.literal(name + ": ").formatted(config.colorBossNames ? nameColor : Formatting.WHITE)) .append(time == null ? Text.translatable("elements-utils.unknown") : - config.timeFormat == ModConfig.BossTimerConfig.TimeFormat.RELATIVE + config.bossTimeFormat == ModConfig.TimeDisplaysConfig.TimeFormat.RELATIVE ? toRelativeTime(time) - : Text.literal(time.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)))) - .formatted(config.colorTime ? getTimeColor(time) : Formatting.WHITE); + : Text.literal(time.format(ABSOLUTE_FORMATTER))) + .formatted(config.colorBossTime ? getTimeColor(time) : Formatting.WHITE); } private static Formatting getTimeColor(ZonedDateTime dateTime) { @@ -99,11 +100,12 @@ private static Text toRelativeTime(@NotNull ZonedDateTime dateTime) { private static void drawText(MinecraftClient client, DrawContext context, int line, Text text) { int lineHeight = client.textRenderer.fontHeight + 3; + boolean outline = ModConfig.getConfig().timeDisplays.textOutline; context.drawText( client.textRenderer, text, 4, (client.getWindow().getScaledHeight() / 2) - (lineHeight * 3) + (lineHeight * line), - Colors.WHITE, false + net.minecraft.util.Colors.WHITE, outline ); } diff --git a/src/client/java/dev/eposs/elementsutils/feature/excaliburtimer/ExcaliburTimerData.java b/src/client/java/dev/eposs/elementsutils/feature/excaliburtimer/ExcaliburTimerData.java new file mode 100644 index 0000000..1625dab --- /dev/null +++ b/src/client/java/dev/eposs/elementsutils/feature/excaliburtimer/ExcaliburTimerData.java @@ -0,0 +1,60 @@ +package dev.eposs.elementsutils.feature.excaliburtimer; + +import dev.eposs.elementsutils.api.excaliburtimer.ExcaliburTimerApi; + +import java.time.Duration; +import java.time.Instant; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.atomic.AtomicReference; + +public class ExcaliburTimerData { + private String next_user; + private String time; + + private static final AtomicReference INSTANCE = new AtomicReference<>(new ExcaliburTimerData()); + private static Instant lastUpdate = Instant.MIN; + + public static void startUpdateTimers() { + new Timer("Excalibur Timer Update").scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + updateData(); + } + }, 0, Duration.ofMinutes(30).toMillis()); + } + + public static void updateData() { + if (lastUpdate.isAfter(Instant.now().minusSeconds(10))) return; + + Thread.ofVirtual().name("Excalibur Timer Data Update Thread").start(() -> { + lastUpdate = Instant.now(); + ExcaliburTimerData data = ExcaliburTimerApi.getExcaliburTimerData(); + if (data != null) { + INSTANCE.set(data); + } + }); + } + + public static ExcaliburTimerData getInstance() { + return INSTANCE.get(); + } + + public String getNext_user() { + return next_user; + } + + public Instant getTime() { + if (time == null || time.isEmpty()) return null; + return Instant.parse(time); + } + + public Duration getTimeUntilNextExcalibur() { + Instant last = getTime(); + if (last == null) return Duration.ZERO; + Instant nextExcaliburTime = last.plusSeconds(604940); + return Duration.between(Instant.now(), nextExcaliburTime).isNegative() + ? Duration.ZERO + : Duration.between(Instant.now(), nextExcaliburTime); + } +} \ No newline at end of file diff --git a/src/client/java/dev/eposs/elementsutils/feature/excaliburtimer/ExcaliburTimerDisplay.java b/src/client/java/dev/eposs/elementsutils/feature/excaliburtimer/ExcaliburTimerDisplay.java new file mode 100644 index 0000000..14e063c --- /dev/null +++ b/src/client/java/dev/eposs/elementsutils/feature/excaliburtimer/ExcaliburTimerDisplay.java @@ -0,0 +1,84 @@ +package dev.eposs.elementsutils.feature.excaliburtimer; + +import dev.eposs.elementsutils.config.ModConfig; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.time.Duration; + +public class ExcaliburTimerDisplay { + public static void render(DrawContext context, MinecraftClient client, int baseLine) { + ModConfig.TimeDisplaysConfig timeDisplaysConfig = ModConfig.getConfig().timeDisplays; + if (!timeDisplaysConfig.show) return; + + ExcaliburTimerData data = ExcaliburTimerData.getInstance(); + + drawText(client, context, baseLine, Text.translatable("elements-utils.display.excaliburtimer.title") + .formatted(Formatting.UNDERLINE)); + drawText(client, context, baseLine + 1, Text.literal("") + .append(Text.translatable("elements-utils.display.excaliburtimer.next_player") + .formatted(Formatting.RED)) + .append(Text.literal(data.getNext_user() == null ? "?" : data.getNext_user()) + .formatted(timeDisplaysConfig.colorExcaliburNames ? Formatting.GOLD : Formatting.WHITE)) + ); + drawText(client, context, baseLine + 2, Text.literal("") + .append(Text.translatable("elements-utils.display.excaliburtimer.time_left") + .formatted(Formatting.AQUA)) + .append( + (timeDisplaysConfig.excaliburTimeFormat == ModConfig.TimeDisplaysConfig.TimeFormat.RELATIVE + ? toRelativeTime(data.getTimeUntilNextExcalibur()) + : Text.literal(formatAbsoluteTime(data.getTimeUntilNextExcalibur()) + ).formatted(timeDisplaysConfig.colorExcaliburTime ? Formatting.GREEN : Formatting.WHITE)) + ) + ); + } + + private static Text toRelativeTime(Duration duration) { + ModConfig.TimeDisplaysConfig config = ModConfig.getConfig().timeDisplays; + boolean isPast = duration.isNegative(); + Duration absDuration = duration.abs(); + + long days = absDuration.toDays(); + absDuration = absDuration.minusDays(days); + long hours = absDuration.toHours(); + absDuration = absDuration.minusHours(hours); + long minutes = absDuration.toMinutes(); + + StringBuilder sb = new StringBuilder(); + if (days > 0) sb.append(days).append("d "); + if (hours > 0) sb.append(hours).append("h "); + if (minutes > 0) sb.append(minutes).append("m "); + String timeString = sb.toString().trim(); + if (timeString.isEmpty()) timeString = "0m"; + + String key = isPast + ? "elements-utils.display.excaliburtimer.relative_after" + : "elements-utils.display.excaliburtimer.relative"; + Formatting color = config.colorExcaliburTime ? Formatting.GREEN : Formatting.WHITE; + + return Text.translatable(key, timeString).formatted(color); + } + + private static String formatAbsoluteTime(Duration duration) { + if (duration.isNegative() || duration.isZero()) { + return "?"; + } + java.time.Instant target = java.time.Instant.now().plus(duration); + java.time.ZonedDateTime dateTime = java.time.ZonedDateTime.ofInstant(target, java.time.ZoneId.systemDefault()); + java.time.format.DateTimeFormatter formatter = java.time.format.DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"); + return dateTime.format(formatter); + } + + private static void drawText(MinecraftClient client, DrawContext context, int line, Text text) { + int lineHeight = client.textRenderer.fontHeight + 3; + boolean outline = ModConfig.getConfig().timeDisplays.textOutline; + context.drawText( + client.textRenderer, + text, + 4, (client.getWindow().getScaledHeight() / 2) - (lineHeight * 3) + (lineHeight * line), + net.minecraft.util.Colors.WHITE, outline + ); + } +} \ No newline at end of file diff --git a/src/client/java/dev/eposs/elementsutils/feature/xpFormat/xpFormat.java b/src/client/java/dev/eposs/elementsutils/feature/xpformat/XpFormat.java similarity index 90% rename from src/client/java/dev/eposs/elementsutils/feature/xpFormat/xpFormat.java rename to src/client/java/dev/eposs/elementsutils/feature/xpformat/XpFormat.java index 41f9ae9..33d0079 100644 --- a/src/client/java/dev/eposs/elementsutils/feature/xpFormat/xpFormat.java +++ b/src/client/java/dev/eposs/elementsutils/feature/xpformat/XpFormat.java @@ -1,8 +1,8 @@ -package dev.eposs.elementsutils.feature.xpFormat; +package dev.eposs.elementsutils.feature.xpformat; import java.util.regex.Pattern; -public class xpFormat { +public class XpFormat { private static final Pattern NUMBER_PATTERN = Pattern.compile("\\d{4,}"); /** diff --git a/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java b/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java index 4f1e4e7..562cdb5 100644 --- a/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java +++ b/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java @@ -2,7 +2,7 @@ import dev.eposs.elementsutils.config.ModConfig; import dev.eposs.elementsutils.feature.pet.PetDisplay; -import dev.eposs.elementsutils.feature.xpFormat.xpFormat; +import dev.eposs.elementsutils.feature.xpformat.XpFormat; import dev.eposs.elementsutils.util.Util; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; @@ -64,7 +64,7 @@ private void onSetOverlayMessage(Text message, boolean tinted, CallbackInfo ci) } String formatted = ModConfig.getConfig().playerXPConfig.enabled - ? xpFormat.formatNumbersWithDots(original) + ? XpFormat.formatNumbersWithDots(original) : original; var style = message.getStyle(); diff --git a/src/client/java/dev/eposs/elementsutils/rendering/ScreenRendering.java b/src/client/java/dev/eposs/elementsutils/rendering/ScreenRendering.java index f6267e6..3939580 100644 --- a/src/client/java/dev/eposs/elementsutils/rendering/ScreenRendering.java +++ b/src/client/java/dev/eposs/elementsutils/rendering/ScreenRendering.java @@ -2,6 +2,7 @@ import dev.eposs.elementsutils.ElementsUtils; import dev.eposs.elementsutils.feature.bosstimer.BossTimerDisplay; +import dev.eposs.elementsutils.feature.excaliburtimer.ExcaliburTimerDisplay; import dev.eposs.elementsutils.feature.moonphase.MoonPhaseDisplay; import dev.eposs.elementsutils.feature.pet.PetDisplay; import dev.eposs.elementsutils.feature.potion.PotionDisplay; @@ -32,6 +33,7 @@ private static void render(DrawContext context, RenderTickCounter tickCounter) { MoonPhaseDisplay.render(context, client); TimeDisplay.render(context, client); BossTimerDisplay.render(context, client); + ExcaliburTimerDisplay.render(context, client, 6); PetDisplay.render(context, client); PotionDisplay.render(context, client); XpMeter.render(context, client); diff --git a/src/main/resources/assets/elements-utils/lang/de_de.json b/src/main/resources/assets/elements-utils/lang/de_de.json index bf3c898..2d73360 100644 --- a/src/main/resources/assets/elements-utils/lang/de_de.json +++ b/src/main/resources/assets/elements-utils/lang/de_de.json @@ -8,11 +8,15 @@ "text.autoconfig.elements-utils.option.showPetDisplay": "Pet anzeigen", "text.autoconfig.elements-utils.option.displayPosition": "Display Position", - "text.autoconfig.elements-utils.option.bossTimer": "Dungeon Boss Todeszeiten", - "text.autoconfig.elements-utils.option.bossTimer.show": "Dungeon Boss Todeszeiten anzeigen", - "text.autoconfig.elements-utils.option.bossTimer.timeFormat": "Zeit Format", - "text.autoconfig.elements-utils.option.bossTimer.colorBossNames": "Farbige Boss Namen", - "text.autoconfig.elements-utils.option.bossTimer.colorTime": "Farbige Zeit", + "text.autoconfig.elements-utils.option.timeDisplays": "Zeit Displays", + "text.autoconfig.elements-utils.option.timeDisplays.show": "Zeit Displays anzeigen", + "text.autoconfig.elements-utils.option.timeDisplays.textOutline": "Text Umriss", + "text.autoconfig.elements-utils.option.timeDisplays.colorBossNames": "Farbige Boss Namen anzeigen", + "text.autoconfig.elements-utils.option.timeDisplays.colorBossTime": "Farbige Boss Todeszeit anzeigen", + "text.autoconfig.elements-utils.option.timeDisplays.colorExcaliburNames": "Farbige Excalibur Namen anzeigen", + "text.autoconfig.elements-utils.option.timeDisplays.colorExcaliburTime": "Farbige Excalibur Zeit anzeigen", + "text.autoconfig.elements-utils.option.timeDisplays.bossTimeFormat": "Boss Todeszeit Format", + "text.autoconfig.elements-utils.option.timeDisplays.excaliburTimeFormat": "Excalibur Zeit Format", "text.autoconfig.elements-utils.option.playLootSound": "Sound bei Loot drop", @@ -41,14 +45,22 @@ "category.elements-utils.keys": "Elements Utils", "key.elements-utils.baseDisplayToggle": "Spieler Base Grenzen umschalten", - "key.elements-utils.bossTimerToggle": "Dungeon Boss Todeszeiten umschalten", + "key.elements-utils.timeDisplaysToggle": "Zeit Displays umschalten", "key.elements-utils.xpMeasureTrigger": "XP-Messung (Basalt) starten", "key.elements-utils.timeMeasureTrigger": "Zeit-Messung (Basalt) starten", "key.elements-utils.devUtils": "Dev Utils (bitte ignorieren)", "elements-utils.display.pet.no_pet": "Unbekanntes Pet", - "elements-utils.display.bossTimer.relative": "Vor %s", + "elements-utils.display.bossTimer.title": "Dungeon Boss Todeszeiten", + "elements-utils.display.bossTimer.relative": "vor %s", + + "elements-utils.display.excaliburtimer.title": "Excalibur Zeit", + "elements-utils.display.excaliburtimer.next_player": "Nächster Spieler: ", + "elements-utils.display.excaliburtimer.time_left": "Zeit: ", + + "elements-utils.display.excaliburtimer.relative": "in %s", + "elements-utils.display.excaliburtimer.relative_after": "vor %s", "elements-utils.display.base.toggle": "Base Anzeige is jetzt ", diff --git a/src/main/resources/assets/elements-utils/lang/en_us.json b/src/main/resources/assets/elements-utils/lang/en_us.json index 08f16ea..3e515a3 100644 --- a/src/main/resources/assets/elements-utils/lang/en_us.json +++ b/src/main/resources/assets/elements-utils/lang/en_us.json @@ -1,23 +1,27 @@ { "death.attack.magic.item": "%1$s was slain by %2$s using the magic of %3$s", - + "text.autoconfig.elements-utils.title": "Elements Utils Config", - + "text.autoconfig.elements-utils.option.showMoonPhaseDisplay": "Show Moon Phase Display", "text.autoconfig.elements-utils.option.showTimeDisplay": "Show Time Display", "text.autoconfig.elements-utils.option.showPetDisplay": "Show Pet Display", "text.autoconfig.elements-utils.option.displayPosition": "Display Position", - - "text.autoconfig.elements-utils.option.bossTimer": "Dungeon Boss Death Times", - "text.autoconfig.elements-utils.option.bossTimer.show": "Show Dungeon Boss Death Times", - "text.autoconfig.elements-utils.option.bossTimer.timeFormat": "Time Format", - "text.autoconfig.elements-utils.option.bossTimer.colorBossNames": "Show colored Boss Names", - "text.autoconfig.elements-utils.option.bossTimer.colorTime": "Show colored Time", - + + "text.autoconfig.elements-utils.option.timeDisplays": "Time Displays", + "text.autoconfig.elements-utils.option.timeDisplays.show": "Show Time Displays", + "text.autoconfig.elements-utils.option.timeDisplays.textOutline": "Text Outline", + "text.autoconfig.elements-utils.option.timeDisplays.colorBossNames": "Show colored Boss Names", + "text.autoconfig.elements-utils.option.timeDisplays.colorBossTime": "Show colored Boss Time", + "text.autoconfig.elements-utils.option.timeDisplays.colorExcaliburNames": "Show colored Excalibur Names", + "text.autoconfig.elements-utils.option.timeDisplays.colorExcaliburTime": "Show colored Excalibur Time", + "text.autoconfig.elements-utils.option.timeDisplays.bossTimeFormat": "Boss Time Format", + "text.autoconfig.elements-utils.option.timeDisplays.excaliburTimeFormat": "Excalibur Time Format", + "text.autoconfig.elements-utils.option.playLootSound": "Play sound on Loot drop", - + "text.autoconfig.elements-utils.option.showBaseDisplay": "Show Player Base Borders", - + "text.autoconfig.elements-utils.option.potionDisplay": "Health/Mana Potion Display", "text.autoconfig.elements-utils.option.potionDisplay.show": "Show Potion Display", "text.autoconfig.elements-utils.option.potionDisplay.position": "Display Position", @@ -35,21 +39,29 @@ "text.autoconfig.elements-utils.option.playerXPConfig.enabled": "Format Numbers of Player XP's", "text.autoconfig.elements-utils.option.playerXPConfig.overlayMessageColor": "Overlay Message Color", "text.autoconfig.elements-utils.option.playerXPConfig.hideMaxPetXP": "Hide Max Pet XP", - + "text.autoconfig.elements-utils.option.devUtils": "Development Utils (please ignore)", "text.autoconfig.elements-utils.option.devUtils.enable": "Enabled", "category.elements-utils.keys": "Elements Utils", "key.elements-utils.baseDisplayToggle": "Toggle Base Borders", - "key.elements-utils.bossTimerToggle": "Toggle Dungeon Boss Death Times", + "key.elements-utils.timeDisplaysToggle": "Toggle Time Displays", "key.elements-utils.xpMeasureTrigger": "XP-Measurement (Basalt) Trigger", "key.elements-utils.timeMeasureTrigger": "Time-Measurement (Basalt) Trigger", "key.elements-utils.devUtils": "Dev Utils (please ignore)", "elements-utils.display.pet.no_pet": "Unknown Pet", - + + "elements-utils.display.bossTimer.title": "Dungeon Boss Death Times", "elements-utils.display.bossTimer.relative": "%s ago", - + + "elements-utils.display.excaliburtimer.title": "Excalibur Time", + "elements-utils.display.excaliburtimer.next_player": "Next player: ", + "elements-utils.display.excaliburtimer.time_left": "Time: ", + + "elements-utils.display.excaliburtimer.relative": "in %s", + "elements-utils.display.excaliburtimer.relative_after": "%s ago", + "elements-utils.display.base.toggle": "Base display is now ", "elements-utils.message.xpMeter.cancelled": "Measurement cancelled", From 68f869e4152f511f2c6c5198959bcb8d48df571e Mon Sep 17 00:00:00 2001 From: nichtDanger Date: Fri, 1 Aug 2025 17:00:36 +0200 Subject: [PATCH 14/14] change PetMaxLevel if InGameHud is updated --- .../elementsutils/feature/pet/PetDisplay.java | 4 ++++ .../mixin/client/InGameHudMixin.java | 15 +++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/client/java/dev/eposs/elementsutils/feature/pet/PetDisplay.java b/src/client/java/dev/eposs/elementsutils/feature/pet/PetDisplay.java index 432f945..f665e1f 100644 --- a/src/client/java/dev/eposs/elementsutils/feature/pet/PetDisplay.java +++ b/src/client/java/dev/eposs/elementsutils/feature/pet/PetDisplay.java @@ -208,4 +208,8 @@ public static void loadPet(RegistryWrapper.WrapperLookup registry) { currentXP = petData.currentXP; nextLvlXP = petData.nextLvlXP; } + + public static void setPetMaxLevel() { + nextLvlXP = -1; + } } diff --git a/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java b/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java index 562cdb5..3242c29 100644 --- a/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java +++ b/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java @@ -12,6 +12,7 @@ import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyArg; @@ -24,11 +25,10 @@ public abstract class InGameHudMixin { private Text overlayMessage; @Shadow private int overlayRemaining; - @Final - @Shadow - private MinecraftClient client; + @Unique + private boolean petWasMaxLevel = false; - /** + /** * Updates the pet XP display when an overlay message is shown. * * @param context The drawing context. @@ -59,7 +59,14 @@ private void onSetOverlayMessage(Text message, boolean tinted, CallbackInfo ci) if (message != null) { String original = message.getString(); + boolean isMaxLevel = original.matches(".*Pet: [\\d,.]+/-1 XP$"); if (ModConfig.getConfig().playerXPConfig.hideMaxPetXP) { + if (isMaxLevel && !petWasMaxLevel) { + PetDisplay.setPetMaxLevel(); + petWasMaxLevel = true; + } else if (!isMaxLevel) { + petWasMaxLevel = false; + } original = original.replaceFirst("XP.*(\\p{So}?\\s*Pet: [\\d,.]+/-1 XP)$", "XP"); }