aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAaron <51387595+AzureAaron@users.noreply.github.com>2024-05-14 17:04:03 -0400
committerGitHub <noreply@github.com>2024-05-14 17:04:03 -0400
commit992ee43a9e5d78b9613f597923e20f0be4a49f63 (patch)
tree7768069aacbbfb255836fd131ca02235d6c56dfe
parentcfff7e13191e8c70c8535b831a13b40ce2888ba6 (diff)
parent7edaa418067580434ef189a361a6802faabb3b1e (diff)
downloadSkyblocker-992ee43a9e5d78b9613f597923e20f0be4a49f63.tar.gz
Skyblocker-992ee43a9e5d78b9613f597923e20f0be4a49f63.tar.bz2
Skyblocker-992ee43a9e5d78b9613f597923e20f0be4a49f63.zip
Merge pull request #686 from olim88/sign-calculator
Sign calculator
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java22
-rw-r--r--src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java11
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/SignEditScreenMixin.java44
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/auction/EditBidPopup.java15
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/calculators/CalculatorCommand.java56
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/calculators/SignCalculator.java66
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Calculator.java208
-rw-r--r--src/main/resources/assets/skyblocker/lang/en_us.json7
-rw-r--r--src/main/resources/skyblocker.mixins.json1
-rw-r--r--src/test/java/de/hysky/skyblocker/utils/CalculatorTest.java31
11 files changed, 460 insertions, 3 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 3c6f33a2..77b1ec2a 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -8,6 +8,7 @@ import de.hysky.skyblocker.config.ImageRepoLoader;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.debug.Debug;
import de.hysky.skyblocker.skyblock.*;
+import de.hysky.skyblocker.skyblock.calculators.CalculatorCommand;
import de.hysky.skyblocker.skyblock.chat.ChatRuleAnnouncementScreen;
import de.hysky.skyblocker.skyblock.chat.ChatRulesHandler;
import de.hysky.skyblocker.skyblock.crimson.kuudra.Kuudra;
@@ -132,6 +133,7 @@ public class SkyblockerMod implements ClientModInitializer {
Shortcuts.init();
ChatRulesHandler.init();
ChatRuleAnnouncementScreen.init();
+ CalculatorCommand.init();
DiscordRPCManager.init();
LividColor.init();
FishingHelper.init();
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 c6936335..e6cd3c54 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java
@@ -333,6 +333,28 @@ public class UIAndVisualsCategory {
.build())
.build())
+ //Input Calculator
+ .group(OptionGroup.createBuilder()
+ .name(Text.translatable("skyblocker.config.uiAndVisuals.inputCalculator"))
+ .collapsed(true)
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("skyblocker.config.uiAndVisuals.inputCalculator.enabled"))
+ .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.inputCalculator.enabled.@Tooltip")))
+ .binding(defaults.uiAndVisuals.inputCalculator.enabled,
+ () -> config.uiAndVisuals.inputCalculator.enabled,
+ newValue -> config.uiAndVisuals.inputCalculator.enabled = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("skyblocker.config.uiAndVisuals.inputCalculator.requiresEquals"))
+ .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.inputCalculator.requiresEquals.@Tooltip")))
+ .binding(defaults.uiAndVisuals.inputCalculator.requiresEquals,
+ () -> config.uiAndVisuals.inputCalculator.requiresEquals,
+ newValue -> config.uiAndVisuals.inputCalculator.requiresEquals = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .build())
+
//Flame Overlay
.group(OptionGroup.createBuilder()
.name(Text.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 267dde14..03d300f4 100644
--- a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java
@@ -54,6 +54,9 @@ public class UIAndVisualsConfig {
public SearchOverlay searchOverlay = new SearchOverlay();
@SerialEntry
+ public InputCalculator inputCalculator = new InputCalculator();
+
+ @SerialEntry
public FlameOverlay flameOverlay = new FlameOverlay();
public static class ChestValue {
@@ -239,6 +242,14 @@ public class UIAndVisualsConfig {
public List<String> auctionHistory = new ArrayList<>();
}
+ public static class InputCalculator {
+ @SerialEntry
+ public boolean enabled = true;
+
+ @SerialEntry
+ public boolean requiresEquals = false;
+ }
+
public static class FlameOverlay {
@SerialEntry
public int flameHeight = 100;
diff --git a/src/main/java/de/hysky/skyblocker/mixins/SignEditScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/SignEditScreenMixin.java
new file mode 100644
index 00000000..6706db58
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/mixins/SignEditScreenMixin.java
@@ -0,0 +1,44 @@
+package de.hysky.skyblocker.mixins;
+
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.calculators.SignCalculator;
+import de.hysky.skyblocker.utils.Utils;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.ingame.AbstractSignEditScreen;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import java.util.Objects;
+
+@Mixin(AbstractSignEditScreen.class)
+public abstract class SignEditScreenMixin {
+ @Shadow
+ @Final
+ private String[] messages;
+
+ @Inject(method = "render", at = @At("HEAD"))
+ private void skyblocker$render(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
+ //if the sign is being used to enter number send it to the sign calculator
+ if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().uiAndVisuals.inputCalculator.enabled && Objects.equals(messages[1], "^^^^^^^^^^^^^^^")) {
+ SignCalculator.renderCalculator(context, messages[0], context.getScaledWindowWidth() / 2, 55);
+ }
+ }
+
+ @Inject(method = "finishEditing", at = @At("HEAD"))
+ private void skyblocker$finishEditing(CallbackInfo ci) {
+ //if the sign is being used to enter number get number from calculator for if maths has been done
+ if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().uiAndVisuals.inputCalculator.enabled && Objects.equals(messages[1], "^^^^^^^^^^^^^^^")) {
+ boolean isPrice = messages[2].contains("price");
+ String value = SignCalculator.getNewValue(isPrice);
+ if (value.length() >= 15) {
+ value = value.substring(0, 15);
+ }
+ messages[0] = value;
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/auction/EditBidPopup.java b/src/main/java/de/hysky/skyblocker/skyblock/auction/EditBidPopup.java
index 9d460803..f96e3231 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/auction/EditBidPopup.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/auction/EditBidPopup.java
@@ -1,5 +1,7 @@
package de.hysky.skyblocker.skyblock.auction;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.calculators.SignCalculator;
import de.hysky.skyblocker.utils.render.gui.AbstractPopupScreen;
import net.minecraft.block.entity.SignBlockEntity;
import net.minecraft.client.MinecraftClient;
@@ -9,7 +11,6 @@ import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import org.jetbrains.annotations.NotNull;
-import org.lwjgl.glfw.GLFW;
public class EditBidPopup extends AbstractPopupScreen {
private DirectionalLayoutWidget layout = DirectionalLayoutWidget.vertical();
@@ -55,6 +56,9 @@ public class EditBidPopup extends AbstractPopupScreen {
public void renderBackground(DrawContext context, int mouseX, int mouseY, float delta) {
super.renderBackground(context, mouseX, mouseY, delta);
drawPopupBackground(context, layout.getX(), layout.getY(), layout.getWidth(), layout.getHeight());
+ if (SkyblockerConfigManager.get().uiAndVisuals.inputCalculator.enabled) {
+ SignCalculator.renderCalculator(context, textFieldWidget.getText(), context.getScaledWindowWidth() / 2, textFieldWidget.getY() - 8);
+ }
}
private boolean isStringGood(String s) {
@@ -69,8 +73,13 @@ public class EditBidPopup extends AbstractPopupScreen {
}
private void done(ButtonWidget widget) {
- if (!isStringGood(textFieldWidget.getText().trim())) return;
- sendPacket(textFieldWidget.getText().trim());
+ if (SkyblockerConfigManager.get().uiAndVisuals.inputCalculator.enabled) {
+ if (!isStringGood(SignCalculator.getNewValue(false))) return;
+ sendPacket(SignCalculator.getNewValue(false));
+ } else {
+ if (!isStringGood(textFieldWidget.getText().trim())) return;
+ sendPacket(textFieldWidget.getText().trim());
+ }
this.close();
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/calculators/CalculatorCommand.java b/src/main/java/de/hysky/skyblocker/skyblock/calculators/CalculatorCommand.java
new file mode 100644
index 00000000..d103bcdd
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/calculators/CalculatorCommand.java
@@ -0,0 +1,56 @@
+package de.hysky.skyblocker.skyblock.calculators;
+
+import com.mojang.brigadier.Command;
+import com.mojang.brigadier.CommandDispatcher;
+import com.mojang.brigadier.arguments.StringArgumentType;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.Calculator;
+import de.hysky.skyblocker.utils.Constants;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.command.CommandRegistryAccess;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.text.NumberFormat;
+
+import static com.mojang.brigadier.arguments.StringArgumentType.getString;
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
+
+public class CalculatorCommand {
+ private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
+ private static final NumberFormat FORMATTER = NumberFormat.getInstance();
+
+ public static void init() {
+ ClientCommandRegistrationCallback.EVENT.register(CalculatorCommand::calculate);
+ }
+
+ private static void calculate(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) {
+ dispatcher.register(literal(SkyblockerMod.NAMESPACE)
+ .then(literal("calculate")
+ .then(argument("equation", StringArgumentType.greedyString())
+ .executes(context -> doCalculation(getString(context, "equation")))
+ )
+ )
+ );
+ }
+
+ private static int doCalculation(String calculation) {
+ MutableText text = Constants.PREFIX.get();
+ try {
+ text.append(Text.literal(FORMATTER.format(Calculator.calculate(calculation))).formatted(Formatting.GREEN));
+ } catch (UnsupportedOperationException e) {
+ text.append(Text.translatable("skyblocker.config.uiAndVisuals.inputCalculator.invalidEquation").formatted(Formatting.RED));
+ }
+
+ if (CLIENT == null || CLIENT.player == null) {
+ return 0;
+ }
+
+ CLIENT.player.sendMessage(text, false);
+ return Command.SINGLE_SUCCESS;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/calculators/SignCalculator.java b/src/main/java/de/hysky/skyblocker/skyblock/calculators/SignCalculator.java
new file mode 100644
index 00000000..dc51e48c
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/calculators/SignCalculator.java
@@ -0,0 +1,66 @@
+package de.hysky.skyblocker.skyblock.calculators;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Calculator;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.text.NumberFormat;
+
+public class SignCalculator {
+ private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
+ private static final NumberFormat FORMATTER = NumberFormat.getInstance();
+
+ private static String lastInput;
+ private static double output;
+
+ public static void renderCalculator(DrawContext context, String message, int renderX, int renderY) {
+ if (SkyblockerConfigManager.get().uiAndVisuals.inputCalculator.requiresEquals && !message.startsWith("=")) {
+ output = -1;
+ lastInput = message;
+ return;
+ }
+ if (message.startsWith("=")) {
+ message = message.substring(1);
+ }
+ //only update output if new input
+ if (!message.equals(lastInput)) { //
+ try {
+ output = Calculator.calculate(message);
+ } catch (Exception e) {
+ output = -1;
+ }
+ }
+
+ render(context, message, renderX, renderY);
+
+ lastInput = message;
+ }
+
+ public static String getNewValue(Boolean isPrice) {
+ if (output == -1) {
+ //if mode is not activated or just invalid equation return what the user typed in
+ return lastInput;
+ }
+
+ //price can except decimals and exponents
+ if (isPrice) {
+ return FORMATTER.format(output);
+ }
+ //amounts want an integer number so round
+ return Long.toString(Math.round(output));
+ }
+
+ private static void render(DrawContext context, String input, int renderX, int renderY) {
+ Text text;
+ if (output == -1) {
+ text = Text.translatable("skyblocker.config.uiAndVisuals.inputCalculator.invalidEquation").formatted(Formatting.RED);
+ } else {
+ text = Text.literal(input + " = " + FORMATTER.format(output)).formatted(Formatting.GREEN);
+ }
+
+ context.drawCenteredTextWithShadow(CLIENT.textRenderer, text, renderX, renderY, 0xFFFFFFFF);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/Calculator.java b/src/main/java/de/hysky/skyblocker/utils/Calculator.java
new file mode 100644
index 00000000..7b0baaf6
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/Calculator.java
@@ -0,0 +1,208 @@
+package de.hysky.skyblocker.utils;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class Calculator {
+ public enum TokenType {
+ NUMBER, OPERATOR, L_PARENTHESIS, R_PARENTHESIS
+ }
+
+ public static class Token {
+ public TokenType type;
+ String value;
+ int tokenLength;
+ }
+
+ private static final Pattern NUMBER_PATTERN = Pattern.compile("(\\d+\\.?\\d*)([sekmbt]?)");
+ private static final Map<String, Long> MAGNITUDE_VALUES = Map.of(
+ "s", 64L,
+ "e", 160L,
+ "k", 1_000L,
+ "m", 1_000_000L,
+ "b", 1_000_000_000L,
+ "t", 1_000_000_000_000L
+ );
+
+ private static List<Token> lex(String input) {
+ List<Token> tokens = new ArrayList<>();
+ input = input.replace(" ", "").toLowerCase().replace("x", "*");
+ int i = 0;
+ while (i < input.length()) {
+ Token token = new Token();
+ switch (input.charAt(i)) {
+ case '+', '-', '*', '/' -> {
+ token.type = TokenType.OPERATOR;
+ token.value = String.valueOf(input.charAt(i));
+ token.tokenLength = 1;
+ }
+
+ case '(' -> {
+ token.type = TokenType.L_PARENTHESIS;
+ token.value = String.valueOf(input.charAt(i));
+ token.tokenLength = 1;
+ //add implicit multiplication when there is a number before brackets
+ if (!tokens.isEmpty()) {
+ TokenType lastType = tokens.getLast().type;
+ if (lastType == TokenType.R_PARENTHESIS || lastType == TokenType.NUMBER) {
+ Token mutliplyToken = new Token();
+ mutliplyToken.type = TokenType.OPERATOR;
+ mutliplyToken.value = "*";
+ tokens.add(mutliplyToken);
+ }
+ }
+ }
+
+ case ')' -> {
+ token.type = TokenType.R_PARENTHESIS;
+ token.value = String.valueOf(input.charAt(i));
+ token.tokenLength = 1;
+ }
+
+ default -> {
+ token.type = TokenType.NUMBER;
+ Matcher numberMatcher = NUMBER_PATTERN.matcher(input.substring(i));
+ if (!numberMatcher.find()) {//invalid value to lex
+ throw new UnsupportedOperationException("invalid character");
+ }
+ int end = numberMatcher.end();
+ token.value = input.substring(i, i + end);
+ token.tokenLength = end;
+ }
+ }
+ tokens.add(token);
+
+ i += token.tokenLength;
+ }
+
+ return tokens;
+ }
+
+ /**
+ * This is an implementation of the shunting yard algorithm to convert the equation to reverse polish notation
+ *
+ * @param tokens equation in infix notation order
+ * @return equation in RPN order
+ */
+ private static List<Token> shunt(List<Token> tokens) {
+ Deque<Token> operatorStack = new ArrayDeque<>();
+ List<Token> outputQueue = new ArrayList<>();
+
+ for (Token shuntingToken : tokens) {
+ switch (shuntingToken.type) {
+ case NUMBER -> outputQueue.add(shuntingToken);
+ case OPERATOR -> {
+ int precedence = getPrecedence(shuntingToken.value);
+ while (!operatorStack.isEmpty()) {
+ Token leftToken = operatorStack.peek();
+ if (leftToken.type == TokenType.L_PARENTHESIS) {
+ break;
+ }
+ assert (leftToken.type == TokenType.OPERATOR);
+ int leftPrecedence = getPrecedence(leftToken.value);
+ if (leftPrecedence >= precedence) {
+ outputQueue.add(operatorStack.pop());
+ continue;
+ }
+ break;
+ }
+ operatorStack.push(shuntingToken);
+ }
+ case L_PARENTHESIS -> operatorStack.push(shuntingToken);
+ case R_PARENTHESIS -> {
+ while (true) {
+ if (operatorStack.isEmpty()) {
+ throw new UnsupportedOperationException("Unbalanced left parenthesis");
+ }
+ Token leftToken = operatorStack.pop();
+ if (leftToken.type == TokenType.L_PARENTHESIS) {
+ break;
+ }
+ outputQueue.add(leftToken);
+ }
+ }
+ }
+ }
+ //empty the operator stack
+ while (!operatorStack.isEmpty()) {
+ Token leftToken = operatorStack.pop();
+ if (leftToken.type == TokenType.L_PARENTHESIS) {
+ //technically unbalanced left parenthesis error but just assume they are close after the equation and ignore them from here
+ continue;
+ }
+ outputQueue.add(leftToken);
+ }
+
+ return outputQueue.stream().toList();
+ }
+
+ private static int getPrecedence(String operator) {
+ switch (operator) {
+ case "+", "-" -> {
+ return 0;
+ }
+ case "*", "/" -> {
+ return 1;
+ }
+ default -> throw new UnsupportedOperationException("Invalid operator");
+ }
+ }
+
+ /**
+ * @param tokens list of Tokens in reverse polish notation
+ * @return answer to equation
+ */
+ private static double evaluate(List<Token> tokens) {
+ Deque<Double> values = new ArrayDeque<>();
+ for (Token token : tokens) {
+ switch (token.type) {
+ case NUMBER -> values.push(calculateValue(token.value));
+ case OPERATOR -> {
+ double right = values.pop();
+ double left = values.pop();
+ switch (token.value) {
+ case "+" -> values.push(left + right);
+ case "-" -> values.push(left - right);
+ case "/" -> {
+ if (right == 0) {
+ throw new UnsupportedOperationException("Can not divide by 0");
+ }
+ values.push(left / right);
+ }
+ case "*" -> values.push(left * right);
+ }
+ }
+ case L_PARENTHESIS, R_PARENTHESIS -> throw new UnsupportedOperationException("Equation is not in RPN");
+ }
+ }
+ if (values.isEmpty()) {
+ throw new UnsupportedOperationException("Equation is empty");
+ }
+ return values.pop();
+ }
+
+ private static double calculateValue(String value) {
+ Matcher numberMatcher = NUMBER_PATTERN.matcher(value.toLowerCase());
+ if (!numberMatcher.matches()) {
+ throw new UnsupportedOperationException("Invalid number");
+ }
+ double number = Double.parseDouble(numberMatcher.group(1));
+ String magnitude = numberMatcher.group(2);
+
+ if (!magnitude.isEmpty()) {
+ if (!MAGNITUDE_VALUES.containsKey(magnitude)) {//its invalid if its another letter
+ throw new UnsupportedOperationException("Invalid magnitude");
+ }
+ number *= MAGNITUDE_VALUES.get(magnitude);
+ }
+
+ return number;
+ }
+
+ public static double calculate(String equation) {
+ //custom bit for replacing purse with its value
+ equation = equation.toLowerCase().replaceAll("p(urse)?", String.valueOf(Utils.getPurse()));
+ return evaluate(shunt(lex(equation)));
+ }
+}
diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json
index 6d2433c0..2549f80e 100644
--- a/src/main/resources/assets/skyblocker/lang/en_us.json
+++ b/src/main/resources/assets/skyblocker/lang/en_us.json
@@ -536,6 +536,13 @@
"skyblocker.config.uiAndVisuals.flameOverlay.flameOpacity": "Flame Opacity",
"skyblocker.config.uiAndVisuals.flameOverlay.flameOpacity.@Tooltip": "100% default opacity\n0% off",
+ "skyblocker.config.uiAndVisuals.inputCalculator": "Input Calculator",
+ "skyblocker.config.uiAndVisuals.inputCalculator.enabled": "Enable Sign Calculator",
+ "skyblocker.config.uiAndVisuals.inputCalculator.enabled.@Tooltip": "Enables the ability for you to do calculations when inputting values such as price for the ah.\n Key:\n S = 64\n E = 160\n K = 1,000\n M = 1,000,000\n B = 1,000,000,000\n\n purse/P = current purse value",
+ "skyblocker.config.uiAndVisuals.inputCalculator.requiresEquals": "Only show with \"=\".",
+ "skyblocker.config.uiAndVisuals.inputCalculator.requiresEquals.@Tooltip": "Only show the calculator when the message start with \"=\".",
+ "skyblocker.config.uiAndVisuals.inputCalculator.invalidEquation": "Invalid Equation",
+
"skyblocker.config.uiAndVisuals.itemCooldown": "Item Cooldown",
"skyblocker.config.uiAndVisuals.itemCooldown.enableItemCooldowns": "Enable Item Cooldown",
diff --git a/src/main/resources/skyblocker.mixins.json b/src/main/resources/skyblocker.mixins.json
index 0032a557..828cc206 100644
--- a/src/main/resources/skyblocker.mixins.json
+++ b/src/main/resources/skyblocker.mixins.json
@@ -32,6 +32,7 @@
"PlayerSkinTextureMixin",
"RenderFishMixin",
"ScoreboardMixin",
+ "SignEditScreenMixin",
"SocialInteractionsPlayerListWidgetMixin",
"WindowMixin",
"WorldRendererMixin",
diff --git a/src/test/java/de/hysky/skyblocker/utils/CalculatorTest.java b/src/test/java/de/hysky/skyblocker/utils/CalculatorTest.java
new file mode 100644
index 00000000..c29efdf2
--- /dev/null
+++ b/src/test/java/de/hysky/skyblocker/utils/CalculatorTest.java
@@ -0,0 +1,31 @@
+package de.hysky.skyblocker.utils;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class CalculatorTest {
+ @Test
+ void testShorthands() {
+ Assertions.assertEquals(Calculator.calculate("1k"), 1000);
+ Assertions.assertEquals(Calculator.calculate("0.12k"), 120);
+ Assertions.assertEquals(Calculator.calculate("1k + 0.12k"), 1120);
+ Assertions.assertEquals(Calculator.calculate("1 + 1s + 1k + 1m + 1b"), 1001001065);
+ }
+
+ @Test
+ void testPrecedence() {
+ Assertions.assertEquals(Calculator.calculate("5 + 2 * 2"), 9);
+ Assertions.assertEquals(Calculator.calculate("5 - 2 / 2"), 4);
+ Assertions.assertEquals(Calculator.calculate("5 * (1 + 2)"), 15);
+ }
+
+ @Test
+ void testImplicitMultiplication() {
+ Assertions.assertEquals(Calculator.calculate("5(2 + 2)"), 20);
+ }
+
+ @Test
+ void testImplicitClosingParenthesis() {
+ Assertions.assertEquals(Calculator.calculate("5(2 + 2"), 20);
+ }
+}