diff --git a/README.md b/README.md index 35a6e3a..b59dd91 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,22 @@ _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 (Basalt) + - 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 +- Excalibur Time Display + - Show the time until the next Excalibur is available + - Show the next Player who can pull Excalibur + ## Config Use Modmenu to toggle features or change the position of the display diff --git a/gradle.properties b/gradle.properties index ffb57dc..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.0+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 306831b..06273d0 100644 --- a/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java +++ b/src/client/java/dev/eposs/elementsutils/ElementsUtilsClient.java @@ -3,16 +3,17 @@ 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; 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; 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; @@ -30,7 +31,9 @@ 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; @Override @@ -43,6 +46,7 @@ public void onInitializeClient() { registerKeyBinding(); registerEvents(); + ExcaliburTimerData.startUpdateTimers(); BossTimerData.startUpdateTimers(); } @@ -63,11 +67,16 @@ 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) { runServerCheck(client); - PetDisplay.loadPet(handler.getRegistryManager()); + + ModConfig.InternalConfig.Servers server = ModConfig.getConfig().internal.server; + if (server != ModConfig.InternalConfig.Servers.UNKNOWN) { + PetDisplay.loadPet(handler.getRegistryManager()); + } } private void onLeave(ClientPlayNetworkHandler handler, MinecraftClient client) { @@ -97,9 +106,8 @@ private void runServerCheck(MinecraftClient client) { private boolean onGameMessage(Text text, boolean b) { LootSound.onGameMessage(text); - - return true; - } + return true; + } private void registerKeyBinding() { String category = "category." + ElementsUtils.MOD_ID + ".keys"; @@ -110,12 +118,24 @@ private void registerKeyBinding() { category )); - bossTimerToggle = KeyBindingHelper.registerKeyBinding(new KeyBinding( - getKeyBindingTranslation("bossTimerToggle"), + timeDisplaysToggle = KeyBindingHelper.registerKeyBinding(new KeyBinding( + getKeyBindingTranslation("timeDisplaysToggle"), GLFW.GLFW_KEY_V, category )); + xpMeasureTrigger = KeyBindingHelper.registerKeyBinding(new KeyBinding( + getKeyBindingTranslation("xpMeasureTrigger"), + GLFW.GLFW_KEY_UNKNOWN, + category + )); + + timeMeasureTrigger = KeyBindingHelper.registerKeyBinding(new KeyBinding( + getKeyBindingTranslation("timeMeasureTrigger"), + GLFW.GLFW_KEY_UNKNOWN, + category + )); + devUtils = KeyBindingHelper.registerKeyBinding(new KeyBinding( getKeyBindingTranslation("devUtils"), GLFW.GLFW_KEY_UNKNOWN, @@ -127,9 +147,15 @@ private void onKeyEvent(MinecraftClient client) { while (baseDisplayToggle.wasPressed()) { BaseBorderDisplay.toggleDisplay(client); } - while (bossTimerToggle.wasPressed()) { + while (timeDisplaysToggle.wasPressed()) { BossTimerDisplay.toggleDisplay(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/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 fc5d359..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,10 +85,56 @@ public static class PotionDisplayConfig { public enum Position { LEFT, RIGHT, - } + } } + @ConfigEntry.Gui.CollapsibleObject + public XPMeterConfig xpMeterConfig = new XPMeterConfig(); + public static class XPMeterConfig { + public Integer measuringXpTarget = 500; + public Integer measuringTimeTarget = 300; + } + + @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(); public static class 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..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) { @@ -94,16 +95,17 @@ 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) { 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/pet/PetDisplay.java b/src/client/java/dev/eposs/elementsutils/feature/pet/PetDisplay.java index 7fb7fcd..f665e1f 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"); } @@ -206,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/feature/xpformat/XpFormat.java b/src/client/java/dev/eposs/elementsutils/feature/xpformat/XpFormat.java new file mode 100644 index 0000000..33d0079 --- /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 new file mode 100644 index 0000000..8262725 --- /dev/null +++ b/src/client/java/dev/eposs/elementsutils/feature/xpmeter/XpMeter.java @@ -0,0 +1,303 @@ +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 { + 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; + private static int noXpTicks = 0; + + private static double displayedElapsedSeconds = 0.0; + private static double displayedXpPerSecond = 0.0; + private static long lastDisplayUpdate = 0; + + 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; + + private static int getTargetXp() { + return ModConfig.getConfig().xpMeterConfig.measuringXpTarget; + } + + private static int getTargetTime() { + return ModConfig.getConfig().xpMeterConfig.measuringTimeTarget * 1000; + } + + public static boolean isMeasuringInProgress() { + return measuringInProgress; + } + + public static float getXpProgress() { + 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; + } + + /** + * 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) { + 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; + } + + /** + * 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; + } + + 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() : ""; + + Matcher matcher = MINING_XP_PATTERN.matcher(overlayText); + + if (!matcher.find()) { + Util.sendChatMessage(Text.translatable("elements-utils.message.xpMeter.startFailed")); + return true; + } + + String xpString = matcher.group(1).replaceAll("[.,]", ""); + startXp = Integer.parseInt(xpString); + startTime = System.currentTimeMillis(); + currentProgress = 0; + measuringInProgress = true; + noXpTicks = 0; + displayedElapsedSeconds = 0.0; + displayedXpPerSecond = 0.0; + lastDisplayUpdate = System.currentTimeMillis(); + + lastItemCount = countItemInInventory(client); + itemsGainedTotal = 0; + 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; + + 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.translatable("elements-utils.message.xpMeter.failed")); + measuringInProgress = false; + } + return; + } + + String xpString = matcher.group(1).replaceAll("[.,]", ""); + int currentXp = Integer.parseInt(xpString); + currentProgress = currentXp - startXp; + noXpTicks = 0; + + int currentCount = countItemInInventory(client); + if (currentCount > lastItemCount) { + itemsGainedTotal += (currentCount - lastItemCount); + } + lastItemCount = currentCount; + + 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; + } + } + } + + /** + * 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; + + 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 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()); + + 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); + } +} \ 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/mixin/client/InGameHudMixin.java b/src/client/java/dev/eposs/elementsutils/mixin/client/InGameHudMixin.java index 39f3c4a..3242c29 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,21 @@ package dev.eposs.elementsutils.mixin.client; +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; 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.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; @Mixin(value = InGameHud.class) @@ -18,11 +25,112 @@ public abstract class InGameHudMixin { private Text overlayMessage; @Shadow private int overlayRemaining; + @Unique + private boolean petWasMaxLevel = false; + /** + * 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) { PetDisplay.updatePetXP(this.overlayMessage, false); } } + + /** + * 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(); + + 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"); + } + + 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( + 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().playerLevelConfig.enabled) return original; + try { + int level = Integer.parseInt(original); + return Util.formatLevel(level); + } catch (NumberFormatException e) { + 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 new file mode 100644 index 0000000..07544ad --- /dev/null +++ b/src/client/java/dev/eposs/elementsutils/mixin/client/PlayerListHudMixin.java @@ -0,0 +1,55 @@ +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.Style; +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 { + + /** + * 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( + 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) { + 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 = ModConfig.getConfig().playerLevelConfig.enabled ? Util.formatLevel(score) : original; + int diff = formatted.length() - original.length(); + int pixelPerChar = 2; + + 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/client/java/dev/eposs/elementsutils/rendering/ScreenRendering.java b/src/client/java/dev/eposs/elementsutils/rendering/ScreenRendering.java index 24f1b3d..3939580 100644 --- a/src/client/java/dev/eposs/elementsutils/rendering/ScreenRendering.java +++ b/src/client/java/dev/eposs/elementsutils/rendering/ScreenRendering.java @@ -2,10 +2,12 @@ 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; 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; @@ -31,7 +33,9 @@ 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/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 adb9c3f..1c6d0d5 100644 --- a/src/client/resources/elements-utils.client.mixins.json +++ b/src/client/resources/elements-utils.client.mixins.json @@ -4,7 +4,9 @@ "compatibilityLevel": "JAVA_21", "client": [ "ExampleClientMixin", - "InGameHudMixin" + "InGameHudAccessor", + "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 e1fa5ee..2d73360 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", @@ -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", @@ -22,20 +26,54 @@ "text.autoconfig.elements-utils.option.potionDisplay.show": "Tränke anzeigen", "text.autoconfig.elements-utils.option.potionDisplay.position": "Display Position", - "text.autoconfig.elements-utils.option.devUtils": "Development Utils (please ignore)", + "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.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.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", "key.elements-utils.baseDisplayToggle": "Spieler Base Grenzen umschalten", - "key.elements-utils.bossTimerToggle": "Dungeon Boss Todeszeiten umschalten", - "key.elements-utils.devUtils": "Dev Utils (please ignore)", + "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 ", - + + "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: ", + "elements-utils.message.xpMeter.timeMode": "Zeit-Messung: ", + "elements-utils.message.xpMeter.compressed": "Komprimierte", + + "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" 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..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,41 +1,79 @@ { "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", - + + "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.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", "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.relative": "%s ago", - + + "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", + "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" 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/"