From 42c7732864cd28873dceaab27a7a65a71ed109f0 Mon Sep 17 00:00:00 2001 From: Aaron <51387595+AzureAaron@users.noreply.github.com> Date: Mon, 11 Sep 2023 02:24:37 -0400 Subject: Dungeon Chest Profit Calculator! --- .../skyblocker/config/SkyblockerConfig.java | 9 ++ .../skyblocker/mixin/HandledScreenMixin.java | 20 +++ .../skyblock/dungeon/DungeonChestProfit.java | 171 +++++++++++++++++++++ .../skyblocker/skyblock/item/PriceInfoTooltip.java | 8 + 4 files changed, 208 insertions(+) create mode 100644 src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java (limited to 'src/main/java/me') diff --git a/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java b/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java index 5e729f19..b3b298a3 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java +++ b/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java @@ -437,6 +437,8 @@ public class SkyblockerConfig implements ConfigData { public static class Dungeons { @ConfigEntry.Gui.CollapsibleObject public SecretWaypoints secretWaypoints = new SecretWaypoints(); + @ConfigEntry.Gui.CollapsibleObject + public DungeonChestProfit dungeonChestProfit = new DungeonChestProfit(); @ConfigEntry.Gui.Tooltip() public boolean croesusHelper = true; public boolean enableMap = true; @@ -474,6 +476,13 @@ public class SkyblockerConfig implements ConfigData { @ConfigEntry.Gui.Tooltip() public boolean enableDefaultWaypoints = true; } + + public static class DungeonChestProfit { + @ConfigEntry.Gui.Tooltip + public boolean enableProfitCalculator = true; + @ConfigEntry.Gui.Tooltip + public boolean includeKismet = false; + } public static class LividColor { @ConfigEntry.Gui.Tooltip() diff --git a/src/main/java/me/xmrvizzy/skyblocker/mixin/HandledScreenMixin.java b/src/main/java/me/xmrvizzy/skyblocker/mixin/HandledScreenMixin.java index af6f6aa7..916fab03 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/mixin/HandledScreenMixin.java +++ b/src/main/java/me/xmrvizzy/skyblocker/mixin/HandledScreenMixin.java @@ -3,6 +3,7 @@ package me.xmrvizzy.skyblocker.mixin; import me.xmrvizzy.skyblocker.SkyblockerMod; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; import me.xmrvizzy.skyblocker.skyblock.BackpackPreview; +import me.xmrvizzy.skyblocker.skyblock.dungeon.DungeonChestProfit; import me.xmrvizzy.skyblocker.skyblock.experiment.ChronomatronSolver; import me.xmrvizzy.skyblocker.skyblock.experiment.ExperimentSolver; import me.xmrvizzy.skyblocker.skyblock.experiment.SuperpairsSolver; @@ -16,10 +17,13 @@ import net.minecraft.client.gui.screen.ingame.HandledScreen; import net.minecraft.inventory.SimpleInventory; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.screen.GenericContainerScreenHandler; +import net.minecraft.screen.ScreenHandlerType; import net.minecraft.screen.slot.Slot; import net.minecraft.screen.slot.SlotActionType; import net.minecraft.text.Text; import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; @@ -30,6 +34,9 @@ import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; + import java.util.Map; @Mixin(HandledScreen.class) @@ -103,4 +110,17 @@ public abstract class HandledScreenMixin extends Screen { } } } + + @WrapOperation(method = "drawForeground", at = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;title:Lnet/minecraft/text/Text;", opcode = Opcodes.GETFIELD)) + private Text skyblocker$modifyScreenTitle(HandledScreen handledScreen, Operation operation) { + Text title = handledScreen.getTitle(); + + if (Utils.isOnSkyblock() && handledScreen.getScreenHandler().getType().equals(ScreenHandlerType.GENERIC_9X6)) { + GenericContainerScreenHandler gcsHandler = (GenericContainerScreenHandler) handledScreen.getScreenHandler(); + + return DungeonChestProfit.getChestProfit(gcsHandler, title, this.client); + } + + return title; + } } diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java new file mode 100644 index 00000000..33521ae2 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java @@ -0,0 +1,171 @@ +package me.xmrvizzy.skyblocker.skyblock.dungeon; + +import java.text.DecimalFormat; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonObject; + +import it.unimi.dsi.fastutil.ints.IntBooleanPair; +import me.xmrvizzy.skyblocker.config.SkyblockerConfig; +import me.xmrvizzy.skyblocker.skyblock.item.PriceInfoTooltip; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.GenericContainerScreenHandler; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.MutableText; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +public class DungeonChestProfit { + private static final Logger LOGGER = LoggerFactory.getLogger(DungeonChestProfit.class); + private static final Pattern ESSENCE_PATTERN = Pattern.compile("(?[A-Za-z]+) Essence x(?[0-9]+)"); + private static final DecimalFormat FORMATTER = new DecimalFormat("#,###"); + private static final Style GRAY = Style.EMPTY.withColor(Formatting.GRAY); + private static final Style GOLD = Style.EMPTY.withColor(0xd4a72c); + private static final Style DARK_GREEN = Style.EMPTY.withColor(Formatting.DARK_GREEN); + private static final Style DARK_RED = Style.EMPTY.withColor(Formatting.DARK_RED); + + public static Text getChestProfit(GenericContainerScreenHandler handler, Text title, MinecraftClient client) { + try { + if (SkyblockerConfig.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator && isDungeonChest(title.getString())) { + int profit = 0; + boolean hasIncompleteData = false, usedKismet = false; + List slots = handler.slots.subList(0, handler.getRows() * 9); + + //If the item stack for the "Open Reward Chest" button or the kismet button hasn't been sent to the client yet + if (slots.get(31).getStack().isEmpty() || slots.get(50).getStack().isEmpty()) return title; + + for (Slot slot : slots) { + ItemStack stack = slot.getStack(); + + if (!stack.isEmpty()) { + String name = stack.getName().getString(); + String id = PriceInfoTooltip.getInternalNameFromNBT(stack, false); + + //Regular item price + if (id != null) { + IntBooleanPair priceData = getItemPrice(id); + + if (priceData.rightBoolean() == false) hasIncompleteData = true; + + //Add the item price to the profit + profit += priceData.leftInt(); + + continue; + } + + //Essence price + if (name.contains("Essence")) { + Matcher matcher = ESSENCE_PATTERN.matcher(name); + + if (matcher.matches()) { + String type = matcher.group("type"); + int amount = Integer.parseInt(matcher.group("amount")); + + IntBooleanPair priceData = getItemPrice(("ESSENCE_" + type).toUpperCase()); + + if (priceData.rightBoolean() == false) hasIncompleteData = true; + + //Add the price of the essence to the profit + profit += priceData.leftInt() * amount; + + continue; + } + } + + //Determine the cost of the chest + if (name.contains("Open Reward Chest")) { + String foundString = searchLoreFor(stack, client, "Coins"); + + //Incase we're searching the free chest + if (!StringUtils.isBlank(foundString)) { + profit -= Integer.parseInt(foundString.replaceAll("[^0-9]", "")); + } + + continue; + } + + //Determine if a kismet was used or not + if (name.contains("Reroll Chest")) { + usedKismet = !StringUtils.isBlank(searchLoreFor(stack, client, "You already rerolled a chest!")); + + continue; + } + } + } + + if (SkyblockerConfig.get().locations.dungeons.dungeonChestProfit.includeKismet && usedKismet) { + IntBooleanPair kismetPriceData = getItemPrice("KISMET_FEATHER"); + + if (kismetPriceData.rightBoolean() == false) hasIncompleteData = true; + + profit -= kismetPriceData.leftInt(); + } + + return ((MutableText) Text.literal(title.getString())).append(getProfitText(profit, hasIncompleteData)); + } + } catch (Exception e) { + LOGGER.error("[Skyblocker Profit Calculator] Failed to calculate dungeon chest profit! ", e); + } + + return title; + } + + /** + * + * @return An {@link IntBooleanPair} with the {@code left int} representing the item's price, and the {@code right boolean} indicating if the price + * was based on complete data. + */ + private static IntBooleanPair getItemPrice(String id) { + JsonObject bazaarPrices = PriceInfoTooltip.getBazaarPrices(); + JsonObject lbinPrices = PriceInfoTooltip.getLBINPrices(); + + if (bazaarPrices == null || lbinPrices == null) return IntBooleanPair.of(0, false); + + if (bazaarPrices.has(id)) { + JsonObject item = bazaarPrices.get(id).getAsJsonObject(); + boolean isPriceNull = item.get("sellPrice").isJsonNull(); + + return IntBooleanPair.of(isPriceNull ? 0 : (int) item.get("sellPrice").getAsDouble(), !isPriceNull); + } + + if (lbinPrices.has(id)) { + return IntBooleanPair.of((int) lbinPrices.get(id).getAsDouble(), true); + } + + return IntBooleanPair.of(0, false); + } + + /** + * Searches for a specific string of characters in the name and lore of an item + */ + private static String searchLoreFor(ItemStack stack, MinecraftClient client, String searchString) { + List lore = stack.getTooltip(client.player, TooltipContext.BASIC); + + for (int i = 0; i < lore.size(); i++) { + String line = lore.get(i).getString(); + + if (line.contains(searchString)) { + return line; + } + } + + return null; + } + + private static boolean isDungeonChest(String name) { + return name.equals("Wood Chest") || name.equals("Gold Chest") || name.equals("Diamond Chest") || name.equals("Emerald Chest") || name.equals("Obsidian Chest") || name.equals("Bedrock Chest"); + } + + private static Text getProfitText(int profit, boolean hasIncompleteData) { + return (profit == 0) ? Text.literal(" " + FORMATTER.format(profit)).setStyle(hasIncompleteData ? GOLD : GRAY) : (profit > 0) ? Text.literal(" +" + FORMATTER.format(profit)).setStyle(hasIncompleteData ? GOLD : DARK_GREEN) : Text.literal(" " + FORMATTER.format(profit)).setStyle(hasIncompleteData ? GOLD : DARK_RED); + } +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java index 60c39ca6..eb3cb08d 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java @@ -409,6 +409,14 @@ public class PriceInfoTooltip { return null; } } + + public static JsonObject getBazaarPrices() { + return bazaarPricesJson; + } + + public static JsonObject getLBINPrices() { + return lowestPricesJson; + } static { apiAddresses = new HashMap<>(); -- cgit From 024df0a5ddb6fa617b303eab9bf95504491fd4f0 Mon Sep 17 00:00:00 2001 From: Aaron <51387595+AzureAaron@users.noreply.github.com> Date: Mon, 11 Sep 2023 16:24:54 -0400 Subject: Load lbin & bazaar prices when profit calc is enabled --- .../java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/main/java/me') diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java index eb3cb08d..44ce7339 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/PriceInfoTooltip.java @@ -376,10 +376,10 @@ public class PriceInfoTooltip { futureList.add(CompletableFuture.runAsync(() -> threeDayAvgPricesJson = downloadPrices("3 day avg"))); } } - if (SkyblockerConfig.get().general.itemTooltip.enableLowestBIN) + if (SkyblockerConfig.get().general.itemTooltip.enableLowestBIN || SkyblockerConfig.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator) futureList.add(CompletableFuture.runAsync(() -> lowestPricesJson = downloadPrices("lowest bins"))); - if (SkyblockerConfig.get().general.itemTooltip.enableBazaarPrice) + if (SkyblockerConfig.get().general.itemTooltip.enableBazaarPrice || SkyblockerConfig.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator) futureList.add(CompletableFuture.runAsync(() -> bazaarPricesJson = downloadPrices("bazaar"))); if (SkyblockerConfig.get().general.itemTooltip.enableNPCPrice && npcPricesJson == null) -- cgit From ec8215a204c144e6c83e9a56efc98e8d2b15cb85 Mon Sep 17 00:00:00 2001 From: Aaron <51387595+AzureAaron@users.noreply.github.com> Date: Tue, 12 Sep 2023 18:06:07 -0400 Subject: Change warning stuff to dark blue --- .../me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java | 4 ++-- src/main/resources/assets/skyblocker/lang/en_us.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/main/java/me') diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java index 33521ae2..ad958503 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java @@ -29,7 +29,7 @@ public class DungeonChestProfit { private static final Pattern ESSENCE_PATTERN = Pattern.compile("(?[A-Za-z]+) Essence x(?[0-9]+)"); private static final DecimalFormat FORMATTER = new DecimalFormat("#,###"); private static final Style GRAY = Style.EMPTY.withColor(Formatting.GRAY); - private static final Style GOLD = Style.EMPTY.withColor(0xd4a72c); + private static final Style DARK_BLUE = Style.EMPTY.withColor(Formatting.DARK_BLUE); private static final Style DARK_GREEN = Style.EMPTY.withColor(Formatting.DARK_GREEN); private static final Style DARK_RED = Style.EMPTY.withColor(Formatting.DARK_RED); @@ -166,6 +166,6 @@ public class DungeonChestProfit { } private static Text getProfitText(int profit, boolean hasIncompleteData) { - return (profit == 0) ? Text.literal(" " + FORMATTER.format(profit)).setStyle(hasIncompleteData ? GOLD : GRAY) : (profit > 0) ? Text.literal(" +" + FORMATTER.format(profit)).setStyle(hasIncompleteData ? GOLD : DARK_GREEN) : Text.literal(" " + FORMATTER.format(profit)).setStyle(hasIncompleteData ? GOLD : DARK_RED); + return (profit == 0) ? Text.literal(" " + FORMATTER.format(profit)).setStyle(hasIncompleteData ? DARK_BLUE : GRAY) : (profit > 0) ? Text.literal(" +" + FORMATTER.format(profit)).setStyle(hasIncompleteData ? DARK_BLUE : DARK_GREEN) : Text.literal(" " + FORMATTER.format(profit)).setStyle(hasIncompleteData ? DARK_BLUE : DARK_RED); } } diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 28e1f85b..d3511dff 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -223,7 +223,7 @@ "text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enableDefaultWaypoints.@Tooltip" : "This includes all waypoints that do not belong to a category.", "text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit": "Dungeon Chest Profit Calculator", "text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.enableProfitCalculator": "Enable Profit Calculator", - "text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.enableProfitCalculator.@Tooltip": "Displays the profit of a dungeon chest in the chest screen's title.\nGreen if there's profit.\nRed if there isn't profit.\nGray if you don't gain or lose anything.\nGold if calculations were based on incomplete data.", + "text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.enableProfitCalculator.@Tooltip": "Displays the profit of a dungeon chest in the chest screen's title.\nGreen if there's profit.\nRed if there isn't profit.\nGray if you don't gain or lose anything.\nBlue if calculations were based on incomplete data.", "text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.includeKismet": "Include Kismet Price", "text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.includeKismet.@Tooltip": "When enabled, if you used a kismet the price of one will be subtracted from the profit", "text.autoconfig.skyblocker.option.locations.dungeons.croesusHelper": "Croesus Helper", -- cgit From 942d3a47c6fea5c5dcd2db8e2e30ada628afbc9f Mon Sep 17 00:00:00 2001 From: Aaron <51387595+AzureAaron@users.noreply.github.com> Date: Tue, 12 Sep 2023 18:39:54 -0400 Subject: Essence Toggle --- src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java | 2 ++ .../me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java | 2 +- src/main/resources/assets/skyblocker/lang/en_us.json | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) (limited to 'src/main/java/me') diff --git a/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java b/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java index b3b298a3..5107833c 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java +++ b/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java @@ -482,6 +482,8 @@ public class SkyblockerConfig implements ConfigData { public boolean enableProfitCalculator = true; @ConfigEntry.Gui.Tooltip public boolean includeKismet = false; + @ConfigEntry.Gui.Tooltip + public boolean includeEssence = true; } public static class LividColor { diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java index ad958503..84d88ecd 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java @@ -63,7 +63,7 @@ public class DungeonChestProfit { } //Essence price - if (name.contains("Essence")) { + if (name.contains("Essence") && SkyblockerConfig.get().locations.dungeons.dungeonChestProfit.includeEssence) { Matcher matcher = ESSENCE_PATTERN.matcher(name); if (matcher.matches()) { diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index d3511dff..ffcb4e81 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -226,6 +226,8 @@ "text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.enableProfitCalculator.@Tooltip": "Displays the profit of a dungeon chest in the chest screen's title.\nGreen if there's profit.\nRed if there isn't profit.\nGray if you don't gain or lose anything.\nBlue if calculations were based on incomplete data.", "text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.includeKismet": "Include Kismet Price", "text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.includeKismet.@Tooltip": "When enabled, if you used a kismet the price of one will be subtracted from the profit", + "text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.includeEssence": "Include Essence", + "text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.includeEssence.@Tooltip": "Disabling is **not recommended** if you're a forgetful person.", "text.autoconfig.skyblocker.option.locations.dungeons.croesusHelper": "Croesus Helper", "text.autoconfig.skyblocker.option.locations.dungeons.croesusHelper.@Tooltip": "Gray out chests that have already been opened.", "text.autoconfig.skyblocker.option.locations.dungeons.enableMap": "Enable Map", -- cgit From e4757b16aca9f67621a5beaad3a55aeb15401d9c Mon Sep 17 00:00:00 2001 From: Kevinthegreat <92656833+kevinthegreat1@users.noreply.github.com> Date: Tue, 12 Sep 2023 20:16:10 -0400 Subject: Refactor DungeonChestProfit --- .../java/me/xmrvizzy/skyblocker/SkyblockerMod.java | 6 +- .../skyblocker/mixin/HandledScreenMixin.java | 13 --- .../skyblocker/mixin/accessor/ScreenAccessor.java | 14 +++ .../skyblock/dungeon/DungeonChestProfit.java | 121 ++++++++++----------- src/main/resources/skyblocker.mixins.json | 1 + 5 files changed, 76 insertions(+), 79 deletions(-) create mode 100644 src/main/java/me/xmrvizzy/skyblocker/mixin/accessor/ScreenAccessor.java (limited to 'src/main/java/me') diff --git a/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java b/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java index cdf78d73..bb08b99a 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java +++ b/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java @@ -4,10 +4,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; import me.xmrvizzy.skyblocker.skyblock.*; -import me.xmrvizzy.skyblocker.skyblock.dungeon.DungeonBlaze; -import me.xmrvizzy.skyblocker.skyblock.dungeon.DungeonMap; -import me.xmrvizzy.skyblocker.skyblock.dungeon.LividColor; -import me.xmrvizzy.skyblocker.skyblock.dungeon.TicTacToe; +import me.xmrvizzy.skyblocker.skyblock.dungeon.*; import me.xmrvizzy.skyblocker.skyblock.dungeon.secrets.DungeonSecrets; import me.xmrvizzy.skyblocker.skyblock.dwarven.DwarvenHud; import me.xmrvizzy.skyblocker.skyblock.item.*; @@ -92,6 +89,7 @@ public class SkyblockerMod implements ClientModInitializer { DungeonMap.init(); DungeonSecrets.init(); DungeonBlaze.init(); + DungeonChestProfit.init(); TheRift.init(); TitleContainer.init(); ScreenMaster.init(); diff --git a/src/main/java/me/xmrvizzy/skyblocker/mixin/HandledScreenMixin.java b/src/main/java/me/xmrvizzy/skyblocker/mixin/HandledScreenMixin.java index 916fab03..eccd63e6 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/mixin/HandledScreenMixin.java +++ b/src/main/java/me/xmrvizzy/skyblocker/mixin/HandledScreenMixin.java @@ -110,17 +110,4 @@ public abstract class HandledScreenMixin extends Screen { } } } - - @WrapOperation(method = "drawForeground", at = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;title:Lnet/minecraft/text/Text;", opcode = Opcodes.GETFIELD)) - private Text skyblocker$modifyScreenTitle(HandledScreen handledScreen, Operation operation) { - Text title = handledScreen.getTitle(); - - if (Utils.isOnSkyblock() && handledScreen.getScreenHandler().getType().equals(ScreenHandlerType.GENERIC_9X6)) { - GenericContainerScreenHandler gcsHandler = (GenericContainerScreenHandler) handledScreen.getScreenHandler(); - - return DungeonChestProfit.getChestProfit(gcsHandler, title, this.client); - } - - return title; - } } diff --git a/src/main/java/me/xmrvizzy/skyblocker/mixin/accessor/ScreenAccessor.java b/src/main/java/me/xmrvizzy/skyblocker/mixin/accessor/ScreenAccessor.java new file mode 100644 index 00000000..6a671601 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/mixin/accessor/ScreenAccessor.java @@ -0,0 +1,14 @@ +package me.xmrvizzy.skyblocker.mixin.accessor; + +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(Screen.class) +public interface ScreenAccessor { + @Accessor + @Mutable + void setTitle(Text title); +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java index 84d88ecd..beed0cd8 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonChestProfit.java @@ -1,28 +1,30 @@ package me.xmrvizzy.skyblocker.skyblock.dungeon; -import java.text.DecimalFormat; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.google.gson.JsonObject; - import it.unimi.dsi.fastutil.ints.IntBooleanPair; import me.xmrvizzy.skyblocker.config.SkyblockerConfig; +import me.xmrvizzy.skyblocker.mixin.accessor.ScreenAccessor; import me.xmrvizzy.skyblocker.skyblock.item.PriceInfoTooltip; +import me.xmrvizzy.skyblocker.utils.Utils; +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; import net.minecraft.client.item.TooltipContext; import net.minecraft.item.ItemStack; import net.minecraft.screen.GenericContainerScreenHandler; +import net.minecraft.screen.ScreenHandlerType; import net.minecraft.screen.slot.Slot; -import net.minecraft.text.MutableText; import net.minecraft.text.Style; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.DecimalFormat; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class DungeonChestProfit { private static final Logger LOGGER = LoggerFactory.getLogger(DungeonChestProfit.class); @@ -32,139 +34,134 @@ public class DungeonChestProfit { private static final Style DARK_BLUE = Style.EMPTY.withColor(Formatting.DARK_BLUE); private static final Style DARK_GREEN = Style.EMPTY.withColor(Formatting.DARK_GREEN); private static final Style DARK_RED = Style.EMPTY.withColor(Formatting.DARK_RED); - + + public static void init() { + ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> ScreenEvents.afterTick(screen).register(screen1 -> { + if (Utils.isOnSkyblock() && screen instanceof GenericContainerScreen genericContainerScreen && genericContainerScreen.getScreenHandler().getType() == ScreenHandlerType.GENERIC_9X6) { + ((ScreenAccessor) screen).setTitle(getChestProfit(genericContainerScreen.getScreenHandler(), screen.getTitle(), client)); + } + })); + } + public static Text getChestProfit(GenericContainerScreenHandler handler, Text title, MinecraftClient client) { try { - if (SkyblockerConfig.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator && isDungeonChest(title.getString())) { + if (SkyblockerConfig.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator && isDungeonChest(title.getString())) { int profit = 0; boolean hasIncompleteData = false, usedKismet = false; List slots = handler.slots.subList(0, handler.getRows() * 9); - + //If the item stack for the "Open Reward Chest" button or the kismet button hasn't been sent to the client yet if (slots.get(31).getStack().isEmpty() || slots.get(50).getStack().isEmpty()) return title; - + for (Slot slot : slots) { ItemStack stack = slot.getStack(); - + if (!stack.isEmpty()) { String name = stack.getName().getString(); String id = PriceInfoTooltip.getInternalNameFromNBT(stack, false); - + //Regular item price - if (id != null) { + if (id != null) { IntBooleanPair priceData = getItemPrice(id); - - if (priceData.rightBoolean() == false) hasIncompleteData = true; - + + if (!priceData.rightBoolean()) hasIncompleteData = true; + //Add the item price to the profit profit += priceData.leftInt(); continue; } - + //Essence price if (name.contains("Essence") && SkyblockerConfig.get().locations.dungeons.dungeonChestProfit.includeEssence) { Matcher matcher = ESSENCE_PATTERN.matcher(name); - + if (matcher.matches()) { String type = matcher.group("type"); int amount = Integer.parseInt(matcher.group("amount")); - + IntBooleanPair priceData = getItemPrice(("ESSENCE_" + type).toUpperCase()); - - if (priceData.rightBoolean() == false) hasIncompleteData = true; - + + if (!priceData.rightBoolean()) hasIncompleteData = true; + //Add the price of the essence to the profit profit += priceData.leftInt() * amount; - + continue; } } - + //Determine the cost of the chest if (name.contains("Open Reward Chest")) { String foundString = searchLoreFor(stack, client, "Coins"); - + //Incase we're searching the free chest if (!StringUtils.isBlank(foundString)) { profit -= Integer.parseInt(foundString.replaceAll("[^0-9]", "")); } - + continue; } - + //Determine if a kismet was used or not if (name.contains("Reroll Chest")) { usedKismet = !StringUtils.isBlank(searchLoreFor(stack, client, "You already rerolled a chest!")); - - continue; } } } - + if (SkyblockerConfig.get().locations.dungeons.dungeonChestProfit.includeKismet && usedKismet) { IntBooleanPair kismetPriceData = getItemPrice("KISMET_FEATHER"); - - if (kismetPriceData.rightBoolean() == false) hasIncompleteData = true; - + + if (!kismetPriceData.rightBoolean()) hasIncompleteData = true; + profit -= kismetPriceData.leftInt(); } - - return ((MutableText) Text.literal(title.getString())).append(getProfitText(profit, hasIncompleteData)); + + return Text.literal(title.getString()).append(getProfitText(profit, hasIncompleteData)); } } catch (Exception e) { LOGGER.error("[Skyblocker Profit Calculator] Failed to calculate dungeon chest profit! ", e); } - + return title; } - + /** - * * @return An {@link IntBooleanPair} with the {@code left int} representing the item's price, and the {@code right boolean} indicating if the price * was based on complete data. */ private static IntBooleanPair getItemPrice(String id) { JsonObject bazaarPrices = PriceInfoTooltip.getBazaarPrices(); JsonObject lbinPrices = PriceInfoTooltip.getLBINPrices(); - + if (bazaarPrices == null || lbinPrices == null) return IntBooleanPair.of(0, false); - + if (bazaarPrices.has(id)) { JsonObject item = bazaarPrices.get(id).getAsJsonObject(); boolean isPriceNull = item.get("sellPrice").isJsonNull(); - + return IntBooleanPair.of(isPriceNull ? 0 : (int) item.get("sellPrice").getAsDouble(), !isPriceNull); } - + if (lbinPrices.has(id)) { return IntBooleanPair.of((int) lbinPrices.get(id).getAsDouble(), true); } - + return IntBooleanPair.of(0, false); } - + /** * Searches for a specific string of characters in the name and lore of an item */ private static String searchLoreFor(ItemStack stack, MinecraftClient client, String searchString) { - List lore = stack.getTooltip(client.player, TooltipContext.BASIC); - - for (int i = 0; i < lore.size(); i++) { - String line = lore.get(i).getString(); - - if (line.contains(searchString)) { - return line; - } - } - - return null; + return stack.getTooltip(client.player, TooltipContext.BASIC).stream().map(Text::getString).filter(line -> line.contains(searchString)).findAny().orElse(null); } - + private static boolean isDungeonChest(String name) { return name.equals("Wood Chest") || name.equals("Gold Chest") || name.equals("Diamond Chest") || name.equals("Emerald Chest") || name.equals("Obsidian Chest") || name.equals("Bedrock Chest"); } - + private static Text getProfitText(int profit, boolean hasIncompleteData) { return (profit == 0) ? Text.literal(" " + FORMATTER.format(profit)).setStyle(hasIncompleteData ? DARK_BLUE : GRAY) : (profit > 0) ? Text.literal(" +" + FORMATTER.format(profit)).setStyle(hasIncompleteData ? DARK_BLUE : DARK_GREEN) : Text.literal(" " + FORMATTER.format(profit)).setStyle(hasIncompleteData ? DARK_BLUE : DARK_RED); } diff --git a/src/main/resources/skyblocker.mixins.json b/src/main/resources/skyblocker.mixins.json index 98759aac..9d2ce7c2 100644 --- a/src/main/resources/skyblocker.mixins.json +++ b/src/main/resources/skyblocker.mixins.json @@ -28,6 +28,7 @@ "accessor.HandledScreenAccessor", "accessor.PlayerListHudAccessor", "accessor.RecipeBookWidgetAccessor", + "accessor.ScreenAccessor", "accessor.WorldRendererAccessor" ], "injectors": { -- cgit