diff --git a/README.md b/README.md index b59dd91..b72b4cf 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,11 @@ _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 + - Option to show a XP/s indicator + - Option to make a Y-Axis offset for the XP display - Excalibur Time Display - - Show the time until the next Excalibur is available - - Show the next Player who can pull Excalibur + - 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 06273d0..a812266 100644 --- a/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java +++ b/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java @@ -4,6 +4,7 @@ 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.excaliburtimer.ExcaliburTimerDisplay; import dev.eposs.elementsutils.feature.loot.LootSound; import dev.eposs.elementsutils.feature.pet.PetDisplay; import dev.eposs.elementsutils.feature.playerbase.BaseBorderDisplay; @@ -31,7 +32,8 @@ public class ElementsUtilsClient implements ClientModInitializer { private static KeyBinding baseDisplayToggle; - private static KeyBinding timeDisplaysToggle; + private static KeyBinding bossTimerToggle; + private static KeyBinding excaliburTimeToggle; private static KeyBinding xpMeasureTrigger; private static KeyBinding timeMeasureTrigger; private static KeyBinding devUtils; @@ -76,6 +78,8 @@ 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()); + BossTimerData.updateData(); + ExcaliburTimerData.updateData(); } } @@ -118,12 +122,18 @@ private void registerKeyBinding() { category )); - timeDisplaysToggle = KeyBindingHelper.registerKeyBinding(new KeyBinding( - getKeyBindingTranslation("timeDisplaysToggle"), + bossTimerToggle = KeyBindingHelper.registerKeyBinding(new KeyBinding( + getKeyBindingTranslation("bossTimerToggle"), GLFW.GLFW_KEY_V, category )); + excaliburTimeToggle = KeyBindingHelper.registerKeyBinding(new KeyBinding( + getKeyBindingTranslation("excaliburTimeToggle"), + GLFW.GLFW_KEY_UNKNOWN, + category + )); + xpMeasureTrigger = KeyBindingHelper.registerKeyBinding(new KeyBinding( getKeyBindingTranslation("xpMeasureTrigger"), GLFW.GLFW_KEY_UNKNOWN, @@ -147,9 +157,12 @@ private void onKeyEvent(MinecraftClient client) { while (baseDisplayToggle.wasPressed()) { BaseBorderDisplay.toggleDisplay(client); } - while (timeDisplaysToggle.wasPressed()) { + while (bossTimerToggle.wasPressed()) { BossTimerDisplay.toggleDisplay(client); } + while (excaliburTimeToggle.wasPressed()) { + ExcaliburTimerDisplay.toggleDisplay(client); + } while (xpMeasureTrigger.wasPressed()) { XpMeter.startXPMeasurement(client); } diff --git a/src/client/java/dev/eposs/elementsutils/config/ModConfig.java b/src/client/java/dev/eposs/elementsutils/config/ModConfig.java index 3ba4328..dc17335 100644 --- a/src/client/java/dev/eposs/elementsutils/config/ModConfig.java +++ b/src/client/java/dev/eposs/elementsutils/config/ModConfig.java @@ -51,25 +51,31 @@ public enum Position { BOTTOM_RIGHT } + public enum TimeFormat { + RELATIVE, + ABSOLUTE, + } + @ConfigEntry.Gui.CollapsibleObject - public TimeDisplaysConfig timeDisplays = new TimeDisplaysConfig(); - public static class TimeDisplaysConfig { + public BossTimerConfig bossTimer = new BossTimerConfig(); + public static class BossTimerConfig { public boolean show = true; public boolean textOutline = true; public TimeFormat bossTimeFormat = TimeFormat.RELATIVE; public boolean colorBossNames = true; public boolean colorBossTime = true; + } + + @ConfigEntry.Gui.CollapsibleObject + public ExcaliburTimeConfig excaliburTime = new ExcaliburTimeConfig(); + public static class ExcaliburTimeConfig { + public boolean show = true; + public boolean textOutline = true; public boolean colorExcaliburNames = true; public boolean colorExcaliburTime = true; public TimeFormat excaliburTimeFormat = TimeFormat.ABSOLUTE; - - public enum TimeFormat { - RELATIVE, - ABSOLUTE, - } - } public boolean playLootSound = true; @@ -131,8 +137,11 @@ public enum KnownColor { public PlayerXPConfig playerXPConfig = new PlayerXPConfig(); public static class PlayerXPConfig { public boolean enabled = true; + public boolean showXpPerSecond = false; + public KnownColor xpPerSecondColor = KnownColor.GRAY; public KnownColor overlayMessageColor = KnownColor.DARK_AQUA; public boolean hideMaxPetXP = false; + public int overlayMessageYOffset = 0; } @ConfigEntry.Gui.CollapsibleObject 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 473a5e6..be2adda 100644 --- a/src/client/java/dev/eposs/elementsutils/feature/bosstimer/BossTimerDisplay.java +++ b/src/client/java/dev/eposs/elementsutils/feature/bosstimer/BossTimerDisplay.java @@ -4,7 +4,6 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.text.Text; -import net.minecraft.util.Colors; import net.minecraft.util.Formatting; import org.jetbrains.annotations.NotNull; @@ -17,16 +16,16 @@ public class BossTimerDisplay { public static void toggleDisplay(@NotNull MinecraftClient client) { if (client.player == null || client.world == null) return; - ModConfig.getConfig().timeDisplays.show = !ModConfig.getConfig().timeDisplays.show; + ModConfig.getConfig().bossTimer.show = !ModConfig.getConfig().bossTimer.show; ModConfig.save(); - if (ModConfig.getConfig().timeDisplays.show) { + if (ModConfig.getConfig().bossTimer.show) { BossTimerData.updateData(); } } public static void render(DrawContext context, MinecraftClient client) { - ModConfig.TimeDisplaysConfig config = ModConfig.getConfig().timeDisplays; + ModConfig.BossTimerConfig config = ModConfig.getConfig().bossTimer; if (!config.show) return; BossTimerData timerData = BossTimerData.getInstance(); @@ -41,11 +40,11 @@ public static void render(DrawContext context, MinecraftClient client) { 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) { + private static Text formattedText(String name, Formatting nameColor, ZonedDateTime time, ModConfig.BossTimerConfig config) { return Text.literal("") .append(Text.literal(name + ": ").formatted(config.colorBossNames ? nameColor : Formatting.WHITE)) .append(time == null ? Text.translatable("elements-utils.unknown") : - config.bossTimeFormat == ModConfig.TimeDisplaysConfig.TimeFormat.RELATIVE + config.bossTimeFormat == ModConfig.TimeFormat.RELATIVE ? toRelativeTime(time) : Text.literal(time.format(ABSOLUTE_FORMATTER))) .formatted(config.colorBossTime ? getTimeColor(time) : Formatting.WHITE); @@ -100,7 +99,7 @@ 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; + boolean outline = ModConfig.getConfig().bossTimer.textOutline; context.drawText( client.textRenderer, text, diff --git a/src/client/java/dev/eposs/elementsutils/feature/excaliburtimer/ExcaliburTimerData.java b/src/client/java/dev/eposs/elementsutils/feature/excaliburtimer/ExcaliburTimerData.java index 1625dab..0de78f7 100644 --- a/src/client/java/dev/eposs/elementsutils/feature/excaliburtimer/ExcaliburTimerData.java +++ b/src/client/java/dev/eposs/elementsutils/feature/excaliburtimer/ExcaliburTimerData.java @@ -8,22 +8,37 @@ import java.util.TimerTask; import java.util.concurrent.atomic.AtomicReference; +/** + * Holds and updates the Excalibur timer data, including the next user and the time. + * Provides thread-safe access and periodic updates. + */ public class ExcaliburTimerData { + /** The name of the next user. */ private String next_user; + /** The time as an ISO-8601 string. */ private String time; + /** Singleton instance holder. */ private static final AtomicReference INSTANCE = new AtomicReference<>(new ExcaliburTimerData()); + /** Timestamp of the last update. */ private static Instant lastUpdate = Instant.MIN; + /** + * Starts a timer that periodically updates the Excalibur timer data every hour. + */ public static void startUpdateTimers() { new Timer("Excalibur Timer Update").scheduleAtFixedRate(new TimerTask() { @Override public void run() { updateData(); } - }, 0, Duration.ofMinutes(30).toMillis()); + }, 0, Duration.ofHours(1).toMillis()); } + /** + * Updates the Excalibur timer data from the API if at least 10 seconds have passed since the last update. + * Runs the update in a virtual thread. + */ public static void updateData() { if (lastUpdate.isAfter(Instant.now().minusSeconds(10))) return; @@ -36,25 +51,31 @@ public static void updateData() { }); } + /** + * Returns the current singleton instance of the timer data. + * + * @return The current ExcaliburTimerData instance. + */ public static ExcaliburTimerData getInstance() { return INSTANCE.get(); } + /** + * Returns the name of the next user. + * + * @return The next user as a String. + */ public String getNext_user() { return next_user; } + /** + * Returns the time as an Instant, or null if not set or empty. + * + * @return The time as an Instant, or null. + */ 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 index 14e063c..7e9b796 100644 --- a/src/client/java/dev/eposs/elementsutils/feature/excaliburtimer/ExcaliburTimerDisplay.java +++ b/src/client/java/dev/eposs/elementsutils/feature/excaliburtimer/ExcaliburTimerDisplay.java @@ -3,48 +3,96 @@ import dev.eposs.elementsutils.config.ModConfig; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; +import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import org.jetbrains.annotations.NotNull; -import java.time.Duration; +import java.time.*; +import java.time.format.DateTimeFormatter; +/** + * Handles the display and logic for the Excalibur timer overlay. + */ public class ExcaliburTimerDisplay { + private static final int DAYS = 7; + private static final long EXTRA_SECONDS = DAYS * 20; + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"); + + /** + * Toggles the visibility of the Excalibur timer overlay and updates data if enabled. + * + * @param client The Minecraft client instance. Must not be null. + */ + public static void toggleDisplay(@NotNull MinecraftClient client) { + if (client.player == null || client.world == null) return; + + ModConfig.getConfig().excaliburTime.show = !ModConfig.getConfig().excaliburTime.show; + ModConfig.save(); + + if (ModConfig.getConfig().excaliburTime.show) { + ExcaliburTimerData.updateData(); + } + } + + /** + * Colors the given text if enabled, otherwise returns the text unchanged. + * + * @param text The text to colorize. + * @param enabled Whether coloring is enabled. + * @param color The formatting color to apply. + * @return The (possibly) colored text. + */ + private static MutableText colorize(MutableText text, boolean enabled, Formatting color) { + return enabled ? text.formatted(color) : text; + } + + /** + * Renders the Excalibur timer overlay on the screen. + * + * @param context The draw context. + * @param client The Minecraft client instance. + * @param baseLine The base line for vertical positioning. + */ public static void render(DrawContext context, MinecraftClient client, int baseLine) { - ModConfig.TimeDisplaysConfig timeDisplaysConfig = ModConfig.getConfig().timeDisplays; - if (!timeDisplaysConfig.show) return; + var config = ModConfig.getConfig().excaliburTime; + if (!config.show) return; - ExcaliburTimerData data = ExcaliburTimerData.getInstance(); + var data = ExcaliburTimerData.getInstance(); + Instant targetInstant = calculateTargetInstant(data.getTime()); + Duration timeUntilTarget = targetInstant == null ? Duration.ZERO : Duration.between(Instant.now(), targetInstant); - drawText(client, context, baseLine, Text.translatable("elements-utils.display.excaliburtimer.title") - .formatted(Formatting.UNDERLINE)); + drawText(client, context, baseLine, Text.translatable("elements-utils.display.excaliburTime.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)) + .append(colorize(Text.translatable("elements-utils.display.excaliburTime.next_player"), config.colorExcaliburNames, Formatting.RED)) + .append(colorize(Text.literal(data.getNext_user() == null ? "?" : data.getNext_user()), config.colorExcaliburNames, Formatting.GOLD)) ); drawText(client, context, baseLine + 2, Text.literal("") - .append(Text.translatable("elements-utils.display.excaliburtimer.time_left") - .formatted(Formatting.AQUA)) + .append(colorize(Text.translatable("elements-utils.display.excaliburTime.time_left"), config.colorExcaliburTime, Formatting.AQUA)) .append( - (timeDisplaysConfig.excaliburTimeFormat == ModConfig.TimeDisplaysConfig.TimeFormat.RELATIVE - ? toRelativeTime(data.getTimeUntilNextExcalibur()) - : Text.literal(formatAbsoluteTime(data.getTimeUntilNextExcalibur()) - ).formatted(timeDisplaysConfig.colorExcaliburTime ? Formatting.GREEN : Formatting.WHITE)) - ) + (config.excaliburTimeFormat == ModConfig.TimeFormat.RELATIVE + ? toRelativeTime(timeUntilTarget) + : Text.literal(formatTargetTime(targetInstant) + ).formatted(config.colorExcaliburTime ? Formatting.GREEN : Formatting.WHITE))) ); } + /** + * Converts a duration to a relative time text (e.g. "2d 3h 5m"). + * + * @param duration The duration until or since the target time. + * @return The formatted relative time as a Text object. + */ private static Text toRelativeTime(Duration duration) { - ModConfig.TimeDisplaysConfig config = ModConfig.getConfig().timeDisplays; + var config = ModConfig.getConfig().excaliburTime; boolean isPast = duration.isNegative(); - Duration absDuration = duration.abs(); + Duration abs = duration.abs(); - long days = absDuration.toDays(); - absDuration = absDuration.minusDays(days); - long hours = absDuration.toHours(); - absDuration = absDuration.minusHours(hours); - long minutes = absDuration.toMinutes(); + long days = abs.toDays(); + abs = abs.minusDays(days); + long hours = abs.toHours(); + abs = abs.minusHours(hours); + long minutes = abs.toMinutes(); StringBuilder sb = new StringBuilder(); if (days > 0) sb.append(days).append("d "); @@ -54,26 +102,49 @@ private static Text toRelativeTime(Duration duration) { if (timeString.isEmpty()) timeString = "0m"; String key = isPast - ? "elements-utils.display.excaliburtimer.relative_after" - : "elements-utils.display.excaliburtimer.relative"; + ? "elements-utils.display.excaliburTime.relative_after" + : "elements-utils.display.excaliburTime.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); + /** + * Calculates the target instant based on the given start time. + * + * @param startTime The start time as an Instant. + * @return The calculated target Instant, or null if startTime is null. + */ + private static Instant calculateTargetInstant(Instant startTime) { + if (startTime == null) return null; + return ZonedDateTime.ofInstant(startTime, ZoneId.systemDefault()) + .plusDays(DAYS) + .plusSeconds(EXTRA_SECONDS) + .toInstant(); + } + + /** + * Formats the target instant as a date-time string. + * + * @param targetInstant The target instant to format. + * @return The formatted date-time string, or "?" if targetInstant is null. + */ + private static String formatTargetTime(Instant targetInstant) { + if (targetInstant == null) return "?"; + return FORMATTER.format(targetInstant.atZone(ZoneId.systemDefault())); } + /** + * Draws a line of text on the screen at the specified line index. + * + * @param client The Minecraft client instance. + * @param context The draw context. + * @param line The line index for vertical positioning. + * @param text The text to draw. + */ private static void drawText(MinecraftClient client, DrawContext context, int line, Text text) { int lineHeight = client.textRenderer.fontHeight + 3; - boolean outline = ModConfig.getConfig().timeDisplays.textOutline; + boolean outline = ModConfig.getConfig().excaliburTime.textOutline; context.drawText( client.textRenderer, text, diff --git a/src/client/java/dev/eposs/elementsutils/feature/xpformat/FarmingXpTracker.java b/src/client/java/dev/eposs/elementsutils/feature/xpformat/FarmingXpTracker.java new file mode 100644 index 0000000..5fc3cb7 --- /dev/null +++ b/src/client/java/dev/eposs/elementsutils/feature/xpformat/FarmingXpTracker.java @@ -0,0 +1,67 @@ +package dev.eposs.elementsutils.feature.xpformat; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tracks farming XP changes over time to calculate XP per second. + */ +public class FarmingXpTracker { + private static final List timestamps = new ArrayList<>(); + private static final List xpValues = new ArrayList<>(); + private static long lastMessageTime = 0; + private static int lastXp = -1; + + /** + * Updates the tracker with the current XP value. + * Resets the history if more than 5 seconds have passed since the last update. + * Only stores values if the XP has changed. + * Removes values older than 20 seconds. + * + * @param currentXp the current XP value + */ + public static void update(int currentXp) { + long now = System.currentTimeMillis(); + + if (now - lastMessageTime > 5000) { + timestamps.clear(); + xpValues.clear(); + lastXp = currentXp; + } + + if (currentXp != lastXp) { + timestamps.add(now); + xpValues.add(currentXp); + lastXp = currentXp; + } + lastMessageTime = now; + + while (!timestamps.isEmpty() && now - timestamps.getFirst() > 20000) { + timestamps.removeFirst(); + xpValues.removeFirst(); + } + } + + /** + * Calculates the XP gained per second over the tracked period. + * + * @return the XP per second as a float + */ + public static float getXpPerSecond() { + if (xpValues.size() < 2) return 0f; + int deltaXp = xpValues.getLast() - xpValues.getFirst(); + long deltaTime = timestamps.getLast() - timestamps.getFirst(); + if (deltaTime == 0) return 0f; + return deltaXp / (deltaTime / 1000f); + } + + /** + * Resets the tracker and clears all stored values. + */ + public static void reset() { + timestamps.clear(); + xpValues.clear(); + lastXp = -1; + lastMessageTime = 0; + } +} \ 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 index a95f34a..c717f8c 100644 --- a/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudAccessor.java +++ b/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudAccessor.java @@ -7,9 +7,6 @@ @Mixin(InGameHud.class) public interface InGameHudAccessor { - @Accessor("title") - Text getTitle(); - @Accessor("overlayMessage") Text getOverlayMessage(); } 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 0999ba2..35987b9 100644 --- a/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java +++ b/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java @@ -2,22 +2,24 @@ import dev.eposs.elementsutils.config.ModConfig; import dev.eposs.elementsutils.feature.pet.PetDisplay; +import dev.eposs.elementsutils.feature.xpformat.FarmingXpTracker; import dev.eposs.elementsutils.feature.xpformat.XpFormat; 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.Style; 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.Unique; 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; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + @Mixin(value = InGameHud.class) public abstract class InGameHudMixin { @@ -41,12 +43,18 @@ 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. + * Intercepts and formats the overlay message before it is displayed. + *

+ * - Formats XP numbers with dots if enabled.
+ * - Optionally appends colored XP/s directly after the first "XP".
+ * - Hides max pet XP if configured.
+ * - Applies a custom overlay color if set in the config.
+ * - Cancels the original method to display the modified message. + *

* - * @param message The original overlay message. - * @param tinted Whether the message is tinted. - * @param ci The callback info. + * @param message The original overlay message to be displayed. + * @param tinted Whether the message should be tinted. + * @param ci The callback info for cancelling or continuing the method. */ @Inject( method = "setOverlayMessage", @@ -54,29 +62,81 @@ private void renderOverlayMessage(DrawContext context, RenderTickCounter tickCou cancellable = true ) private void onSetOverlayMessage(Text message, boolean tinted, CallbackInfo ci) { - if (message != null) { - String original = message.getString(); + if (message == null) return; + + String original = message.getString(); + String formatted = ModConfig.getConfig().playerXPConfig.enabled + ? XpFormat.formatNumbersWithDots(original) + : original; + + Style overlayStyle = message.getStyle(); + var overlayColor = ModConfig.getConfig().playerXPConfig.overlayMessageColor; + if (overlayColor != null && formatted.contains("XP")) { + overlayStyle = overlayStyle.withColor(overlayColor.color); + } - if (ModConfig.getConfig().playerXPConfig.hideMaxPetXP) { - boolean isMaxLevel = original.matches(".*Pet: [\\d,.]+/-1 XP$"); - if (isMaxLevel) PetDisplay.setPetMaxLevel(); - original = original.replaceFirst("XP.*(\\p{So}?\\s*Pet: [\\d,.]+/-1 XP)$", "XP"); + Text xpPerSecText = null; + Style xpPerSecStyle = Style.EMPTY; + Matcher matcher = Pattern.compile("^[^:]+: ([\\d,.]+)/[\\d,.]+ XP").matcher(original); + if (matcher.find()) { + int farmingXp = Integer.parseInt(matcher.group(1).replace(".", "").replace(",", "")); + FarmingXpTracker.update(farmingXp); + + if (ModConfig.getConfig().playerXPConfig.showXpPerSecond) { + float xpPerSec = FarmingXpTracker.getXpPerSecond(); + var color = ModConfig.getConfig().playerXPConfig.xpPerSecondColor; + if (color != null) xpPerSecStyle = xpPerSecStyle.withColor(color.color); + xpPerSecText = Text.literal(String.format("%.2fXP/s", xpPerSec)).setStyle(xpPerSecStyle); } + } - String formatted = ModConfig.getConfig().playerXPConfig.enabled + if (ModConfig.getConfig().playerXPConfig.hideMaxPetXP) { + if (original.matches(".*Pet: [\\d,.]+/-1 XP$")) PetDisplay.setPetMaxLevel(); + original = original.replaceFirst("\\s*\\p{So}?\\s*Pet: [\\d,.]+/-1 XP$", ""); + 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); + if (xpPerSecText != null) { + int xpIndex = formatted.indexOf("XP"); + if (xpIndex != -1) { + String beforeXp = formatted.substring(0, xpIndex + 2); + String afterXp = formatted.substring(xpIndex + 2); + this.overlayMessage = Text.literal(beforeXp).setStyle(overlayStyle) + .append(Text.literal(" (").setStyle(xpPerSecStyle)) + .append(xpPerSecText) + .append(Text.literal(")").setStyle(xpPerSecStyle)) + .append(Text.literal(afterXp).setStyle(overlayStyle)); + this.overlayRemaining = 60; + ci.cancel(); + return; } - - this.overlayMessage = Text.literal(formatted).setStyle(style); - this.overlayRemaining = 60; - ci.cancel(); } + + this.overlayMessage = Text.literal(formatted).setStyle(overlayStyle); + this.overlayRemaining = 60; + ci.cancel(); + } + + /** + * Modifies the Y position of the overlay message. + * Adds a configurable offset to the original Y position. + * + * @param originalY The original Y position of the overlay message. + * @return The modified Y position with the offset applied. + */ + @ModifyArg( + method = "renderOverlayMessage", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/DrawContext;drawTextWithBackground(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/Text;IIII)I" + ), + index = 3 + ) + private int modifyOverlayMessageY(int originalY) { + int yOffset = ModConfig.getConfig().playerXPConfig.overlayMessageYOffset; + return originalY + yOffset; } /** 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 2d73360..bec06c9 100644 --- a/src/main/resources/assets/elements-utils/lang/de_de.json +++ b/src/main/resources/assets/elements-utils/lang/de_de.json @@ -8,15 +8,19 @@ "text.autoconfig.elements-utils.option.showPetDisplay": "Pet anzeigen", "text.autoconfig.elements-utils.option.displayPosition": "Display Position", - "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.bossTimer": "Dungeon Boss Todeszeiten", + "text.autoconfig.elements-utils.option.bossTimer.show": "Display anzeigen", + "text.autoconfig.elements-utils.option.bossTimer.textOutline": "Text Umriss", + "text.autoconfig.elements-utils.option.bossTimer.colorBossNames": "Farbige Boss Namen anzeigen", + "text.autoconfig.elements-utils.option.bossTimer.colorBossTime": "Farbige Boss Zeit anzeigen", + "text.autoconfig.elements-utils.option.bossTimer.bossTimeFormat": "Boss Zeit Format", + + "text.autoconfig.elements-utils.option.excaliburTime": "Excalibur Zeit Anzeige", + "text.autoconfig.elements-utils.option.excaliburTime.show": "Display anzeigen", + "text.autoconfig.elements-utils.option.excaliburTime.textOutline": "Text Umriss", + "text.autoconfig.elements-utils.option.excaliburTime.colorExcaliburNames": "Farbige Excalibur Namen anzeigen", + "text.autoconfig.elements-utils.option.excaliburTime.colorExcaliburTime": "Farbige Excalibur Zeit anzeigen", + "text.autoconfig.elements-utils.option.excaliburTime.excaliburTimeFormat": "Excalibur Zeit Format", "text.autoconfig.elements-utils.option.playLootSound": "Sound bei Loot drop", @@ -37,15 +41,19 @@ "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.showXpPerSecond": "XP/s anzeigen", + "text.autoconfig.elements-utils.option.playerXPConfig.xpPerSecondColor": "XP/s Farbe", "text.autoconfig.elements-utils.option.playerXPConfig.hideMaxPetXP": "Max Pet-XP ausblenden", + "text.autoconfig.elements-utils.option.playerXPConfig.overlayMessageColor": "Farbe der Overlay-Nachricht", + "text.autoconfig.elements-utils.option.playerXPConfig.overlayMessageYOffset": "Overlay Nachricht Y-Offset", "text.autoconfig.elements-utils.option.devUtils": "Development Utils (bitte ignorieren)", "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.timeDisplaysToggle": "Zeit Displays umschalten", + "key.elements-utils.bossTimerToggle": "Dungeon Boss Todeszeiten umschalten", + "key.elements-utils.excaliburTimeToggle": "Excalibur Zeit Anzeige 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)", @@ -55,12 +63,12 @@ "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.excaliburTime.title": "Excalibur Zeit", + "elements-utils.display.excaliburTime.next_player": "Nächster Spieler: ", + "elements-utils.display.excaliburTime.time_left": "Zeit: ", - "elements-utils.display.excaliburtimer.relative": "in %s", - "elements-utils.display.excaliburtimer.relative_after": "vor %s", + "elements-utils.display.excaliburTime.relative": "in %s", + "elements-utils.display.excaliburTime.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 3e515a3..dc43c7e 100644 --- a/src/main/resources/assets/elements-utils/lang/en_us.json +++ b/src/main/resources/assets/elements-utils/lang/en_us.json @@ -8,15 +8,19 @@ "text.autoconfig.elements-utils.option.showPetDisplay": "Show Pet Display", "text.autoconfig.elements-utils.option.displayPosition": "Display Position", - "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.bossTimer": "Dungeon Boss Death Times", + "text.autoconfig.elements-utils.option.bossTimer.show": "Show Display", + "text.autoconfig.elements-utils.option.bossTimer.textOutline": "Text Outline", + "text.autoconfig.elements-utils.option.bossTimer.colorBossNames": "Show colored Boss Names", + "text.autoconfig.elements-utils.option.bossTimer.colorBossTime": "Show colored Boss Time", + "text.autoconfig.elements-utils.option.bossTimer.bossTimeFormat": "Boss Time Format", + + "text.autoconfig.elements-utils.option.excaliburTime": "Excalibur Time Display", + "text.autoconfig.elements-utils.option.excaliburTime.show": "Show Display", + "text.autoconfig.elements-utils.option.excaliburTime.textOutline": "Text Outline", + "text.autoconfig.elements-utils.option.excaliburTime.colorExcaliburNames": "Show colored Excalibur Names", + "text.autoconfig.elements-utils.option.excaliburTime.colorExcaliburTime": "Show colored Excalibur Time", + "text.autoconfig.elements-utils.option.excaliburTime.excaliburTimeFormat": "Excalibur Time Format", "text.autoconfig.elements-utils.option.playLootSound": "Play sound on Loot drop", @@ -37,15 +41,19 @@ "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.showXpPerSecond": "Show XP/s", + "text.autoconfig.elements-utils.option.playerXPConfig.xpPerSecondColor": "XP/s Color", "text.autoconfig.elements-utils.option.playerXPConfig.hideMaxPetXP": "Hide Max Pet XP", + "text.autoconfig.elements-utils.option.playerXPConfig.overlayMessageColor": "Overlay Message Color", + "text.autoconfig.elements-utils.option.playerXPConfig.overlayMessageYOffset": "Overlay Message Y-Offset", "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.timeDisplaysToggle": "Toggle Time Displays", + "key.elements-utils.bossTimerToggle": "Toggle Dungeon Boss Death Times", + "key.elements-utils.excaliburTimeToggle": "Toggle Excalibur Time Display", "key.elements-utils.xpMeasureTrigger": "XP-Measurement (Basalt) Trigger", "key.elements-utils.timeMeasureTrigger": "Time-Measurement (Basalt) Trigger", "key.elements-utils.devUtils": "Dev Utils (please ignore)", @@ -55,12 +63,12 @@ "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.excaliburTime.title": "Excalibur Time", + "elements-utils.display.excaliburTime.next_player": "Next player: ", + "elements-utils.display.excaliburTime.time_left": "Time: ", - "elements-utils.display.excaliburtimer.relative": "in %s", - "elements-utils.display.excaliburtimer.relative_after": "%s ago", + "elements-utils.display.excaliburTime.relative": "in %s", + "elements-utils.display.excaliburTime.relative_after": "%s ago", "elements-utils.display.base.toggle": "Base display is now ",