diff --git a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java index f8adf23f860..eb940be7a86 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java @@ -802,6 +802,28 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig .build()) .build()) + //Time Input Calculator + .group(OptionGroup.createBuilder() + .name(Component.translatable("skyblocker.config.uiAndVisuals.timeInputCalculator")) + .collapsed(true) + .option(Option.createBuilder() + .name(Component.translatable("skyblocker.config.uiAndVisuals.timeInputCalculator.enabled")) + .description(Component.translatable("skyblocker.config.uiAndVisuals.timeInputCalculator.enabled.@Tooltip")) + .binding(defaults.uiAndVisuals.timeInputCalculator.enabled, + () -> config.uiAndVisuals.timeInputCalculator.enabled, + newValue -> config.uiAndVisuals.timeInputCalculator.enabled = newValue) + .controller(ConfigUtils.createBooleanController()) + .build()) + .option(Option.createBuilder() + .name(Component.translatable("skyblocker.config.uiAndVisuals.timeInputCalculator.closeSignsWithEnter")) + .description(Component.translatable("skyblocker.config.uiAndVisuals.timeInputCalculator.closeSignsWithEnter.@Tooltip")) + .binding(defaults.uiAndVisuals.timeInputCalculator.closeSignsWithEnter, + () -> config.uiAndVisuals.timeInputCalculator.closeSignsWithEnter, + newValue -> config.uiAndVisuals.timeInputCalculator.closeSignsWithEnter = newValue) + .controller(ConfigUtils.createBooleanController()) + .build()) + .build()) + //Flame Overlay .group(OptionGroup.createBuilder() .name(Component.translatable("skyblocker.config.uiAndVisuals.flameOverlay")) diff --git a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java index 6e0323c06d3..787bc472f64 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java @@ -76,6 +76,8 @@ public class UIAndVisualsConfig { public InputCalculator inputCalculator = new InputCalculator(); + public TimeInputCalculator timeInputCalculator = new TimeInputCalculator(); + public FlameOverlay flameOverlay = new FlameOverlay(); public CompactDamage compactDamage = new CompactDamage(); @@ -411,6 +413,12 @@ public static class InputCalculator { public boolean closeSignsWithEnter = true; } + public static class TimeInputCalculator { + public boolean enabled = true; + + public boolean closeSignsWithEnter = true; + } + public static class FlameOverlay { public int flameHeight = 100; diff --git a/src/main/java/de/hysky/skyblocker/mixins/AbstractSignEditScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/AbstractSignEditScreenMixin.java index 50c6b10149f..c58d96a5af5 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/AbstractSignEditScreenMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/AbstractSignEditScreenMixin.java @@ -5,6 +5,7 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.bazaar.BazaarQuickQuantities; import de.hysky.skyblocker.skyblock.calculators.SignCalculator; +import de.hysky.skyblocker.skyblock.calculators.SignTimeCalculator; import de.hysky.skyblocker.skyblock.speedpreset.SpeedPresets; import de.hysky.skyblocker.utils.Utils; import net.minecraft.ChatFormatting; @@ -14,6 +15,7 @@ import net.minecraft.client.gui.screens.inventory.AbstractSignEditScreen; import net.minecraft.client.input.KeyEvent; import net.minecraft.network.chat.Component; +import org.jspecify.annotations.Nullable; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -59,18 +61,28 @@ protected AbstractSignEditScreenMixin(Component title) { context.guiWidth() / 2, 55, 0xFFFFFFFF); } } - //if the sign is being used to enter number send it to the sign calculator - else if (isInputSign() && config.uiAndVisuals.inputCalculator.enabled) { - SignCalculator.renderCalculator(context, messages[0], context.guiWidth() / 2, 55); + else { + SignTimeCalculator.TimeType timeInputType = getTimeInputType(); + if (timeInputType != null) { + if (config.uiAndVisuals.timeInputCalculator.enabled) { + SignTimeCalculator.renderTime(context, messages[0], timeInputType, context.guiWidth() / 2, 55); + } + } + //if the sign is being used to enter number send it to the sign calculator + else if (isInputSign() && config.uiAndVisuals.inputCalculator.enabled) { + SignCalculator.renderCalculator(context, messages[0], context.guiWidth() / 2, 55); + } } } } @Inject(method = "keyPressed", at = @At("HEAD")) private void skyblocker$keyPressed(KeyEvent input, CallbackInfoReturnable cir) { - if (SkyblockerConfigManager.get().uiAndVisuals.inputCalculator.closeSignsWithEnter - && Utils.isOnSkyblock() && isInputSign() - && (input.isConfirmation())) this.onClose(); + if (input.isConfirmation() && Utils.isOnSkyblock() + && ( + SkyblockerConfigManager.get().uiAndVisuals.timeInputCalculator.closeSignsWithEnter && getTimeInputType() != null + || SkyblockerConfigManager.get().uiAndVisuals.inputCalculator.closeSignsWithEnter && isInputSign() + )) this.onClose(); } @Inject(method = "onDone", at = @At("HEAD")) @@ -84,6 +96,12 @@ else if (isInputSign() && config.uiAndVisuals.inputCalculator.enabled) { messages[0] = String.valueOf(presets.getPreset(messages[0])); } } + else if (getTimeInputType() != null) { + if (config.uiAndVisuals.timeInputCalculator.enabled) { + String value = SignTimeCalculator.getNewValue(); + if (value != null) messages[0] = value; + } + } //if the sign is being used to enter number get number from calculator for if maths has been done else if (isInputSign() && config.uiAndVisuals.inputCalculator.enabled) { boolean isPrice = messages[2].contains("price"); @@ -99,6 +117,10 @@ else if (isInputSign() && config.uiAndVisuals.inputCalculator.enabled) { @Unique private static final String SPEED_INPUT_MARKER = "speed cap!"; @Unique + private static final String TIME_INPUT_MARKER_HOURS = "hours"; + @Unique + private static final String TIME_INPUT_MARKER_MINUTES = "minutes"; + @Unique private static final String INPUT_SIGN_MARKER = "^^^^^^^^^^^^^^^"; /** This is used for some things like the super craft amount input */ @Unique @@ -111,6 +133,20 @@ private boolean isSpeedInputSign() { return messages[3].equals(SPEED_INPUT_MARKER); } + @Unique + private SignTimeCalculator.@Nullable TimeType getTimeInputType() { + if (messages[1].equals(INPUT_SIGN_MARKER)) { + if (messages[3].endsWith(TIME_INPUT_MARKER_HOURS)) { + return SignTimeCalculator.TimeType.HOURS; + } + if (messages[3].endsWith(TIME_INPUT_MARKER_MINUTES)) { + return SignTimeCalculator.TimeType.MINUTES; + } + } + + return null; + } + /** * Used to exclude search signs with {@link AbstractSignEditScreenMixin#INPUT_SIGN_MARKER}. *
Works for /recipes & /shards signs @@ -131,6 +167,10 @@ private boolean isAltInputSearchSign() { @Unique private boolean isInputSign() { - return messages[1].equals(INPUT_SIGN_MARKER) && !isInputSearchSign() || messages[1].equals(ALT_INPUT_SIGN_MARKER) && !isAltInputSearchSign() || messages[1].equals(BAZAAR_FLIP_MARKER); + return ( + messages[1].equals(INPUT_SIGN_MARKER) && !isInputSearchSign() + || messages[1].equals(ALT_INPUT_SIGN_MARKER) && !isAltInputSearchSign() + || messages[1].equals(BAZAAR_FLIP_MARKER) + ) && getTimeInputType() == null; } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/calculators/SignTimeCalculator.java b/src/main/java/de/hysky/skyblocker/skyblock/calculators/SignTimeCalculator.java new file mode 100644 index 00000000000..f7db422fefb --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/calculators/SignTimeCalculator.java @@ -0,0 +1,142 @@ +package de.hysky.skyblocker.skyblock.calculators; + +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import org.jetbrains.annotations.VisibleForTesting; + +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SignTimeCalculator +{ + public enum TimeType { + HOURS, MINUTES + } + + private static final Minecraft CLIENT = Minecraft.getInstance(); + private static final Pattern TIME_CODE_PATTERN = Pattern.compile("(\\d+\\.\\d+|\\d+)(\\D+)?"); + private static final int SECONDS_PER_DAY = 86400; + private static final short SECONDS_PER_HOUR = 3600; + private static final byte SECONDS_PER_MINUTE = 60; + + private static String lastInput; + private static long seconds; + private static Component error = null; + + public static void renderTime(GuiGraphics context, String message, TimeType type, int renderX, int renderY) { + calculate(message, type); + render(context, renderX, renderY); + } + + @VisibleForTesting + public static void calculate(String message, TimeType type) { + //only update if new input + if (message.equals(lastInput)) return; + lastInput = message; + + if (message.isBlank()) { + seconds = -1; + error = Component.translatable("skyblocker.config.uiAndVisuals.timeInputCalculator.emptyInputError").withStyle(ChatFormatting.RED); + return; + } + + long newSeconds = 0; + for (String segment : message.trim().split(" +")) { + Matcher matcher = TIME_CODE_PATTERN.matcher(segment); + if (matcher.matches()) { + String numberString = matcher.group(1); + if (numberString.startsWith("-")) { + seconds = -1; + error = Component.translatable("skyblocker.config.uiAndVisuals.timeInputCalculator.invalidNumberError", numberString).withStyle(ChatFormatting.RED); + return; + } + double number; + try { + number = Double.parseDouble(numberString); + } catch (NumberFormatException e) { + seconds = -1; + error = Component.translatable("skyblocker.config.uiAndVisuals.timeInputCalculator.invalidNumberError", numberString).withStyle(ChatFormatting.RED); + return; + } + + String timeUnitString = matcher.group(2); + if (timeUnitString == null || timeUnitString.isEmpty()) { + newSeconds += switch (type) { + case HOURS -> Math.round(number * SECONDS_PER_HOUR); + case MINUTES -> Math.round(number * SECONDS_PER_MINUTE); + }; + } + //mimics how Hypixel parses numbers, except also allowing plurals + else if ("days".startsWith(timeUnitString.toLowerCase(Locale.ENGLISH))) { + newSeconds += Math.round(number * SECONDS_PER_DAY); + } else if ("hours".startsWith(timeUnitString.toLowerCase(Locale.ENGLISH))) { + newSeconds += Math.round(number * SECONDS_PER_HOUR); + } else if ("minutes".startsWith(timeUnitString.toLowerCase(Locale.ENGLISH))) { + newSeconds += Math.round(number * SECONDS_PER_MINUTE); + } else if ("seconds".startsWith(timeUnitString.toLowerCase(Locale.ENGLISH))) { + //note/fun fact: for some reason Hypixel only allows up to "secon", not "second" + newSeconds += Math.round(number); + } else { + seconds = -1; + error = Component.translatable("skyblocker.config.uiAndVisuals.timeInputCalculator.invalidTimeUnit", timeUnitString).withStyle(ChatFormatting.RED); + return; + } + } else { + seconds = -1; + error = Component.translatable("skyblocker.config.uiAndVisuals.timeInputCalculator.invalidInputSpecific", segment).withStyle(ChatFormatting.RED); + return; + } + } + + seconds = newSeconds; + error = null; + } + + public static String getNewValue() { + if (seconds == -1) { + //if calculator is not activated or just invalid input return what the user typed in + return lastInput; + } + + //we can just return the total seconds, Hypixel converts it for us :) + return seconds + "s"; + } + + private static void render(GuiGraphics context, int renderX, int renderY) { + Component text; + if (seconds == -1) { + text = error != null ? error : Component.translatable("skyblocker.config.uiAndVisuals.timeInputCalculator.invalidInput").withStyle(ChatFormatting.RED); + } else { + long remainingSeconds = seconds; + MutableComponent timeText = Component.empty(); + remainingSeconds = appendTimeComponent(timeText, remainingSeconds, SECONDS_PER_DAY, "day"); + remainingSeconds = appendTimeComponent(timeText, remainingSeconds, SECONDS_PER_HOUR, "hour"); + remainingSeconds = appendTimeComponent(timeText, remainingSeconds, SECONDS_PER_MINUTE, "minute"); + appendTimeComponent(timeText, remainingSeconds, 1, "second"); + text = timeText.withStyle(ChatFormatting.GREEN); + } + + context.drawCenteredString(CLIENT.font, text, renderX, renderY, 0xFFFFFFFF); + } + + private static long appendTimeComponent(MutableComponent component, long seconds, int secondsPerUnit, String translationKeyUnit) + { + long units = seconds / secondsPerUnit; //long division -> cuts off decimal places automatically + if (units > 0) { + seconds -= units * secondsPerUnit; + if (!component.equals(CommonComponents.EMPTY)) { + component.append(" "); + } + component.append(Component.translatable( + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit." + translationKeyUnit + (units == 1 ? "" : "s"), + units + )); + } + return seconds; + } +} diff --git a/src/main/resources/assets/skyblocker/lang/de_de.json b/src/main/resources/assets/skyblocker/lang/de_de.json index ce2e54ad27c..750a8f5660e 100644 --- a/src/main/resources/assets/skyblocker/lang/de_de.json +++ b/src/main/resources/assets/skyblocker/lang/de_de.json @@ -9,6 +9,24 @@ "skyblocker.config.uiAndVisuals.tabHud.tabHudScale.@Tooltip": "Wert in %, relativ zur Skalierung des Vanilla-GUIs", "skyblocker.config.uiAndVisuals.bars.enableBars": "Balken aktivieren", "skyblocker.config.uiAndVisuals.hideEmptyTooltips": "Leere Item-Tooltips in Menüs verstecken", + "skyblocker.config.uiAndVisuals.timeInputCalculator": "Zeiteingaberechner", + "skyblocker.config.uiAndVisuals.timeInputCalculator.closeSignsWithEnter": "Schilder mit Eingabetaste schließen", + "skyblocker.config.uiAndVisuals.timeInputCalculator.closeSignsWithEnter.@Tooltip": "Schließe das aktuell geöffnete Schildfenster mit der Eingabetaste", + "skyblocker.config.uiAndVisuals.timeInputCalculator.emptyInputError": "Eingabe ist leer", + "skyblocker.config.uiAndVisuals.timeInputCalculator.enabled": "Zeiteingaberechner aktivieren", + "skyblocker.config.uiAndVisuals.timeInputCalculator.enabled.@Tooltip": "Zeigt Zeiteinheiten an und konvertiert diese beim Eingeben von Zeitwerten wie Dauer für das Auktionshaus.\n(Beispiel: '0.5d 2h' = 14 Stunden)\n Einheiten:\n s(econds)\n m(inutes)\n h(ours)\n d(ays)", + "skyblocker.config.uiAndVisuals.timeInputCalculator.invalidInput": "Ungültige Eingabe", + "skyblocker.config.uiAndVisuals.timeInputCalculator.invalidInputSpecific": "Ungültige Eingabe '%s'", + "skyblocker.config.uiAndVisuals.timeInputCalculator.invalidNumberError": "Ungültige Zahl '%s'", + "skyblocker.config.uiAndVisuals.timeInputCalculator.invalidTimeUnit": "Ungültige Zeiteinheit '%s'", + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit.day": "%s Tag", + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit.days": "%s Tage", + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit.hour": "%s Stunde", + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit.hours": "%s Stunden", + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit.minute": "%s Minute", + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit.minutes": "%s Minuten", + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit.second": "%s Sekunde", + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit.seconds": "%s Sekunden", "skyblocker.config.otherLocations": "Andere Orte", "skyblocker.config.dungeons": "Dungeons", "skyblocker.config.dungeons.map.enableMap": "Karte aktivieren", diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 127e947f957..cce0f9659fe 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -1390,6 +1390,25 @@ "skyblocker.config.uiAndVisuals.teleportOverlay.showWhenInAir": "Show Overlay Mid Air", "skyblocker.config.uiAndVisuals.teleportOverlay.teleportOverlayColor": "Teleport Overlay Color", + "skyblocker.config.uiAndVisuals.timeInputCalculator": "Time Input Calculator", + "skyblocker.config.uiAndVisuals.timeInputCalculator.closeSignsWithEnter": "Close signs with enter", + "skyblocker.config.uiAndVisuals.timeInputCalculator.closeSignsWithEnter.@Tooltip": "Close the current open sign screen with the enter key", + "skyblocker.config.uiAndVisuals.timeInputCalculator.emptyInputError": "Input is empty", + "skyblocker.config.uiAndVisuals.timeInputCalculator.enabled": "Enable Time Input Calculator", + "skyblocker.config.uiAndVisuals.timeInputCalculator.enabled.@Tooltip": "Displays and converts time units when inputting time values such as duration for the ah.\nExample: '0.5d 2h' = 14 Hours\n Units:\n s(econds)\n m(inutes)\n h(ours)\n d(ays)", + "skyblocker.config.uiAndVisuals.timeInputCalculator.invalidInput": "Invalid input", + "skyblocker.config.uiAndVisuals.timeInputCalculator.invalidInputSpecific": "Invalid input '%s'", + "skyblocker.config.uiAndVisuals.timeInputCalculator.invalidNumberError": "Invalid number '%s'", + "skyblocker.config.uiAndVisuals.timeInputCalculator.invalidTimeUnit": "Invalid time unit '%s'", + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit.day": "%s Day", + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit.days": "%s Days", + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit.hour": "%s Hour", + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit.hours": "%s Hours", + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit.minute": "%s Minute", + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit.minutes": "%s Minutes", + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit.second": "%s Second", + "skyblocker.config.uiAndVisuals.timeInputCalculator.unit.seconds": "%s Seconds", + "skyblocker.config.uiAndVisuals.titleContainer": "Title Container", "skyblocker.config.uiAndVisuals.titleContainer.@Tooltip": "Used to display multiple titles at once, Example use: Vampire Slayer", "skyblocker.config.uiAndVisuals.titleContainer.alignment.LEFT": "Left",