aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-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
8 files changed, 421 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)));
+ }
+}