From af13108acd4926bc5e2a15a3d40776a62a5d311e Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Sat, 23 Nov 2024 10:22:03 +0300 Subject: [PATCH 01/18] Add chat events --- .../hysky/skyblocker/events/ChatEvents.java | 39 +++++++++++++++++++ .../mixins/MessageHandlerMixin.java | 19 +++++++++ src/main/resources/skyblocker.mixins.json | 1 + 3 files changed, 59 insertions(+) create mode 100644 src/main/java/de/hysky/skyblocker/events/ChatEvents.java create mode 100644 src/main/java/de/hysky/skyblocker/mixins/MessageHandlerMixin.java diff --git a/src/main/java/de/hysky/skyblocker/events/ChatEvents.java b/src/main/java/de/hysky/skyblocker/events/ChatEvents.java new file mode 100644 index 0000000000..3753770fcd --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/events/ChatEvents.java @@ -0,0 +1,39 @@ +package de.hysky.skyblocker.events; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.text.Text; + +@Environment(EnvType.CLIENT) +public class ChatEvents { + /** + * This will be called when a game message is received, cancelled or not. + */ + public static final Event RECEIVE_TEXT = EventFactory.createArrayBacked(ChatTextEvent.class, listeners -> message -> { + for (ChatTextEvent listener : listeners) { + listener.onMessage(message); + } + }); + + /** + * This will be called when a game message is received, cancelled or not. + * This method is called with the result of {@link Text#getString()} to avoid each listener having to call it. + */ + public static final Event RECEIVE_STRING = EventFactory.createArrayBacked(ChatStringEvent.class, listeners -> message -> { + for (ChatStringEvent listener : listeners) { + listener.onMessage(message); + } + }); + + @FunctionalInterface + public interface ChatTextEvent { + void onMessage(Text message); + } + + @FunctionalInterface + public interface ChatStringEvent { + void onMessage(String message); + } +} diff --git a/src/main/java/de/hysky/skyblocker/mixins/MessageHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/MessageHandlerMixin.java new file mode 100644 index 0000000000..60a73e9995 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/mixins/MessageHandlerMixin.java @@ -0,0 +1,19 @@ +package de.hysky.skyblocker.mixins; + +import de.hysky.skyblocker.events.ChatEvents; +import net.minecraft.client.network.message.MessageHandler; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = MessageHandler.class, priority = 600) //Inject before the default of 1000 so it bypasses fabric's injections +public class MessageHandlerMixin { + @Inject(method = "onGameMessage", at = @At("HEAD")) + private void skyblocker$monitorGameMessage(Text message, boolean overlay, CallbackInfo ci) { + if (overlay) return; //Can add overlay-specific events in the future or incorporate it into the existing events. For now, it's not necessary. + ChatEvents.RECEIVE_TEXT.invoker().onMessage(message); + ChatEvents.RECEIVE_STRING.invoker().onMessage(message.getString()); + } +} diff --git a/src/main/resources/skyblocker.mixins.json b/src/main/resources/skyblocker.mixins.json index bf32e11acc..7943eb879c 100644 --- a/src/main/resources/skyblocker.mixins.json +++ b/src/main/resources/skyblocker.mixins.json @@ -27,6 +27,7 @@ "InventoryScreenMixin", "ItemStackMixin", "LeverBlockMixin", + "MessageHandlerMixin", "MinecraftClientMixin", "MouseMixin", "MushroomPlantBlockMixin", From 536476595a60a3a50a5843fac3bdc4095a425b3c Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:36:59 +0300 Subject: [PATCH 02/18] Add powder mining tracker --- .../skyblock/dwarven/PowderMiningTracker.java | 223 ++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java new file mode 100644 index 0000000000..484306ca23 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java @@ -0,0 +1,223 @@ +package de.hysky.skyblocker.skyblock.dwarven; + +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.events.ChatEvents; +import de.hysky.skyblocker.events.HudRenderEvents; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.Location; +import de.hysky.skyblocker.utils.Utils; +import it.unimi.dsi.fastutil.doubles.DoubleBooleanPair; +import it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.minecraft.client.MinecraftClient; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.text.NumberFormat; +import java.util.List; +import java.util.regex.Pattern; + +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; + +public class PowderMiningTracker { + private static final Pattern GEMSTONE_SYMBOLS = Pattern.compile("[α☘☠✎✧❁❂❈❤⸕] "); + private static final Object2IntAVLTreeMap REWARDS = new Object2IntAVLTreeMap<>((o1, o2) -> { + String o1String = GEMSTONE_SYMBOLS.matcher(o1.getString()).replaceAll(""); + String o2String = GEMSTONE_SYMBOLS.matcher(o2.getString()).replaceAll(""); + int priority1 = comparePriority(o1String); + int priority2 = comparePriority(o2String); + if (priority1 != priority2) return Integer.compare(priority1, priority2); + return o1String.compareTo(o2String); + } + ); + private static final Object2ObjectMap NAME2ID_MAP = new Object2ObjectArrayMap<>(50); + private static boolean insideChestMessage = false; + private static boolean accountForLootChestsAsWell = true; // TODO: Add a config option for this + private static double profit = 0; + + @Init + public static void init() { + ChatEvents.RECEIVE_TEXT.register(text -> { + if (Utils.getLocation() != Location.CRYSTAL_HOLLOWS) return; + List siblings = text.getSiblings(); + switch (siblings.size()) { + // The separator message has 1 sibling: "▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬" for which we can just use .getString on the main text + case 1 -> { + if (insideChestMessage && text.getString().equals("▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬")) insideChestMessage = false; + } + // CHEST LOCKPICKED message has 2 siblings: " ", "CHEST LOCKPICKED " + // LOOT CHEST COLLECTED message has 2 siblings: " ", "LOOT CHEST COLLECTED " + // Reward message with 1 item count has 2 siblings: " ", "item name" + case 2 -> { + String space = siblings.get(0).getString(); + Text second = siblings.get(1); + String secondString = second.getString(); + if (!insideChestMessage && space.equals(" ") && (secondString.equals("CHEST LOCKPICKED ") || (accountForLootChestsAsWell && secondString.equals("LOOT CHEST COLLECTED ")))) { + insideChestMessage = true; + return; + } + + if (insideChestMessage && space.equals(" ")) { + REWARDS.mergeInt(second, 1, Integer::sum); + calculateProfitForItem(second, 1); + } + } + // Reward message with more than 1 item count has 3 siblings: " ", "item name ", "x" (the space at the end of the item name is not a typo, it's there) + // For some reason the green goblin egg and 1 more that I can't figure out have an extra sibling that is always empty (at the 2nd position) + // To account for that, this case includes 4 size and there's a check for the 2nd sibling to be empty and the amount parsing has getLast() instead of hardcoded position + case 3, 4 -> { + if (!insideChestMessage) return; + String space = siblings.get(0).getString(); + if (!space.equals(" ")) return; + + Text itemName = siblings.get(1); + int amount; + + if (itemName.getString().isEmpty() && siblings.size() == 3) { + itemName = siblings.get(2); + amount = 1; + } else { + String nameTrimmed = itemName.getString().stripTrailing(); + itemName = Text.literal(nameTrimmed).setStyle(itemName.getStyle()); + amount = Integer.parseInt(siblings.getLast().getString().substring(1).replace(",", "")); + } + + REWARDS.mergeInt(itemName, amount, Integer::sum); + calculateProfitForItem(itemName, amount); + } + default -> {} + } + }); + + HudRenderEvents.AFTER_MAIN_HUD.register((context, tickCounter) -> { + if (Utils.getLocation() != Location.CRYSTAL_HOLLOWS) return; + int y = MinecraftClient.getInstance().getWindow().getScaledHeight() / 2 - 100; + var set = REWARDS.object2IntEntrySet(); + for (Object2IntMap.Entry entry : set) { + context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, entry.getKey(), 5, y, 0xFFFFFF); + context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, Text.of(String.valueOf(entry.getIntValue())), 10 + MinecraftClient.getInstance().textRenderer.getWidth(entry.getKey()), y, 0xFFFFFF); + y += 10; + } + context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, Text.literal("Gain: " + NumberFormat.getInstance().format(profit) + " coins").formatted(Formatting.GOLD), 5, y + 10, 0xFFFFFF); + }); + + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register( + literal(SkyblockerMod.NAMESPACE) + .then( + literal("clearrewards") + .executes(context -> { + REWARDS.clear(); + return 1; + }) + ) + .then( + literal("listrewards") + .executes(context -> { + var set = REWARDS.object2IntEntrySet(); + for (Object2IntMap.Entry entry : set) { + MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(entry.getKey().copy().append(" ").append(Text.of(String.valueOf(entry.getIntValue())))); + } + return 1; + }) + ) + )); + } + + private static int comparePriority(String s) { + // Puts gemstone powder at the top of the list, then gold and diamond essence, then gemstones by ascending rarity and then whatever else. + switch (s) { + case "Gemstone Powder" -> { + return 1; + } + case "Gold Essence" -> { + return 2; + } + case "Diamond Essence" -> { + return 3; + } + default -> { + if (s.startsWith("Rough")) return 4; + if (s.startsWith("Flawed")) return 5; + if (s.startsWith("Fine")) return 6; + if (s.startsWith("Flawless")) return 7; + } + } + return 8; + } + + private static void calculateProfitForItem(Text text, int amount) { + String id = getItemId(text); + DoubleBooleanPair price = ItemUtils.getItemPrice(id); + if (price.rightBoolean()) profit += price.leftDouble() * amount; + } + + static { + NAME2ID_MAP.put("Rough Ruby Gemstone", "ROUGH_RUBY_GEM"); + NAME2ID_MAP.put("Flawed Ruby Gemstone", "FLAWED_RUBY_GEM"); + NAME2ID_MAP.put("Fine Ruby Gemstone", "FINE_RUBY_GEM"); + NAME2ID_MAP.put("Flawless Ruby Gemstone", "FLAWLESS_RUBY_GEM"); + + NAME2ID_MAP.put("Rough Amethyst Gemstone", "ROUGH_AMETHYST_GEM"); + NAME2ID_MAP.put("Flawed Amethyst Gemstone", "FLAWED_AMETHYST_GEM"); + NAME2ID_MAP.put("Fine Amethyst Gemstone", "FINE_AMETHYST_GEM"); + NAME2ID_MAP.put("Flawless Amethyst Gemstone", "FLAWLESS_AMETHYST_GEM"); + + NAME2ID_MAP.put("Rough Jade Gemstone", "ROUGH_JADE_GEM"); + NAME2ID_MAP.put("Flawed Jade Gemstone", "FLAWED_JADE_GEM"); + NAME2ID_MAP.put("Fine Jade Gemstone", "FINE_JADE_GEM"); + NAME2ID_MAP.put("Flawless Jade Gemstone", "FLAWLESS_JADE_GEM"); + + NAME2ID_MAP.put("Rough Amber Gemstone", "ROUGH_AMBER_GEM"); + NAME2ID_MAP.put("Flawed Amber Gemstone", "FLAWED_AMBER_GEM"); + NAME2ID_MAP.put("Fine Amber Gemstone", "FINE_AMBER_GEM"); + NAME2ID_MAP.put("Flawless Amber Gemstone", "FLAWLESS_AMBER_GEM"); + + NAME2ID_MAP.put("Rough Sapphire Gemstone", "ROUGH_SAPPHIRE_GEM"); + NAME2ID_MAP.put("Flawed Sapphire Gemstone", "FLAWED_SAPPHIRE_GEM"); + NAME2ID_MAP.put("Fine Sapphire Gemstone", "FINE_SAPPHIRE_GEM"); + NAME2ID_MAP.put("Flawless Sapphire Gemstone", "FLAWLESS_SAPPHIRE_GEM"); + + NAME2ID_MAP.put("Rough Topaz Gemstone", "ROUGH_TOPAZ_GEM"); + NAME2ID_MAP.put("Flawed Topaz Gemstone", "FLAWED_TOPAZ_GEM"); + NAME2ID_MAP.put("Fine Topaz Gemstone", "FINE_TOPAZ_GEM"); + NAME2ID_MAP.put("Flawless Topaz Gemstone", "FLAWLESS_TOPAZ_GEM"); + + NAME2ID_MAP.put("Rough Jasper Gemstone", "ROUGH_JASPER_GEM"); + NAME2ID_MAP.put("Flawed Jasper Gemstone", "FLAWED_JASPER_GEM"); + NAME2ID_MAP.put("Fine Jasper Gemstone", "FINE_JASPER_GEM"); + NAME2ID_MAP.put("Flawless Jasper Gemstone", "FLAWLESS_JASPER_GEM"); + + NAME2ID_MAP.put("Pickonimbus 2000", "PICKONIMBUS"); + NAME2ID_MAP.put("Ascension Rope", "ASCENSION_ROPE"); + NAME2ID_MAP.put("Wishing Compass", "WISHING_COMPASS"); + NAME2ID_MAP.put("Gold Essence", "ESSENCE_GOLD"); + NAME2ID_MAP.put("Diamond Essence", "ESSENCE_DIAMOND"); + NAME2ID_MAP.put("Prehistoric Egg", "PREHISTORIC_EGG"); + NAME2ID_MAP.put("Sludge Juice", "SLUDGE_JUICE"); + NAME2ID_MAP.put("Oil Barrel", "OIL_BARREL"); + NAME2ID_MAP.put("Jungle Heart", "JUNGLE_HEART"); + NAME2ID_MAP.put("Treasurite", "TREASURITE"); + NAME2ID_MAP.put("Yoggie", "YOGGIE"); + + NAME2ID_MAP.put("Goblin Egg", "GOBLIN_EGG"); + NAME2ID_MAP.put("Green Goblin Egg", "GOBLIN_EGG_GREEN"); + NAME2ID_MAP.put("Blue Goblin Egg", "GOBLIN_EGG_BLUE"); + NAME2ID_MAP.put("Red Goblin Egg", "GOBLIN_EGG_RED"); + NAME2ID_MAP.put("Yellow Goblin Egg", "GOBLIN_EGG_YELLOW"); + + NAME2ID_MAP.put("Control Switch", "CONTROL_SWITCH"); + NAME2ID_MAP.put("Electron Transmitter", "ELECTRON_TRANSMITTER"); + NAME2ID_MAP.put("FTX 3070", "FTX_3070"); + NAME2ID_MAP.put("Synthetic Heart", "SYNTHETIC_HEART"); + NAME2ID_MAP.put("Robotron Reflector", "ROBOTRON_REFLECTOR"); + NAME2ID_MAP.put("Superlite Motor", "SUPERLITE_MOTOR"); + } + + private static String getItemId(Text text) { + return NAME2ID_MAP.getOrDefault(GEMSTONE_SYMBOLS.matcher(text.getString()).replaceAll(""), ""); + } +} From cd7c39456ccaf66c0819694452a1bc33f1835afc Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:49:27 +0300 Subject: [PATCH 03/18] Add ON_PRICE_UPDATE event and re-calculate profit after prices are updated --- .../skyblock/dwarven/PowderMiningTracker.java | 14 +++++--- .../skyblocker/skyblock/item/ItemPrice.java | 32 +++++++++++++------ .../skyblock/item/tooltip/ItemTooltip.java | 2 ++ 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java index 484306ca23..29f906e4eb 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java @@ -4,14 +4,12 @@ import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.events.ChatEvents; import de.hysky.skyblocker.events.HudRenderEvents; +import de.hysky.skyblocker.skyblock.item.ItemPrice; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.Utils; import it.unimi.dsi.fastutil.doubles.DoubleBooleanPair; -import it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.*; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.minecraft.client.MinecraftClient; import net.minecraft.text.Text; @@ -105,6 +103,14 @@ public static void init() { context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, Text.literal("Gain: " + NumberFormat.getInstance().format(profit) + " coins").formatted(Formatting.GOLD), 5, y + 10, 0xFFFFFF); }); + ItemPrice.ON_PRICE_UPDATE.register(() -> { + profit = 0; + ObjectSortedSet> set = REWARDS.object2IntEntrySet(); + for (Object2IntMap.Entry entry : set) { + calculateProfitForItem(entry.getKey(), entry.getIntValue()); + } + }); + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register( literal(SkyblockerMod.NAMESPACE) .then( diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemPrice.java b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemPrice.java index a4a6b928a4..db1798dbc2 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemPrice.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemPrice.java @@ -1,6 +1,5 @@ package de.hysky.skyblocker.skyblock.item; -import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; import de.hysky.skyblocker.skyblock.item.tooltip.info.DataTooltipInfoType; import de.hysky.skyblocker.skyblock.item.tooltip.info.TooltipInfoType; @@ -9,6 +8,8 @@ import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.scheduler.MessageScheduler; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.option.KeyBinding; import net.minecraft.item.ItemStack; @@ -33,8 +34,14 @@ public class ItemPrice { "key.categories.skyblocker" )); - @Init - public static void init() {} + /** + * An event that is fired when all prices are updated. + */ + public static final Event ON_PRICE_UPDATE = EventFactory.createArrayBacked(OnPriceUpdate.class, listeners -> () -> { + for (OnPriceUpdate listener : listeners) { + listener.onPriceUpdate(); + } + }); public static void itemPriceLookup(ClientPlayerEntity player, @NotNull Slot slot) { ItemStack stack = slot.getStack(); @@ -69,11 +76,18 @@ public static void refreshItemPrices(ClientPlayerEntity player) { CompletableFuture.allOf(Stream.of(TooltipInfoType.NPC, TooltipInfoType.BAZAAR, TooltipInfoType.LOWEST_BINS, TooltipInfoType.ONE_DAY_AVERAGE, TooltipInfoType.THREE_DAY_AVERAGE) .map(DataTooltipInfoType::downloadIfEnabled) .toArray(CompletableFuture[]::new) - ).thenRun(() -> player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.helpers.itemPrice.refreshedItemPrices")), false)) - .exceptionally(e -> { - ItemTooltip.LOGGER.error("[Skyblocker Item Price] Failed to refresh item prices", e); - player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.helpers.itemPrice.itemPriceRefreshFailed")), false); - return null; - }); + ).thenRun(() -> { + ON_PRICE_UPDATE.invoker().onPriceUpdate(); + player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.helpers.itemPrice.refreshedItemPrices")), false); + }).exceptionally(e -> { + ItemTooltip.LOGGER.error("[Skyblocker Item Price] Failed to refresh item prices", e); + player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.helpers.itemPrice.itemPriceRefreshFailed")), false); + return null; + }); } + + @FunctionalInterface + public interface OnPriceUpdate { + void onPriceUpdate(); + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java index c21fd0a14c..3b08fcaeba 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java @@ -3,6 +3,7 @@ import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.GeneralConfig; +import de.hysky.skyblocker.skyblock.item.ItemPrice; import de.hysky.skyblocker.skyblock.item.tooltip.adders.CraftPriceTooltip; import de.hysky.skyblocker.skyblock.item.tooltip.info.DataTooltipInfoType; import de.hysky.skyblocker.skyblock.item.tooltip.info.TooltipInfoType; @@ -83,6 +84,7 @@ public static void init() { .map(DataTooltipInfoType.class::cast) .map(DataTooltipInfoType::downloadIfEnabled) .toArray(CompletableFuture[]::new) + ).thenRun(() -> ItemPrice.ON_PRICE_UPDATE.invoker().onPriceUpdate() ).exceptionally(e -> { LOGGER.error("[Skyblocker] Encountered unknown error while downloading tooltip data", e); return null; From fa3c3ee66c4c42a360be7ce944f6430aae7ce0ba Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Sat, 23 Nov 2024 16:03:56 +0300 Subject: [PATCH 04/18] Initial config --- .../skyblocker/config/configs/MiningConfig.java | 6 ++++++ .../skyblock/dwarven/PowderMiningTracker.java | 15 +++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java index 0eb76f2273..0d4e3e8b1b 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java @@ -83,6 +83,12 @@ public static class CrystalHollows { @SerialEntry public Color chestHighlightColor = new Color(0, 0, 255, 128); + + @SerialEntry + public boolean enablePowderTracker = true; + + @SerialEntry + public boolean countNaturalChestsInTracker = true; } public static class CrystalsHud { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java index 29f906e4eb..d252fc8ba8 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java @@ -2,6 +2,7 @@ import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.events.ChatEvents; import de.hysky.skyblocker.events.HudRenderEvents; import de.hysky.skyblocker.skyblock.item.ItemPrice; @@ -23,6 +24,8 @@ public class PowderMiningTracker { private static final Pattern GEMSTONE_SYMBOLS = Pattern.compile("[α☘☠✎✧❁❂❈❤⸕] "); + // This constructor takes in a comparator that is triggered to decide where to add the element in the tree map + // This causes it to be sorted at all times. This is for rendering them in a sort of easy-to-read manner. private static final Object2IntAVLTreeMap REWARDS = new Object2IntAVLTreeMap<>((o1, o2) -> { String o1String = GEMSTONE_SYMBOLS.matcher(o1.getString()).replaceAll(""); String o2String = GEMSTONE_SYMBOLS.matcher(o2.getString()).replaceAll(""); @@ -34,13 +37,17 @@ public class PowderMiningTracker { ); private static final Object2ObjectMap NAME2ID_MAP = new Object2ObjectArrayMap<>(50); private static boolean insideChestMessage = false; - private static boolean accountForLootChestsAsWell = true; // TODO: Add a config option for this private static double profit = 0; + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private static boolean isEnabled() { + return SkyblockerConfigManager.get().mining.crystalHollows.enablePowderTracker; + } + @Init public static void init() { ChatEvents.RECEIVE_TEXT.register(text -> { - if (Utils.getLocation() != Location.CRYSTAL_HOLLOWS) return; + if (Utils.getLocation() != Location.CRYSTAL_HOLLOWS || !isEnabled()) return; List siblings = text.getSiblings(); switch (siblings.size()) { // The separator message has 1 sibling: "▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬" for which we can just use .getString on the main text @@ -54,7 +61,7 @@ public static void init() { String space = siblings.get(0).getString(); Text second = siblings.get(1); String secondString = second.getString(); - if (!insideChestMessage && space.equals(" ") && (secondString.equals("CHEST LOCKPICKED ") || (accountForLootChestsAsWell && secondString.equals("LOOT CHEST COLLECTED ")))) { + if (!insideChestMessage && space.equals(" ") && (secondString.equals("CHEST LOCKPICKED ") || (SkyblockerConfigManager.get().mining.crystalHollows.countNaturalChestsInTracker && secondString.equals("LOOT CHEST COLLECTED ")))) { insideChestMessage = true; return; } @@ -92,7 +99,7 @@ public static void init() { }); HudRenderEvents.AFTER_MAIN_HUD.register((context, tickCounter) -> { - if (Utils.getLocation() != Location.CRYSTAL_HOLLOWS) return; + if (Utils.getLocation() != Location.CRYSTAL_HOLLOWS || !isEnabled()) return; int y = MinecraftClient.getInstance().getWindow().getScaledHeight() / 2 - 100; var set = REWARDS.object2IntEntrySet(); for (Object2IntMap.Entry entry : set) { From 9095174eb7a735abae2e6b6a367a9bfb56bee39b Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Mon, 25 Nov 2024 00:41:38 +0300 Subject: [PATCH 05/18] Add config screens for filtering the shown items --- .../config/categories/MiningCategory.java | 9 + .../config/configs/MiningConfig.java | 5 + .../screens/powdertracker/ItemTickList.java | 79 ++++++++ .../PowderFilterConfigScreen.java | 72 ++++++++ .../skyblock/dwarven/PowderMiningTracker.java | 174 +++++++++++------- .../assets/skyblocker/lang/en_us.json | 4 + 6 files changed, 278 insertions(+), 65 deletions(-) create mode 100644 src/main/java/de/hysky/skyblocker/config/screens/powdertracker/ItemTickList.java create mode 100644 src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java diff --git a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java index 343643685b..d12f523a72 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java @@ -3,14 +3,17 @@ import de.hysky.skyblocker.config.ConfigUtils; import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.configs.MiningConfig; +import de.hysky.skyblocker.config.screens.powdertracker.PowderFilterConfigScreen; import de.hysky.skyblocker.skyblock.dwarven.CrystalsHudWidget; import de.hysky.skyblocker.skyblock.dwarven.CarpetHighlighter; +import de.hysky.skyblocker.skyblock.dwarven.PowderMiningTracker; import dev.isxander.yacl3.api.*; import dev.isxander.yacl3.api.controller.ColorControllerBuilder; import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; import de.hysky.skyblocker.utils.Location; import dev.isxander.yacl3.api.controller.FloatFieldControllerBuilder; import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder; +import it.unimi.dsi.fastutil.objects.ObjectImmutableList; import net.minecraft.client.MinecraftClient; import net.minecraft.text.Text; @@ -112,6 +115,12 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig newValue -> config.mining.crystalHollows.chestHighlightColor = newValue) .controller(v -> ColorControllerBuilder.create(v).allowAlpha(true)) .build()) + .option(ButtonOption.createBuilder() + .name(Text.translatable("skyblocker.config.mining.crystalHollows.powderTrackerFilter")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.mining.crystalHollows.powderTrackerFilter.@Tooltip"))) + .text(Text.translatable("text.skyblocker.open")) + .action((screen, opt) -> MinecraftClient.getInstance().setScreen(new PowderFilterConfigScreen(screen, new ObjectImmutableList<>(PowderMiningTracker.getName2IdMap().keySet())))) + .build()) .build()) //Crystal Hollows Map diff --git a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java index 0d4e3e8b1b..201c5c4961 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java @@ -4,6 +4,8 @@ import net.minecraft.client.resource.language.I18n; import java.awt.*; +import java.util.ArrayList; +import java.util.List; public class MiningConfig { @SerialEntry @@ -89,6 +91,9 @@ public static class CrystalHollows { @SerialEntry public boolean countNaturalChestsInTracker = true; + + @SerialEntry + public List powderTrackerFilter = new ArrayList<>(); } public static class CrystalsHud { diff --git a/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/ItemTickList.java b/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/ItemTickList.java new file mode 100644 index 0000000000..ed67b4561b --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/ItemTickList.java @@ -0,0 +1,79 @@ +package de.hysky.skyblocker.config.screens.powdertracker; + +import de.hysky.skyblocker.mixins.accessors.CheckboxWidgetAccessor; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.widget.CheckboxWidget; +import net.minecraft.client.gui.widget.ElementListWidget; +import net.minecraft.text.Text; + +import java.util.List; + +public class ItemTickList extends ElementListWidget { + private final List filters; + private final List allItems; + + public ItemTickList(MinecraftClient minecraftClient, int width, int height, int y, int entryHeight, List filters, List allItems) { + super(minecraftClient, width, height, y, entryHeight); + this.filters = filters; + this.allItems = allItems; + } + + public void clearAndInit() { + clearEntries(); + init(); + } + + public ItemTickList init() { + for (String item : allItems) { + ItemTickEntry entry = new ItemTickEntry( + CheckboxWidget.builder(Text.of(item), client.textRenderer) + .checked(!filters.contains(item)) + .callback((checkbox1, checked) -> { + if (checked) filters.remove(item); + else filters.add(item); + }) + .build() + ); + addEntry(entry); + } + return this; + } + + public static class ItemTickEntry extends ElementListWidget.Entry { + private final List children; + + ItemTickEntry(CheckboxWidget checkboxWidget) { + children = List.of(checkboxWidget); + } + + public void setChecked(boolean checked) { + for (CheckboxWidget child : children) { + ((CheckboxWidgetAccessor) child).setChecked(checked); + } + } + + @Override + public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + for (CheckboxWidget child : children) { + child.setX(x); + child.setY(y); + child.setWidth(entryWidth); + child.setHeight(entryHeight); + child.render(context, mouseX, mouseY, tickDelta); + } + } + + @Override + public List selectableChildren() { + return children; + } + + @Override + public List children() { + return children; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java b/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java new file mode 100644 index 0000000000..1f71c5c589 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java @@ -0,0 +1,72 @@ +package de.hysky.skyblocker.config.screens.powdertracker; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.dwarven.PowderMiningTracker; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.GridWidget; +import net.minecraft.client.gui.widget.SimplePositioningWidget; +import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class PowderFilterConfigScreen extends Screen { + @Nullable + private final Screen parent; + private final List filters; + private final List allItems; + + public PowderFilterConfigScreen(@Nullable Screen parent, List allItems) { + super(Text.of("Powder Mining Tracker Filter Config")); + this.parent = parent; + this.filters = new ArrayList<>(SkyblockerConfigManager.get().mining.crystalHollows.powderTrackerFilter); // Copy the list so we can undo changes when necessary + this.allItems = allItems; + } + + @Override + protected void init() { + addDrawable((context, mouseX, mouseY, delta) -> { + assert client != null; + context.drawCenteredTextWithShadow(client.textRenderer, Text.literal("Shown Items").formatted(Formatting.BOLD), width / 2, (32 - client.textRenderer.fontHeight) / 2, 0xFFFFFF); + }); + ItemTickList itemTickList = addDrawableChild(new ItemTickList(MinecraftClient.getInstance(), width, height - 96, 32, 24, filters, allItems).init()); + //Grid code gratuitously stolen from WaypointsScreen. Same goes for the y and heights above. + GridWidget gridWidget = new GridWidget(); + gridWidget.getMainPositioner().marginX(5).marginY(2); + GridWidget.Adder adder = gridWidget.createAdder(2); + + adder.add(ButtonWidget.builder(Text.translatable("text.skyblocker.reset"), button -> { + filters.clear(); + itemTickList.clearAndInit(); + }).build()); + adder.add(ButtonWidget.builder(Text.translatable("text.skyblocker.undo"), button -> { + filters.clear(); + filters.addAll(SkyblockerConfigManager.get().mining.crystalHollows.powderTrackerFilter); + itemTickList.clearAndInit(); + }).build()); + adder.add(ButtonWidget.builder(ScreenTexts.DONE, button -> { + saveFilters(); + close(); + }).build(), 2); + gridWidget.refreshPositions(); + SimplePositioningWidget.setPos(gridWidget, 0, this.height - 64, this.width, 64); + gridWidget.forEachChild(this::addDrawableChild); + } + + public void saveFilters() { + SkyblockerConfigManager.get().mining.crystalHollows.powderTrackerFilter = filters; + SkyblockerConfigManager.save(); + PowderMiningTracker.filterChangeCallback(); + } + + @Override + public void close() { + assert client != null; + client.setScreen(parent); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java index d252fc8ba8..0ab9dd65c2 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java @@ -13,8 +13,11 @@ import it.unimi.dsi.fastutil.objects.*; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.hud.ChatHud; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import org.apache.commons.lang3.math.NumberUtils; +import org.jetbrains.annotations.Unmodifiable; import java.text.NumberFormat; import java.util.List; @@ -26,15 +29,24 @@ public class PowderMiningTracker { private static final Pattern GEMSTONE_SYMBOLS = Pattern.compile("[α☘☠✎✧❁❂❈❤⸕] "); // This constructor takes in a comparator that is triggered to decide where to add the element in the tree map // This causes it to be sorted at all times. This is for rendering them in a sort of easy-to-read manner. - private static final Object2IntAVLTreeMap REWARDS = new Object2IntAVLTreeMap<>((o1, o2) -> { - String o1String = GEMSTONE_SYMBOLS.matcher(o1.getString()).replaceAll(""); - String o2String = GEMSTONE_SYMBOLS.matcher(o2.getString()).replaceAll(""); - int priority1 = comparePriority(o1String); - int priority2 = comparePriority(o2String); - if (priority1 != priority2) return Integer.compare(priority1, priority2); - return o1String.compareTo(o2String); - } - ); + private static final Object2IntAVLTreeMap SHOWN_REWARDS = new Object2IntAVLTreeMap<>((o1, o2) -> { + String o1String = GEMSTONE_SYMBOLS.matcher(o1.getString()).replaceAll(""); + String o2String = GEMSTONE_SYMBOLS.matcher(o2.getString()).replaceAll(""); + int priority1 = comparePriority(o1String); + int priority2 = comparePriority(o2String); + if (priority1 != priority2) return Integer.compare(priority1, priority2); + return o1String.compareTo(o2String); + }); + + /** + *

+ * Holds the total amount of each reward obtained. If any items are filtered out, they are still added to this map but not to the {@link #SHOWN_REWARDS} map. + * Once the filter is changed, the {@link #SHOWN_REWARDS} map is cleared and recalculated based on this map. + *

+ *

This is similar to how {@link ChatHud#messages} and {@link ChatHud#visibleMessages} behave.

+ */ + @SuppressWarnings("JavadocReference") + private static final Object2IntMap ALL_REWARDS = new Object2IntArrayMap<>(); private static final Object2ObjectMap NAME2ID_MAP = new Object2ObjectArrayMap<>(50); private static boolean insideChestMessage = false; private static double profit = 0; @@ -67,12 +79,12 @@ public static void init() { } if (insideChestMessage && space.equals(" ")) { - REWARDS.mergeInt(second, 1, Integer::sum); + incrementReward(second, 1); calculateProfitForItem(second, 1); } } // Reward message with more than 1 item count has 3 siblings: " ", "item name ", "x" (the space at the end of the item name is not a typo, it's there) - // For some reason the green goblin egg and 1 more that I can't figure out have an extra sibling that is always empty (at the 2nd position) + // For some reason the colored goblin eggs have an extra sibling that is always empty (at the 2nd position) // To account for that, this case includes 4 size and there's a check for the 2nd sibling to be empty and the amount parsing has getLast() instead of hardcoded position case 3, 4 -> { if (!insideChestMessage) return; @@ -80,18 +92,15 @@ public static void init() { if (!space.equals(" ")) return; Text itemName = siblings.get(1); - int amount; - - if (itemName.getString().isEmpty() && siblings.size() == 3) { - itemName = siblings.get(2); - amount = 1; - } else { - String nameTrimmed = itemName.getString().stripTrailing(); - itemName = Text.literal(nameTrimmed).setStyle(itemName.getStyle()); - amount = Integer.parseInt(siblings.getLast().getString().substring(1).replace(",", "")); - } + if (itemName.getString().isEmpty()) itemName = siblings.get(2); + + // If there's nothing to trim this will just do nothing + String nameTrimmed = itemName.getString().stripTrailing(); + itemName = Text.literal(nameTrimmed).setStyle(itemName.getStyle()); + // The failing of the conversion below when the amount is not included is intentional, saves some thinking + int amount = NumberUtils.toInt(siblings.getLast().getString().substring(1).replace(",", ""), 1); - REWARDS.mergeInt(itemName, amount, Integer::sum); + incrementReward(itemName, amount); calculateProfitForItem(itemName, amount); } default -> {} @@ -101,7 +110,7 @@ public static void init() { HudRenderEvents.AFTER_MAIN_HUD.register((context, tickCounter) -> { if (Utils.getLocation() != Location.CRYSTAL_HOLLOWS || !isEnabled()) return; int y = MinecraftClient.getInstance().getWindow().getScaledHeight() / 2 - 100; - var set = REWARDS.object2IntEntrySet(); + var set = SHOWN_REWARDS.object2IntEntrySet(); for (Object2IntMap.Entry entry : set) { context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, entry.getKey(), 5, y, 0xFFFFFF); context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, Text.of(String.valueOf(entry.getIntValue())), 10 + MinecraftClient.getInstance().textRenderer.getWidth(entry.getKey()), y, 0xFFFFFF); @@ -111,11 +120,7 @@ public static void init() { }); ItemPrice.ON_PRICE_UPDATE.register(() -> { - profit = 0; - ObjectSortedSet> set = REWARDS.object2IntEntrySet(); - for (Object2IntMap.Entry entry : set) { - calculateProfitForItem(entry.getKey(), entry.getIntValue()); - } + if (isEnabled()) recalculateAll(); }); ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register( @@ -123,14 +128,14 @@ public static void init() { .then( literal("clearrewards") .executes(context -> { - REWARDS.clear(); + SHOWN_REWARDS.clear(); return 1; }) ) .then( literal("listrewards") .executes(context -> { - var set = REWARDS.object2IntEntrySet(); + var set = SHOWN_REWARDS.object2IntEntrySet(); for (Object2IntMap.Entry entry : set) { MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(entry.getKey().copy().append(" ").append(Text.of(String.valueOf(entry.getIntValue())))); } @@ -140,6 +145,11 @@ public static void init() { )); } + private static void incrementReward(Text text, int amount) { + ALL_REWARDS.mergeInt(text, amount, Integer::sum); + if (!SkyblockerConfigManager.get().mining.crystalHollows.powderTrackerFilter.contains(text.getString())) SHOWN_REWARDS.mergeInt(text, amount, Integer::sum); + } + private static int comparePriority(String s) { // Puts gemstone powder at the top of the list, then gold and diamond essence, then gemstones by ascending rarity and then whatever else. switch (s) { @@ -162,47 +172,81 @@ private static int comparePriority(String s) { return 8; } + /** + * Normally, the price is calculated on a per-reward basis as they are obtained. This is what this method does. + */ private static void calculateProfitForItem(Text text, int amount) { String id = getItemId(text); DoubleBooleanPair price = ItemUtils.getItemPrice(id); if (price.rightBoolean()) profit += price.leftDouble() * amount; } + /** + * When the bz/ah prices are updated, this method recalculates the profit for all rewards at once. + */ + private static void recalculateAll() { + profit = 0; + ObjectSortedSet> set = SHOWN_REWARDS.object2IntEntrySet(); + for (Object2IntMap.Entry entry : set) { + calculateProfitForItem(entry.getKey(), entry.getIntValue()); + } + } + + /** + * Resets the shown rewards and profit to 0 and recalculates them based on the config filter. + */ + public static void filterChangeCallback() { + SHOWN_REWARDS.clear(); + profit = 0; + ObjectSet> set = ALL_REWARDS.object2IntEntrySet(); + for (Object2IntMap.Entry entry : set) { + if (!SkyblockerConfigManager.get().mining.crystalHollows.powderTrackerFilter.contains(entry.getKey().getString())) { + SHOWN_REWARDS.put(entry.getKey(), entry.getIntValue()); + calculateProfitForItem(entry.getKey(), entry.getIntValue()); + } + } + } + + @Unmodifiable + public static Object2ObjectMap getName2IdMap() { + return Object2ObjectMaps.unmodifiable(NAME2ID_MAP); + } + static { - NAME2ID_MAP.put("Rough Ruby Gemstone", "ROUGH_RUBY_GEM"); - NAME2ID_MAP.put("Flawed Ruby Gemstone", "FLAWED_RUBY_GEM"); - NAME2ID_MAP.put("Fine Ruby Gemstone", "FINE_RUBY_GEM"); - NAME2ID_MAP.put("Flawless Ruby Gemstone", "FLAWLESS_RUBY_GEM"); - - NAME2ID_MAP.put("Rough Amethyst Gemstone", "ROUGH_AMETHYST_GEM"); - NAME2ID_MAP.put("Flawed Amethyst Gemstone", "FLAWED_AMETHYST_GEM"); - NAME2ID_MAP.put("Fine Amethyst Gemstone", "FINE_AMETHYST_GEM"); - NAME2ID_MAP.put("Flawless Amethyst Gemstone", "FLAWLESS_AMETHYST_GEM"); - - NAME2ID_MAP.put("Rough Jade Gemstone", "ROUGH_JADE_GEM"); - NAME2ID_MAP.put("Flawed Jade Gemstone", "FLAWED_JADE_GEM"); - NAME2ID_MAP.put("Fine Jade Gemstone", "FINE_JADE_GEM"); - NAME2ID_MAP.put("Flawless Jade Gemstone", "FLAWLESS_JADE_GEM"); - - NAME2ID_MAP.put("Rough Amber Gemstone", "ROUGH_AMBER_GEM"); - NAME2ID_MAP.put("Flawed Amber Gemstone", "FLAWED_AMBER_GEM"); - NAME2ID_MAP.put("Fine Amber Gemstone", "FINE_AMBER_GEM"); - NAME2ID_MAP.put("Flawless Amber Gemstone", "FLAWLESS_AMBER_GEM"); - - NAME2ID_MAP.put("Rough Sapphire Gemstone", "ROUGH_SAPPHIRE_GEM"); - NAME2ID_MAP.put("Flawed Sapphire Gemstone", "FLAWED_SAPPHIRE_GEM"); - NAME2ID_MAP.put("Fine Sapphire Gemstone", "FINE_SAPPHIRE_GEM"); - NAME2ID_MAP.put("Flawless Sapphire Gemstone", "FLAWLESS_SAPPHIRE_GEM"); - - NAME2ID_MAP.put("Rough Topaz Gemstone", "ROUGH_TOPAZ_GEM"); - NAME2ID_MAP.put("Flawed Topaz Gemstone", "FLAWED_TOPAZ_GEM"); - NAME2ID_MAP.put("Fine Topaz Gemstone", "FINE_TOPAZ_GEM"); - NAME2ID_MAP.put("Flawless Topaz Gemstone", "FLAWLESS_TOPAZ_GEM"); - - NAME2ID_MAP.put("Rough Jasper Gemstone", "ROUGH_JASPER_GEM"); - NAME2ID_MAP.put("Flawed Jasper Gemstone", "FLAWED_JASPER_GEM"); - NAME2ID_MAP.put("Fine Jasper Gemstone", "FINE_JASPER_GEM"); - NAME2ID_MAP.put("Flawless Jasper Gemstone", "FLAWLESS_JASPER_GEM"); + NAME2ID_MAP.put("❤ Rough Ruby Gemstone", "ROUGH_RUBY_GEM"); + NAME2ID_MAP.put("❤ Flawed Ruby Gemstone", "FLAWED_RUBY_GEM"); + NAME2ID_MAP.put("❤ Fine Ruby Gemstone", "FINE_RUBY_GEM"); + NAME2ID_MAP.put("❤ Flawless Ruby Gemstone", "FLAWLESS_RUBY_GEM"); + + NAME2ID_MAP.put("❈ Rough Amethyst Gemstone", "ROUGH_AMETHYST_GEM"); + NAME2ID_MAP.put("❈ Flawed Amethyst Gemstone", "FLAWED_AMETHYST_GEM"); + NAME2ID_MAP.put("❈ Fine Amethyst Gemstone", "FINE_AMETHYST_GEM"); + NAME2ID_MAP.put("❈ Flawless Amethyst Gemstone", "FLAWLESS_AMETHYST_GEM"); + + NAME2ID_MAP.put("☘ Rough Jade Gemstone", "ROUGH_JADE_GEM"); + NAME2ID_MAP.put("☘ Flawed Jade Gemstone", "FLAWED_JADE_GEM"); + NAME2ID_MAP.put("☘ Fine Jade Gemstone", "FINE_JADE_GEM"); + NAME2ID_MAP.put("☘ Flawless Jade Gemstone", "FLAWLESS_JADE_GEM"); + + NAME2ID_MAP.put("⸕ Rough Amber Gemstone", "ROUGH_AMBER_GEM"); + NAME2ID_MAP.put("⸕ Flawed Amber Gemstone", "FLAWED_AMBER_GEM"); + NAME2ID_MAP.put("⸕ Fine Amber Gemstone", "FINE_AMBER_GEM"); + NAME2ID_MAP.put("⸕ Flawless Amber Gemstone", "FLAWLESS_AMBER_GEM"); + + NAME2ID_MAP.put("✎ Rough Sapphire Gemstone", "ROUGH_SAPPHIRE_GEM"); + NAME2ID_MAP.put("✎ Flawed Sapphire Gemstone", "FLAWED_SAPPHIRE_GEM"); + NAME2ID_MAP.put("✎ Fine Sapphire Gemstone", "FINE_SAPPHIRE_GEM"); + NAME2ID_MAP.put("✎ Flawless Sapphire Gemstone", "FLAWLESS_SAPPHIRE_GEM"); + + NAME2ID_MAP.put("✧ Rough Topaz Gemstone", "ROUGH_TOPAZ_GEM"); + NAME2ID_MAP.put("✧ Flawed Topaz Gemstone", "FLAWED_TOPAZ_GEM"); + NAME2ID_MAP.put("✧ Fine Topaz Gemstone", "FINE_TOPAZ_GEM"); + NAME2ID_MAP.put("✧ Flawless Topaz Gemstone", "FLAWLESS_TOPAZ_GEM"); + + NAME2ID_MAP.put("❁ Rough Jasper Gemstone", "ROUGH_JASPER_GEM"); + NAME2ID_MAP.put("❁ Flawed Jasper Gemstone", "FLAWED_JASPER_GEM"); + NAME2ID_MAP.put("❁ Fine Jasper Gemstone", "FINE_JASPER_GEM"); + NAME2ID_MAP.put("❁ Flawless Jasper Gemstone", "FLAWLESS_JASPER_GEM"); NAME2ID_MAP.put("Pickonimbus 2000", "PICKONIMBUS"); NAME2ID_MAP.put("Ascension Rope", "ASCENSION_ROPE"); @@ -231,6 +275,6 @@ private static void calculateProfitForItem(Text text, int amount) { } private static String getItemId(Text text) { - return NAME2ID_MAP.getOrDefault(GEMSTONE_SYMBOLS.matcher(text.getString()).replaceAll(""), ""); + return NAME2ID_MAP.getOrDefault(text.getString(), ""); } } diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index f980869d5f..19ca2876ab 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -23,6 +23,8 @@ "text.skyblocker.quit_discard": "Quit & Discard Changes", "text.skyblocker.confirm": "Confirm", "text.skyblocker.config": "Open Config...", + "text.skyblocker.reset": "Reset", + "text.skyblocker.undo": "Undo", "text.skyblocker.source": "Source", "text.skyblocker.website": "Website", "text.skyblocker.translate": "Translate", @@ -558,6 +560,8 @@ "skyblocker.config.mining.crystalHollows.chestHighlighter.@Tooltip": "Highlight found treasure chests and lock pick locations when powder mining.", "skyblocker.config.mining.crystalHollows.chestHighlighter.color": "Chest Highlight Color", "skyblocker.config.mining.crystalHollows.chestHighlighter.color.@Tooltip": "What color the treasure chests / lock pick should be highlighted.", + "skyblocker.config.mining.crystalHollows.powderTrackerFilter": "Powder Tracker Shown Items", + "skyblocker.config.mining.crystalHollows.powderTrackerFilter.@Tooltip": "Choose which items to show & include in the profit calculation for the powder tracker.", "skyblocker.config.mining.crystalsHud": "Crystal Hollows Map", "skyblocker.config.mining.crystalsHud.enabled": "Enabled", From 8675e96bac10ea3ab09fd6b5adcbc1d8d3947155 Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Mon, 25 Nov 2024 00:53:30 +0300 Subject: [PATCH 06/18] Fix Done button width being only 1 col wide when it's supposed to be 2 --- .../screens/powdertracker/PowderFilterConfigScreen.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java b/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java index 1f71c5c589..baeff8956c 100644 --- a/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java +++ b/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java @@ -50,9 +50,11 @@ protected void init() { itemTickList.clearAndInit(); }).build()); adder.add(ButtonWidget.builder(ScreenTexts.DONE, button -> { - saveFilters(); - close(); - }).build(), 2); + saveFilters(); + close(); + }) + .width((ButtonWidget.DEFAULT_WIDTH * 2) + 10) + .build(), 2); gridWidget.refreshPositions(); SimplePositioningWidget.setPos(gridWidget, 0, this.height - 64, this.width, 64); gridWidget.forEachChild(this::addDrawableChild); From d2bc9fd48f94b0b0fdfb9d84949168903906292e Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Mon, 25 Nov 2024 01:57:31 +0300 Subject: [PATCH 07/18] Change to regex because index magic wasn't magic-ing Regex my beloved --- .../skyblock/dwarven/PowderMiningTracker.java | 143 ++++++++++-------- 1 file changed, 79 insertions(+), 64 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java index 0ab9dd65c2..0c68e04274 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java @@ -6,6 +6,7 @@ import de.hysky.skyblocker.events.ChatEvents; import de.hysky.skyblocker.events.HudRenderEvents; import de.hysky.skyblocker.skyblock.item.ItemPrice; +import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.Utils; @@ -14,24 +15,31 @@ import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.hud.ChatHud; +import net.minecraft.item.ItemStack; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import org.apache.commons.lang3.math.NumberUtils; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Unmodifiable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.text.NumberFormat; import java.util.List; +import java.util.regex.Matcher; import java.util.regex.Pattern; import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; public class PowderMiningTracker { + private static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Powder Mining Tracker"); private static final Pattern GEMSTONE_SYMBOLS = Pattern.compile("[α☘☠✎✧❁❂❈❤⸕] "); + private static final Pattern REWARD_PATTERN = Pattern.compile(" +(.*?) ?x?(\\d*)"); // This constructor takes in a comparator that is triggered to decide where to add the element in the tree map // This causes it to be sorted at all times. This is for rendering them in a sort of easy-to-read manner. private static final Object2IntAVLTreeMap SHOWN_REWARDS = new Object2IntAVLTreeMap<>((o1, o2) -> { - String o1String = GEMSTONE_SYMBOLS.matcher(o1.getString()).replaceAll(""); - String o2String = GEMSTONE_SYMBOLS.matcher(o2.getString()).replaceAll(""); + String o1String = o1.getString(); + String o2String = o2.getString(); int priority1 = comparePriority(o1String); int priority2 = comparePriority(o2String); if (priority1 != priority2) return Integer.compare(priority1, priority2); @@ -44,10 +52,12 @@ public class PowderMiningTracker { * Once the filter is changed, the {@link #SHOWN_REWARDS} map is cleared and recalculated based on this map. *

*

This is similar to how {@link ChatHud#messages} and {@link ChatHud#visibleMessages} behave.

+ * + * @implNote This is a map of item IDs to the amount of that item obtained. */ @SuppressWarnings("JavadocReference") - private static final Object2IntMap ALL_REWARDS = new Object2IntArrayMap<>(); - private static final Object2ObjectMap NAME2ID_MAP = new Object2ObjectArrayMap<>(50); + private static final Object2IntMap ALL_REWARDS = new Object2IntArrayMap<>(); + private static final Object2ObjectArrayMap NAME2ID_MAP = new Object2ObjectArrayMap<>(50); private static boolean insideChestMessage = false; private static double profit = 0; @@ -58,53 +68,32 @@ private static boolean isEnabled() { @Init public static void init() { - ChatEvents.RECEIVE_TEXT.register(text -> { + ChatEvents.RECEIVE_STRING.register(text -> { if (Utils.getLocation() != Location.CRYSTAL_HOLLOWS || !isEnabled()) return; - List siblings = text.getSiblings(); - switch (siblings.size()) { - // The separator message has 1 sibling: "▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬" for which we can just use .getString on the main text - case 1 -> { - if (insideChestMessage && text.getString().equals("▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬")) insideChestMessage = false; - } - // CHEST LOCKPICKED message has 2 siblings: " ", "CHEST LOCKPICKED " - // LOOT CHEST COLLECTED message has 2 siblings: " ", "LOOT CHEST COLLECTED " - // Reward message with 1 item count has 2 siblings: " ", "item name" - case 2 -> { - String space = siblings.get(0).getString(); - Text second = siblings.get(1); - String secondString = second.getString(); - if (!insideChestMessage && space.equals(" ") && (secondString.equals("CHEST LOCKPICKED ") || (SkyblockerConfigManager.get().mining.crystalHollows.countNaturalChestsInTracker && secondString.equals("LOOT CHEST COLLECTED ")))) { - insideChestMessage = true; - return; - } - - if (insideChestMessage && space.equals(" ")) { - incrementReward(second, 1); - calculateProfitForItem(second, 1); - } - } - // Reward message with more than 1 item count has 3 siblings: " ", "item name ", "x" (the space at the end of the item name is not a typo, it's there) - // For some reason the colored goblin eggs have an extra sibling that is always empty (at the 2nd position) - // To account for that, this case includes 4 size and there's a check for the 2nd sibling to be empty and the amount parsing has getLast() instead of hardcoded position - case 3, 4 -> { - if (!insideChestMessage) return; - String space = siblings.get(0).getString(); - if (!space.equals(" ")) return; - - Text itemName = siblings.get(1); - if (itemName.getString().isEmpty()) itemName = siblings.get(2); - - // If there's nothing to trim this will just do nothing - String nameTrimmed = itemName.getString().stripTrailing(); - itemName = Text.literal(nameTrimmed).setStyle(itemName.getStyle()); - // The failing of the conversion below when the amount is not included is intentional, saves some thinking - int amount = NumberUtils.toInt(siblings.getLast().getString().substring(1).replace(",", ""), 1); - - incrementReward(itemName, amount); - calculateProfitForItem(itemName, amount); - } - default -> {} + // Reward messages end with a separator like so + if (insideChestMessage && text.equals("▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬")) { + insideChestMessage = false; + return; + } + + if (!insideChestMessage && (text.equals(" CHEST LOCKPICKED ") || (SkyblockerConfigManager.get().mining.crystalHollows.countNaturalChestsInTracker && text.equals(" LOOT CHEST COLLECTED ")))) { + insideChestMessage = true; + return; + } + + if (!insideChestMessage) return; + Matcher matcher = REWARD_PATTERN.matcher(text); + if (!matcher.matches()) return; + String itemName = matcher.group(1); + int amount = NumberUtils.toInt(matcher.group(2).replace(",", ""), 1); + + String itemId = getItemId(itemName); + if (itemId.isEmpty()) { + LOGGER.error("No matching item id for name `{}`. Report this!", itemName); + return; } + incrementReward(itemName, itemId, amount); + calculateProfitForItem(itemId, amount); }); HudRenderEvents.AFTER_MAIN_HUD.register((context, tickCounter) -> { @@ -123,6 +112,7 @@ public static void init() { if (isEnabled()) recalculateAll(); }); + //TODO: Sort out proper commands for this ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register( literal(SkyblockerMod.NAMESPACE) .then( @@ -145,12 +135,24 @@ public static void init() { )); } - private static void incrementReward(Text text, int amount) { - ALL_REWARDS.mergeInt(text, amount, Integer::sum); - if (!SkyblockerConfigManager.get().mining.crystalHollows.powderTrackerFilter.contains(text.getString())) SHOWN_REWARDS.mergeInt(text, amount, Integer::sum); + private static void incrementReward(String itemName, String itemId, int amount) { + ALL_REWARDS.mergeInt(itemId, amount, Integer::sum); + if (!SkyblockerConfigManager.get().mining.crystalHollows.powderTrackerFilter.contains(itemName)) { + if (itemId.equals("GEMSTONE_POWDER")) { + SHOWN_REWARDS.merge(Text.literal("Gemstone Powder").formatted(Formatting.LIGHT_PURPLE), amount, Integer::sum); + } else { + ItemStack stack = ItemRepository.getItemStack(itemId); + if (stack == null) { + LOGGER.warn("Item stack for id `{}` is null! This might be caused by failed item repository downloads.", itemId); + return; + } + SHOWN_REWARDS.merge(stack.getName(), amount, Integer::sum); + } + } } private static int comparePriority(String s) { + s = GEMSTONE_SYMBOLS.matcher(s).replaceAll(""); // Removes the gemstone symbol from the string to make it easier to compare // Puts gemstone powder at the top of the list, then gold and diamond essence, then gemstones by ascending rarity and then whatever else. switch (s) { case "Gemstone Powder" -> { @@ -175,9 +177,8 @@ private static int comparePriority(String s) { /** * Normally, the price is calculated on a per-reward basis as they are obtained. This is what this method does. */ - private static void calculateProfitForItem(Text text, int amount) { - String id = getItemId(text); - DoubleBooleanPair price = ItemUtils.getItemPrice(id); + private static void calculateProfitForItem(String itemId, int amount) { + DoubleBooleanPair price = ItemUtils.getItemPrice(itemId); if (price.rightBoolean()) profit += price.leftDouble() * amount; } @@ -188,7 +189,7 @@ private static void recalculateAll() { profit = 0; ObjectSortedSet> set = SHOWN_REWARDS.object2IntEntrySet(); for (Object2IntMap.Entry entry : set) { - calculateProfitForItem(entry.getKey(), entry.getIntValue()); + calculateProfitForItem(entry.getKey().getString(), entry.getIntValue()); } } @@ -197,14 +198,25 @@ private static void recalculateAll() { */ public static void filterChangeCallback() { SHOWN_REWARDS.clear(); - profit = 0; - ObjectSet> set = ALL_REWARDS.object2IntEntrySet(); - for (Object2IntMap.Entry entry : set) { - if (!SkyblockerConfigManager.get().mining.crystalHollows.powderTrackerFilter.contains(entry.getKey().getString())) { - SHOWN_REWARDS.put(entry.getKey(), entry.getIntValue()); - calculateProfitForItem(entry.getKey(), entry.getIntValue()); + ObjectSet> set = ALL_REWARDS.object2IntEntrySet(); + // The filters are actually item names so that they would look nice and not need a lot of mapping under the screen code + // Here they are converted to item IDs for comparison + List filters = SkyblockerConfigManager.get().mining.crystalHollows.powderTrackerFilter.stream().map(PowderMiningTracker::getItemId).toList(); + for (Object2IntMap.Entry entry : set) { + if (filters.contains(entry.getKey())) continue; + + if (entry.getKey().equals("GEMSTONE_POWDER")) { + SHOWN_REWARDS.put(Text.literal("Gemstone Powder").formatted(Formatting.LIGHT_PURPLE), entry.getIntValue()); + } else { + ItemStack stack = ItemRepository.getItemStack(entry.getKey()); + if (stack == null) { + LOGGER.warn("Item stack for id `{}` is null! This might be caused by failed item repository downloads.", entry.getKey()); + continue; + } + SHOWN_REWARDS.put(stack.getName(), entry.getIntValue()); } } + recalculateAll(); } @Unmodifiable @@ -213,6 +225,8 @@ public static Object2ObjectMap getName2IdMap() { } static { + NAME2ID_MAP.put("Gemstone Powder", "GEMSTONE_POWDER"); // Not an actual item, but since we're using IDs for mapping to colored text we need to have this here + NAME2ID_MAP.put("❤ Rough Ruby Gemstone", "ROUGH_RUBY_GEM"); NAME2ID_MAP.put("❤ Flawed Ruby Gemstone", "FLAWED_RUBY_GEM"); NAME2ID_MAP.put("❤ Fine Ruby Gemstone", "FINE_RUBY_GEM"); @@ -274,7 +288,8 @@ public static Object2ObjectMap getName2IdMap() { NAME2ID_MAP.put("Superlite Motor", "SUPERLITE_MOTOR"); } - private static String getItemId(Text text) { - return NAME2ID_MAP.getOrDefault(text.getString(), ""); + @NotNull + private static String getItemId(String itemName) { + return NAME2ID_MAP.getOrDefault(itemName, ""); } } From 5e0e1e70cab17f61c3eaf7578a095ed4b75c0db9 Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Mon, 25 Nov 2024 02:43:52 +0300 Subject: [PATCH 08/18] Read and write from file and fix incorrect param used in recalculateAll Also a small fix for the regex --- .../PowderFilterConfigScreen.java | 2 +- .../skyblock/dwarven/PowderMiningTracker.java | 56 +++++++++++++++++-- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java b/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java index baeff8956c..59548b2f2e 100644 --- a/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java +++ b/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java @@ -63,7 +63,7 @@ protected void init() { public void saveFilters() { SkyblockerConfigManager.get().mining.crystalHollows.powderTrackerFilter = filters; SkyblockerConfigManager.save(); - PowderMiningTracker.filterChangeCallback(); + PowderMiningTracker.recalculateAll(); } @Override diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java index 0c68e04274..2c03ae57d4 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java @@ -1,5 +1,9 @@ package de.hysky.skyblocker.skyblock.dwarven; +import com.google.gson.JsonElement; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.codecs.UnboundedMapCodec; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; @@ -13,6 +17,7 @@ import it.unimi.dsi.fastutil.doubles.DoubleBooleanPair; import it.unimi.dsi.fastutil.objects.*; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.hud.ChatHud; import net.minecraft.item.ItemStack; @@ -24,6 +29,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.file.Files; +import java.nio.file.Path; import java.text.NumberFormat; import java.util.List; import java.util.regex.Matcher; @@ -34,7 +41,8 @@ public class PowderMiningTracker { private static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Powder Mining Tracker"); private static final Pattern GEMSTONE_SYMBOLS = Pattern.compile("[α☘☠✎✧❁❂❈❤⸕] "); - private static final Pattern REWARD_PATTERN = Pattern.compile(" +(.*?) ?x?(\\d*)"); + private static final Pattern REWARD_PATTERN = Pattern.compile(" {4}(.*?) ?x?([\\d,]*)"); + private static final UnboundedMapCodec REWARDS_CODEC = Codec.unboundedMap(Codec.STRING, Codec.INT); // This constructor takes in a comparator that is triggered to decide where to add the element in the tree map // This causes it to be sorted at all times. This is for rendering them in a sort of easy-to-read manner. private static final Object2IntAVLTreeMap SHOWN_REWARDS = new Object2IntAVLTreeMap<>((o1, o2) -> { @@ -109,9 +117,12 @@ public static void init() { }); ItemPrice.ON_PRICE_UPDATE.register(() -> { - if (isEnabled()) recalculateAll(); + if (isEnabled()) recalculatePrices(); }); + ClientLifecycleEvents.CLIENT_STARTED.register(PowderMiningTracker::loadRewards); + ClientLifecycleEvents.CLIENT_STOPPING.register(PowderMiningTracker::saveRewards); + //TODO: Sort out proper commands for this ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register( literal(SkyblockerMod.NAMESPACE) @@ -185,18 +196,18 @@ private static void calculateProfitForItem(String itemId, int amount) { /** * When the bz/ah prices are updated, this method recalculates the profit for all rewards at once. */ - private static void recalculateAll() { + private static void recalculatePrices() { profit = 0; ObjectSortedSet> set = SHOWN_REWARDS.object2IntEntrySet(); for (Object2IntMap.Entry entry : set) { - calculateProfitForItem(entry.getKey().getString(), entry.getIntValue()); + calculateProfitForItem(getItemId(entry.getKey().getString()), entry.getIntValue()); } } /** * Resets the shown rewards and profit to 0 and recalculates them based on the config filter. */ - public static void filterChangeCallback() { + public static void recalculateAll() { SHOWN_REWARDS.clear(); ObjectSet> set = ALL_REWARDS.object2IntEntrySet(); // The filters are actually item names so that they would look nice and not need a lot of mapping under the screen code @@ -216,7 +227,7 @@ public static void filterChangeCallback() { SHOWN_REWARDS.put(stack.getName(), entry.getIntValue()); } } - recalculateAll(); + recalculatePrices(); } @Unmodifiable @@ -224,6 +235,35 @@ public static Object2ObjectMap getName2IdMap() { return Object2ObjectMaps.unmodifiable(NAME2ID_MAP); } + private static void loadRewards(MinecraftClient client) { + if (Files.notExists(getRewardFilePath())) return; + try { + String jsonString = Files.readString(getRewardFilePath()); + JsonElement json = SkyblockerMod.GSON.fromJson(jsonString, JsonElement.class); + ALL_REWARDS.clear(); + ALL_REWARDS.putAll(REWARDS_CODEC.decode(JsonOps.INSTANCE, json).getOrThrow().getFirst()); + recalculateAll(); + LOGGER.info("Loaded powder mining rewards from file."); + } catch (Exception e) { + LOGGER.error("Failed to load powder mining rewards from file!", e); + } + + } + + private static void saveRewards(MinecraftClient client) { + try { + String jsonString = REWARDS_CODEC.encodeStart(JsonOps.INSTANCE, ALL_REWARDS).getOrThrow().toString(); + if (Files.notExists(getRewardFilePath())) { + Files.createDirectories(getRewardFilePath().getParent()); + Files.createFile(getRewardFilePath()); + } + Files.writeString(getRewardFilePath(), jsonString); + LOGGER.info("Saved powder mining rewards to file."); + } catch (Exception e) { + LOGGER.error("Failed to save powder mining rewards to file!", e); + } + } + static { NAME2ID_MAP.put("Gemstone Powder", "GEMSTONE_POWDER"); // Not an actual item, but since we're using IDs for mapping to colored text we need to have this here @@ -292,4 +332,8 @@ public static Object2ObjectMap getName2IdMap() { private static String getItemId(String itemName) { return NAME2ID_MAP.getOrDefault(itemName, ""); } + + private static Path getRewardFilePath() { + return SkyblockerMod.CONFIG_DIR.resolve("reward-trackers/powder-mining.json"); + } } From 8ee0f11f53a806e0814237709d180a58353781d4 Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Mon, 25 Nov 2024 02:57:39 +0300 Subject: [PATCH 09/18] Change literal text to translatable --- .../config/screens/powdertracker/PowderFilterConfigScreen.java | 2 +- src/main/resources/assets/skyblocker/lang/en_us.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java b/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java index 59548b2f2e..84337d7bd9 100644 --- a/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java +++ b/src/main/java/de/hysky/skyblocker/config/screens/powdertracker/PowderFilterConfigScreen.java @@ -32,7 +32,7 @@ public PowderFilterConfigScreen(@Nullable Screen parent, List allItems) protected void init() { addDrawable((context, mouseX, mouseY, delta) -> { assert client != null; - context.drawCenteredTextWithShadow(client.textRenderer, Text.literal("Shown Items").formatted(Formatting.BOLD), width / 2, (32 - client.textRenderer.fontHeight) / 2, 0xFFFFFF); + context.drawCenteredTextWithShadow(client.textRenderer, Text.translatable("skyblocker.config.mining.crystalHollows.powderTrackerFilter.screenTitle").formatted(Formatting.BOLD), width / 2, (32 - client.textRenderer.fontHeight) / 2, 0xFFFFFF); }); ItemTickList itemTickList = addDrawableChild(new ItemTickList(MinecraftClient.getInstance(), width, height - 96, 32, 24, filters, allItems).init()); //Grid code gratuitously stolen from WaypointsScreen. Same goes for the y and heights above. diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 19ca2876ab..80090353b0 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -562,6 +562,7 @@ "skyblocker.config.mining.crystalHollows.chestHighlighter.color.@Tooltip": "What color the treasure chests / lock pick should be highlighted.", "skyblocker.config.mining.crystalHollows.powderTrackerFilter": "Powder Tracker Shown Items", "skyblocker.config.mining.crystalHollows.powderTrackerFilter.@Tooltip": "Choose which items to show & include in the profit calculation for the powder tracker.", + "skyblocker.config.mining.crystalHollows.powderTrackerFilter.screenTitle": "Shown Items", "skyblocker.config.mining.crystalsHud": "Crystal Hollows Map", "skyblocker.config.mining.crystalsHud.enabled": "Enabled", From 170120fe45bd9ca0d6dea2ac78e9d808309859fa Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:04:24 +0300 Subject: [PATCH 10/18] Extract `ON_PRICE_UPDATE` event to a separate class under the `events` package --- .../events/ItemPriceUpdateEvent.java | 21 +++++++++++++++++++ .../skyblock/dwarven/PowderMiningTracker.java | 4 ++-- .../skyblocker/skyblock/item/ItemPrice.java | 19 ++--------------- .../skyblock/item/tooltip/ItemTooltip.java | 4 ++-- 4 files changed, 27 insertions(+), 21 deletions(-) create mode 100644 src/main/java/de/hysky/skyblocker/events/ItemPriceUpdateEvent.java diff --git a/src/main/java/de/hysky/skyblocker/events/ItemPriceUpdateEvent.java b/src/main/java/de/hysky/skyblocker/events/ItemPriceUpdateEvent.java new file mode 100644 index 0000000000..f641509646 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/events/ItemPriceUpdateEvent.java @@ -0,0 +1,21 @@ +package de.hysky.skyblocker.events; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +@FunctionalInterface +@Environment(EnvType.CLIENT) +public interface ItemPriceUpdateEvent { + void onPriceUpdate(); + + /** + * An event that is fired when all prices are updated. + */ + Event ON_PRICE_UPDATE = EventFactory.createArrayBacked(ItemPriceUpdateEvent.class, listeners -> () -> { + for (ItemPriceUpdateEvent listener : listeners) { + listener.onPriceUpdate(); + } + }); +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java index 2c03ae57d4..cbf781f999 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java @@ -9,7 +9,7 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.events.ChatEvents; import de.hysky.skyblocker.events.HudRenderEvents; -import de.hysky.skyblocker.skyblock.item.ItemPrice; +import de.hysky.skyblocker.events.ItemPriceUpdateEvent; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Location; @@ -116,7 +116,7 @@ public static void init() { context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, Text.literal("Gain: " + NumberFormat.getInstance().format(profit) + " coins").formatted(Formatting.GOLD), 5, y + 10, 0xFFFFFF); }); - ItemPrice.ON_PRICE_UPDATE.register(() -> { + ItemPriceUpdateEvent.ON_PRICE_UPDATE.register(() -> { if (isEnabled()) recalculatePrices(); }); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemPrice.java b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemPrice.java index db1798dbc2..9621802298 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemPrice.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemPrice.java @@ -1,5 +1,6 @@ package de.hysky.skyblocker.skyblock.item; +import de.hysky.skyblocker.events.ItemPriceUpdateEvent; import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; import de.hysky.skyblocker.skyblock.item.tooltip.info.DataTooltipInfoType; import de.hysky.skyblocker.skyblock.item.tooltip.info.TooltipInfoType; @@ -8,8 +9,6 @@ import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.scheduler.MessageScheduler; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; -import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.option.KeyBinding; import net.minecraft.item.ItemStack; @@ -34,15 +33,6 @@ public class ItemPrice { "key.categories.skyblocker" )); - /** - * An event that is fired when all prices are updated. - */ - public static final Event ON_PRICE_UPDATE = EventFactory.createArrayBacked(OnPriceUpdate.class, listeners -> () -> { - for (OnPriceUpdate listener : listeners) { - listener.onPriceUpdate(); - } - }); - public static void itemPriceLookup(ClientPlayerEntity player, @NotNull Slot slot) { ItemStack stack = slot.getStack(); String skyblockApiId = stack.getSkyblockApiId(); @@ -77,7 +67,7 @@ public static void refreshItemPrices(ClientPlayerEntity player) { .map(DataTooltipInfoType::downloadIfEnabled) .toArray(CompletableFuture[]::new) ).thenRun(() -> { - ON_PRICE_UPDATE.invoker().onPriceUpdate(); + ItemPriceUpdateEvent.ON_PRICE_UPDATE.invoker().onPriceUpdate(); player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.helpers.itemPrice.refreshedItemPrices")), false); }).exceptionally(e -> { ItemTooltip.LOGGER.error("[Skyblocker Item Price] Failed to refresh item prices", e); @@ -85,9 +75,4 @@ public static void refreshItemPrices(ClientPlayerEntity player) { return null; }); } - - @FunctionalInterface - public interface OnPriceUpdate { - void onPriceUpdate(); - } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java index 3b08fcaeba..fadff943cc 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java @@ -3,7 +3,7 @@ import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.GeneralConfig; -import de.hysky.skyblocker.skyblock.item.ItemPrice; +import de.hysky.skyblocker.events.ItemPriceUpdateEvent; import de.hysky.skyblocker.skyblock.item.tooltip.adders.CraftPriceTooltip; import de.hysky.skyblocker.skyblock.item.tooltip.info.DataTooltipInfoType; import de.hysky.skyblocker.skyblock.item.tooltip.info.TooltipInfoType; @@ -84,7 +84,7 @@ public static void init() { .map(DataTooltipInfoType.class::cast) .map(DataTooltipInfoType::downloadIfEnabled) .toArray(CompletableFuture[]::new) - ).thenRun(() -> ItemPrice.ON_PRICE_UPDATE.invoker().onPriceUpdate() + ).thenRun(ItemPriceUpdateEvent.ON_PRICE_UPDATE.invoker()::onPriceUpdate ).exceptionally(e -> { LOGGER.error("[Skyblocker] Encountered unknown error while downloading tooltip data", e); return null; From eed32ec3af845600efd1f813758a29b78fc54f17 Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:06:36 +0300 Subject: [PATCH 11/18] Simplify switch to use pattern matching --- .../skyblock/dwarven/PowderMiningTracker.java | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java index cbf781f999..5b5f2ba1a8 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java @@ -162,27 +162,19 @@ private static void incrementReward(String itemName, String itemId, int amount) } } - private static int comparePriority(String s) { - s = GEMSTONE_SYMBOLS.matcher(s).replaceAll(""); // Removes the gemstone symbol from the string to make it easier to compare + private static int comparePriority(String string) { + string = GEMSTONE_SYMBOLS.matcher(string).replaceAll(""); // Removes the gemstone symbol from the string to make it easier to compare // Puts gemstone powder at the top of the list, then gold and diamond essence, then gemstones by ascending rarity and then whatever else. - switch (s) { - case "Gemstone Powder" -> { - return 1; - } - case "Gold Essence" -> { - return 2; - } - case "Diamond Essence" -> { - return 3; - } - default -> { - if (s.startsWith("Rough")) return 4; - if (s.startsWith("Flawed")) return 5; - if (s.startsWith("Fine")) return 6; - if (s.startsWith("Flawless")) return 7; - } - } - return 8; + return switch (string) { + case "Gemstone Powder" -> 1; + case "Gold Essence" -> 2; + case "Diamond Essence" -> 3; + case String s when s.startsWith("Rough") -> 4; + case String s when s.startsWith("Flawed") -> 5; + case String s when s.startsWith("Fine") -> 6; + case String s when s.startsWith("Flawless") -> 7; + default -> 8; + }; } /** From 1aefba4a5aa34a5f9556a317abc9a57bf95664c6 Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:31:51 +0300 Subject: [PATCH 12/18] Extract render and chat message lambdas to methods --- .../skyblock/dwarven/PowderMiningTracker.java | 83 ++++++++++--------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java index 5b5f2ba1a8..591555535d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java @@ -19,7 +19,9 @@ import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.hud.ChatHud; +import net.minecraft.client.render.RenderTickCounter; import net.minecraft.item.ItemStack; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -76,45 +78,8 @@ private static boolean isEnabled() { @Init public static void init() { - ChatEvents.RECEIVE_STRING.register(text -> { - if (Utils.getLocation() != Location.CRYSTAL_HOLLOWS || !isEnabled()) return; - // Reward messages end with a separator like so - if (insideChestMessage && text.equals("▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬")) { - insideChestMessage = false; - return; - } - - if (!insideChestMessage && (text.equals(" CHEST LOCKPICKED ") || (SkyblockerConfigManager.get().mining.crystalHollows.countNaturalChestsInTracker && text.equals(" LOOT CHEST COLLECTED ")))) { - insideChestMessage = true; - return; - } - - if (!insideChestMessage) return; - Matcher matcher = REWARD_PATTERN.matcher(text); - if (!matcher.matches()) return; - String itemName = matcher.group(1); - int amount = NumberUtils.toInt(matcher.group(2).replace(",", ""), 1); - - String itemId = getItemId(itemName); - if (itemId.isEmpty()) { - LOGGER.error("No matching item id for name `{}`. Report this!", itemName); - return; - } - incrementReward(itemName, itemId, amount); - calculateProfitForItem(itemId, amount); - }); - - HudRenderEvents.AFTER_MAIN_HUD.register((context, tickCounter) -> { - if (Utils.getLocation() != Location.CRYSTAL_HOLLOWS || !isEnabled()) return; - int y = MinecraftClient.getInstance().getWindow().getScaledHeight() / 2 - 100; - var set = SHOWN_REWARDS.object2IntEntrySet(); - for (Object2IntMap.Entry entry : set) { - context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, entry.getKey(), 5, y, 0xFFFFFF); - context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, Text.of(String.valueOf(entry.getIntValue())), 10 + MinecraftClient.getInstance().textRenderer.getWidth(entry.getKey()), y, 0xFFFFFF); - y += 10; - } - context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, Text.literal("Gain: " + NumberFormat.getInstance().format(profit) + " coins").formatted(Formatting.GOLD), 5, y + 10, 0xFFFFFF); - }); + ChatEvents.RECEIVE_STRING.register(PowderMiningTracker::onChatMessage); + HudRenderEvents.AFTER_MAIN_HUD.register(PowderMiningTracker::render); ItemPriceUpdateEvent.ON_PRICE_UPDATE.register(() -> { if (isEnabled()) recalculatePrices(); @@ -146,6 +111,34 @@ public static void init() { )); } + private static void onChatMessage(String text) { + if (Utils.getLocation() != Location.CRYSTAL_HOLLOWS || !isEnabled()) return; + // Reward messages end with a separator like so + if (insideChestMessage && text.equals("▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬")) { + insideChestMessage = false; + return; + } + + if (!insideChestMessage && (text.equals(" CHEST LOCKPICKED ") || (SkyblockerConfigManager.get().mining.crystalHollows.countNaturalChestsInTracker && text.equals(" LOOT CHEST COLLECTED ")))) { + insideChestMessage = true; + return; + } + + if (!insideChestMessage) return; + Matcher matcher = REWARD_PATTERN.matcher(text); + if (!matcher.matches()) return; + String itemName = matcher.group(1); + int amount = NumberUtils.toInt(matcher.group(2).replace(",", ""), 1); + + String itemId = getItemId(itemName); + if (itemId.isEmpty()) { + LOGGER.error("No matching item id for name `{}`. Report this!", itemName); + return; + } + incrementReward(itemName, itemId, amount); + calculateProfitForItem(itemId, amount); + } + private static void incrementReward(String itemName, String itemId, int amount) { ALL_REWARDS.mergeInt(itemId, amount, Integer::sum); if (!SkyblockerConfigManager.get().mining.crystalHollows.powderTrackerFilter.contains(itemName)) { @@ -328,4 +321,16 @@ private static String getItemId(String itemName) { private static Path getRewardFilePath() { return SkyblockerMod.CONFIG_DIR.resolve("reward-trackers/powder-mining.json"); } + + private static void render(DrawContext context, RenderTickCounter tickCounter) { + if (Utils.getLocation() != Location.CRYSTAL_HOLLOWS || !isEnabled()) return; + int y = MinecraftClient.getInstance().getWindow().getScaledHeight() / 2 - 100; + var set = SHOWN_REWARDS.object2IntEntrySet(); + for (Object2IntMap.Entry entry : set) { + context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, entry.getKey(), 5, y, 0xFFFFFF); + context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, Text.of(String.valueOf(entry.getIntValue())), 10 + MinecraftClient.getInstance().textRenderer.getWidth(entry.getKey()), y, 0xFFFFFF); + y += 10; + } + context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, Text.literal("Gain: " + NumberFormat.getInstance().format(profit) + " coins").formatted(Formatting.GOLD), 5, y + 10, 0xFFFFFF); + } } From bb13c8866abc4581637149eecf4978653f12ee5c Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Mon, 23 Dec 2024 09:23:04 +0300 Subject: [PATCH 13/18] Add PROFILE_INIT event --- .../skyblocker/events/SkyblockEvents.java | 128 ++++++++++-------- .../java/de/hysky/skyblocker/utils/Utils.java | 5 + 2 files changed, 78 insertions(+), 55 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java b/src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java index 93622d8265..6add9fb657 100644 --- a/src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java +++ b/src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java @@ -9,68 +9,86 @@ @Environment(EnvType.CLIENT) public final class SkyblockEvents { - public static final Event JOIN = EventFactory.createArrayBacked(SkyblockJoin.class, callbacks -> () -> { - for (SkyblockEvents.SkyblockJoin callback : callbacks) { - callback.onSkyblockJoin(); - } - }); + public static final Event JOIN = EventFactory.createArrayBacked(SkyblockJoin.class, callbacks -> () -> { + for (SkyblockEvents.SkyblockJoin callback : callbacks) { + callback.onSkyblockJoin(); + } + }); - public static final Event LEAVE = EventFactory.createArrayBacked(SkyblockLeave.class, callbacks -> () -> { - for (SkyblockLeave callback : callbacks) { - callback.onSkyblockLeave(); - } - }); + public static final Event LEAVE = EventFactory.createArrayBacked(SkyblockLeave.class, callbacks -> () -> { + for (SkyblockLeave callback : callbacks) { + callback.onSkyblockLeave(); + } + }); - public static final Event LOCATION_CHANGE = EventFactory.createArrayBacked(SkyblockLocationChange.class, callbacks -> location -> { - for (SkyblockLocationChange callback : callbacks) { - callback.onSkyblockLocationChange(location); - } - }); + public static final Event LOCATION_CHANGE = EventFactory.createArrayBacked(SkyblockLocationChange.class, callbacks -> location -> { + for (SkyblockLocationChange callback : callbacks) { + callback.onSkyblockLocationChange(location); + } + }); - /** - * Called when the player's Skyblock profile changes. - * - * @implNote This is called upon receiving the chat message for the profile change rather than the exact moment of profile change, so it may be delayed by a few seconds. - */ - public static final Event PROFILE_CHANGE = EventFactory.createArrayBacked(ProfileChange.class, callbacks -> (prev, profile) -> { - for (ProfileChange callback : callbacks) { - callback.onSkyblockProfileChange(prev, profile); - } - }); + /** + * Called when the player's Skyblock profile changes. + * + * @implNote This is called upon receiving the chat message for the profile change rather than the exact moment of profile change, so it may be delayed by a few seconds. + */ + public static final Event PROFILE_CHANGE = EventFactory.createArrayBacked(ProfileChange.class, callbacks -> (prev, profile) -> { + for (ProfileChange callback : callbacks) { + callback.onSkyblockProfileChange(prev, profile); + } + }); - public static final Event PURSE_CHANGE = EventFactory.createArrayBacked(PurseChange.class, callbacks -> (diff, cause) -> { - for (PurseChange callback : callbacks) { - callback.onPurseChange(diff, cause); - } - }); + /** + *

Called when the player's skyblock profile is first detected via chat messages.

+ *

This is useful for initializing data on features that track data for separate profiles separately.

+ * + * @implNote This is called upon receiving the chat message for the profile change rather than the exact moment of profile change, so it may be delayed by a few seconds. + */ + public static final Event PROFILE_INIT = EventFactory.createArrayBacked(ProfileInit.class, callbacks -> profile -> { + for (ProfileInit callback : callbacks) { + callback.onSkyblockProfileInit(profile); + } + }); - @Environment(EnvType.CLIENT) - @FunctionalInterface - public interface SkyblockJoin { - void onSkyblockJoin(); - } + public static final Event PURSE_CHANGE = EventFactory.createArrayBacked(PurseChange.class, callbacks -> (diff, cause) -> { + for (PurseChange callback : callbacks) { + callback.onPurseChange(diff, cause); + } + }); - @Environment(EnvType.CLIENT) - @FunctionalInterface - public interface SkyblockLeave { - void onSkyblockLeave(); - } + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface SkyblockJoin { + void onSkyblockJoin(); + } - @Environment(EnvType.CLIENT) - @FunctionalInterface - public interface SkyblockLocationChange { - void onSkyblockLocationChange(Location location); - } + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface SkyblockLeave { + void onSkyblockLeave(); + } - @Environment(EnvType.CLIENT) - @FunctionalInterface - public interface ProfileChange { - void onSkyblockProfileChange(String prevProfileId, String profileId); - } + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface SkyblockLocationChange { + void onSkyblockLocationChange(Location location); + } - @Environment(EnvType.CLIENT) - @FunctionalInterface - public interface PurseChange { - void onPurseChange(double diff, PurseChangeCause cause); - } + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface ProfileChange { + void onSkyblockProfileChange(String prevProfileId, String profileId); + } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface ProfileInit { + void onSkyblockProfileInit(String profileId); + } + + @Environment(EnvType.CLIENT) + @FunctionalInterface + public interface PurseChange { + void onPurseChange(double diff, PurseChangeCause cause); + } } diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java index 6994b9e3bf..4fe2876d47 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Utils.java +++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java @@ -98,6 +98,8 @@ public class Utils { @NotNull public static double purse = 0; + private static boolean firstProfileUpdate = true; + /** * @implNote The parent text will always be empty, the actual text content is inside the text's siblings. */ @@ -513,6 +515,9 @@ public static boolean onChatMessage(Text text, boolean overlay) { if (!prevProfileId.equals(profileId)) { SkyblockEvents.PROFILE_CHANGE.invoker().onSkyblockProfileChange(prevProfileId, profileId); + } else if (firstProfileUpdate) { + SkyblockEvents.PROFILE_INIT.invoker().onSkyblockProfileInit(profileId); + firstProfileUpdate = false; } MuseumItemCache.tick(profileId); From 39fd2308887f9e72e33c4461ced2af0fda487122 Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Mon, 23 Dec 2024 10:47:03 +0300 Subject: [PATCH 14/18] Add support for multiple profiles --- .../skyblock/dwarven/PowderMiningTracker.java | 73 +++++++++++++------ 1 file changed, 51 insertions(+), 22 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java index 591555535d..78471484f6 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java @@ -10,7 +10,9 @@ import de.hysky.skyblocker.events.ChatEvents; import de.hysky.skyblocker.events.HudRenderEvents; import de.hysky.skyblocker.events.ItemPriceUpdateEvent; +import de.hysky.skyblocker.events.SkyblockEvents; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; +import de.hysky.skyblocker.utils.CodecUtils; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.Utils; @@ -44,7 +46,11 @@ public class PowderMiningTracker { private static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Powder Mining Tracker"); private static final Pattern GEMSTONE_SYMBOLS = Pattern.compile("[α☘☠✎✧❁❂❈❤⸕] "); private static final Pattern REWARD_PATTERN = Pattern.compile(" {4}(.*?) ?x?([\\d,]*)"); - private static final UnboundedMapCodec REWARDS_CODEC = Codec.unboundedMap(Codec.STRING, Codec.INT); + private static final Codec> REWARDS_CODEC = CodecUtils.object2IntMapCodec(Codec.STRING); + // Doesn't matter if the codec outputs a java map instead of a fastutils map, it's only used in #putAll anyway so the contents are copied over + private static final UnboundedMapCodec> ALL_REWARDS_CODEC = Codec.unboundedMap(Codec.STRING, REWARDS_CODEC); + private static final Object2ObjectArrayMap NAME2ID_MAP = new Object2ObjectArrayMap<>(50); + // This constructor takes in a comparator that is triggered to decide where to add the element in the tree map // This causes it to be sorted at all times. This is for rendering them in a sort of easy-to-read manner. private static final Object2IntAVLTreeMap SHOWN_REWARDS = new Object2IntAVLTreeMap<>((o1, o2) -> { @@ -56,9 +62,17 @@ public class PowderMiningTracker { return o1String.compareTo(o2String); }); + /** + * Holds the total reward maps for all accounts and profiles. {@link #currentProfileRewards} is a subset of this map, updated on profile change. + * + * @implNote This is a map from (account uuid + "+" + profile uuid) to itemId/amount map. + */ + private static final Object2ObjectArrayMap> ALL_REWARDS = new Object2ObjectArrayMap<>(); + /** *

- * Holds the total amount of each reward obtained. If any items are filtered out, they are still added to this map but not to the {@link #SHOWN_REWARDS} map. + * Holds the total amount of each reward obtained for the current profile. + * If any items are filtered out, they are still added to this map but not to the {@link #SHOWN_REWARDS} map. * Once the filter is changed, the {@link #SHOWN_REWARDS} map is cleared and recalculated based on this map. *

*

This is similar to how {@link ChatHud#messages} and {@link ChatHud#visibleMessages} behave.

@@ -66,8 +80,7 @@ public class PowderMiningTracker { * @implNote This is a map of item IDs to the amount of that item obtained. */ @SuppressWarnings("JavadocReference") - private static final Object2IntMap ALL_REWARDS = new Object2IntArrayMap<>(); - private static final Object2ObjectArrayMap NAME2ID_MAP = new Object2ObjectArrayMap<>(50); + private static Object2IntMap currentProfileRewards = new Object2IntOpenHashMap<>(); private static boolean insideChestMessage = false; private static double profit = 0; @@ -88,6 +101,9 @@ public static void init() { ClientLifecycleEvents.CLIENT_STARTED.register(PowderMiningTracker::loadRewards); ClientLifecycleEvents.CLIENT_STOPPING.register(PowderMiningTracker::saveRewards); + SkyblockEvents.PROFILE_CHANGE.register(PowderMiningTracker::onProfileChange); + SkyblockEvents.PROFILE_INIT.register(PowderMiningTracker::onProfileInit); + //TODO: Sort out proper commands for this ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register( literal(SkyblockerMod.NAMESPACE) @@ -95,6 +111,8 @@ public static void init() { literal("clearrewards") .executes(context -> { SHOWN_REWARDS.clear(); + currentProfileRewards.clear(); + profit = 0; return 1; }) ) @@ -139,19 +157,28 @@ private static void onChatMessage(String text) { calculateProfitForItem(itemId, amount); } + private static void onProfileChange(String prevProfileId, String newProfileId) { + onProfileInit(newProfileId); + } + + private static void onProfileInit(String profileId) { + if (!isEnabled()) return; + currentProfileRewards = ALL_REWARDS.computeIfAbsent(getCombinedId(profileId), k -> new Object2IntArrayMap<>()); + recalculateAll(); + } + private static void incrementReward(String itemName, String itemId, int amount) { - ALL_REWARDS.mergeInt(itemId, amount, Integer::sum); - if (!SkyblockerConfigManager.get().mining.crystalHollows.powderTrackerFilter.contains(itemName)) { - if (itemId.equals("GEMSTONE_POWDER")) { - SHOWN_REWARDS.merge(Text.literal("Gemstone Powder").formatted(Formatting.LIGHT_PURPLE), amount, Integer::sum); - } else { - ItemStack stack = ItemRepository.getItemStack(itemId); - if (stack == null) { - LOGGER.warn("Item stack for id `{}` is null! This might be caused by failed item repository downloads.", itemId); - return; - } - SHOWN_REWARDS.merge(stack.getName(), amount, Integer::sum); + currentProfileRewards.mergeInt(itemId, amount, Integer::sum); + if (SkyblockerConfigManager.get().mining.crystalHollows.powderTrackerFilter.contains(itemName)) return; + if (itemId.equals("GEMSTONE_POWDER")) { + SHOWN_REWARDS.merge(Text.literal("Gemstone Powder").formatted(Formatting.LIGHT_PURPLE), amount, Integer::sum); + } else { + ItemStack stack = ItemRepository.getItemStack(itemId); + if (stack == null) { + LOGGER.warn("Item stack for id `{}` is null! This might be caused by failed item repository downloads.", itemId); + return; } + SHOWN_REWARDS.merge(stack.getName(), amount, Integer::sum); } } @@ -190,11 +217,11 @@ private static void recalculatePrices() { } /** - * Resets the shown rewards and profit to 0 and recalculates them based on the config filter. + * Resets the shown rewards and profit to 0 and recalculates rewards for the current profile based on the config filter. */ public static void recalculateAll() { SHOWN_REWARDS.clear(); - ObjectSet> set = ALL_REWARDS.object2IntEntrySet(); + ObjectSet> set = currentProfileRewards.object2IntEntrySet(); // The filters are actually item names so that they would look nice and not need a lot of mapping under the screen code // Here they are converted to item IDs for comparison List filters = SkyblockerConfigManager.get().mining.crystalHollows.powderTrackerFilter.stream().map(PowderMiningTracker::getItemId).toList(); @@ -226,20 +253,18 @@ private static void loadRewards(MinecraftClient client) { String jsonString = Files.readString(getRewardFilePath()); JsonElement json = SkyblockerMod.GSON.fromJson(jsonString, JsonElement.class); ALL_REWARDS.clear(); - ALL_REWARDS.putAll(REWARDS_CODEC.decode(JsonOps.INSTANCE, json).getOrThrow().getFirst()); - recalculateAll(); + ALL_REWARDS.putAll(ALL_REWARDS_CODEC.decode(JsonOps.INSTANCE, json).getOrThrow().getFirst()); LOGGER.info("Loaded powder mining rewards from file."); } catch (Exception e) { LOGGER.error("Failed to load powder mining rewards from file!", e); } - } private static void saveRewards(MinecraftClient client) { try { - String jsonString = REWARDS_CODEC.encodeStart(JsonOps.INSTANCE, ALL_REWARDS).getOrThrow().toString(); + String jsonString = ALL_REWARDS_CODEC.encodeStart(JsonOps.INSTANCE, ALL_REWARDS).getOrThrow().toString(); if (Files.notExists(getRewardFilePath())) { - Files.createDirectories(getRewardFilePath().getParent()); + Files.createDirectories(getRewardFilePath().getParent()); // Create all parent directories if they don't exist Files.createFile(getRewardFilePath()); } Files.writeString(getRewardFilePath(), jsonString); @@ -322,6 +347,10 @@ private static Path getRewardFilePath() { return SkyblockerMod.CONFIG_DIR.resolve("reward-trackers/powder-mining.json"); } + private static String getCombinedId(String profileUuid) { + return Utils.getUndashedUuid() + "+" + profileUuid; + } + private static void render(DrawContext context, RenderTickCounter tickCounter) { if (Utils.getLocation() != Location.CRYSTAL_HOLLOWS || !isEnabled()) return; int y = MinecraftClient.getInstance().getWindow().getScaledHeight() / 2 - 100; From 053c1769557960a26739cd734cb6ca06b818a8e9 Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:03:37 +0300 Subject: [PATCH 15/18] Re-add ItemPrice#init with additional documentation --- .../skyblocker/skyblock/item/ItemPrice.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemPrice.java b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemPrice.java index 9621802298..411e1d3e1a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemPrice.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemPrice.java @@ -1,5 +1,6 @@ package de.hysky.skyblocker.skyblock.item; +import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.events.ItemPriceUpdateEvent; import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; import de.hysky.skyblocker.skyblock.item.tooltip.info.DataTooltipInfoType; @@ -33,6 +34,22 @@ public class ItemPrice { "key.categories.skyblocker" )); + /** + *

Crucial init method, do not remove.

+ * + *

This is required due to the way keybindings are registered via Fabric api and lazy static initialization.

+ *

+ * Key bindings are required to be registered before {@link net.minecraft.client.MinecraftClient#options MinecraftClient#options} is initialized. + * This is probably due to how fabric adds key binding options to the key binding options screen. + * Since {@link #ITEM_PRICE_LOOKUP} and {@link #ITEM_PRICE_REFRESH} are static fields, they are initialized lazily, which means they are only initialized when the class is accessed for the first time. + * That first time is generally when the player is already in the game and tries to use the key bindings in a handled screen, which is much later than the possible initialization period. + * This causes an {@link IllegalStateException} to be thrown from {@link net.fabricmc.fabric.impl.client.keybinding.KeyBindingRegistryImpl#registerKeyBinding(KeyBinding) KeyBindingRegistryImpl#registerKeybinding} and the game to crash. + *

+ */ + @SuppressWarnings("UnstableApiUsage") //For the javadoc reference. + @Init + public static void init() {} + public static void itemPriceLookup(ClientPlayerEntity player, @NotNull Slot slot) { ItemStack stack = slot.getStack(); String skyblockApiId = stack.getSkyblockApiId(); From 20c94a2c61c03a836d2597d94cf264e0fa2dd05a Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:20:55 +0300 Subject: [PATCH 16/18] Prevent rendering profit if there are no rewards --- .../hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java index 78471484f6..798aad7858 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java @@ -360,6 +360,6 @@ private static void render(DrawContext context, RenderTickCounter tickCounter) { context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, Text.of(String.valueOf(entry.getIntValue())), 10 + MinecraftClient.getInstance().textRenderer.getWidth(entry.getKey()), y, 0xFFFFFF); y += 10; } - context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, Text.literal("Gain: " + NumberFormat.getInstance().format(profit) + " coins").formatted(Formatting.GOLD), 5, y + 10, 0xFFFFFF); + if (!set.isEmpty()) context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, Text.literal("Gain: " + NumberFormat.getInstance().format(profit) + " coins").formatted(Formatting.GOLD), 5, y + 10, 0xFFFFFF); } } From 074b818830af043342d89b0084fcb8dba00adea5 Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:21:45 +0300 Subject: [PATCH 17/18] Format item amounts when rendering --- .../hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java index 798aad7858..5459d9551c 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PowderMiningTracker.java @@ -357,7 +357,7 @@ private static void render(DrawContext context, RenderTickCounter tickCounter) { var set = SHOWN_REWARDS.object2IntEntrySet(); for (Object2IntMap.Entry entry : set) { context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, entry.getKey(), 5, y, 0xFFFFFF); - context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, Text.of(String.valueOf(entry.getIntValue())), 10 + MinecraftClient.getInstance().textRenderer.getWidth(entry.getKey()), y, 0xFFFFFF); + context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, Text.of(NumberFormat.getInstance().format(entry.getIntValue())), 10 + MinecraftClient.getInstance().textRenderer.getWidth(entry.getKey()), y, 0xFFFFFF); y += 10; } if (!set.isEmpty()) context.drawTextWithShadow(MinecraftClient.getInstance().textRenderer, Text.literal("Gain: " + NumberFormat.getInstance().format(profit) + " coins").formatted(Formatting.GOLD), 5, y + 10, 0xFFFFFF); From 82f13086d18b4119c7ed0375a4ae6d4c8dfe66f8 Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Tue, 24 Dec 2024 07:18:34 +0300 Subject: [PATCH 18/18] Add more documentation --- src/main/java/de/hysky/skyblocker/events/ChatEvents.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/de/hysky/skyblocker/events/ChatEvents.java b/src/main/java/de/hysky/skyblocker/events/ChatEvents.java index 3753770fcd..2c50aeb056 100644 --- a/src/main/java/de/hysky/skyblocker/events/ChatEvents.java +++ b/src/main/java/de/hysky/skyblocker/events/ChatEvents.java @@ -5,12 +5,16 @@ import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.EventFactory; import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Environment(EnvType.CLIENT) public class ChatEvents { /** * This will be called when a game message is received, cancelled or not. + * + * @implNote Not fired when {@code overlay} is {@code true}. See {@link de.hysky.skyblocker.mixins.MessageHandlerMixin#skyblocker$monitorGameMessage(Text, boolean, CallbackInfo) the mixin} for more information. */ + @SuppressWarnings("JavadocReference") public static final Event RECEIVE_TEXT = EventFactory.createArrayBacked(ChatTextEvent.class, listeners -> message -> { for (ChatTextEvent listener : listeners) { listener.onMessage(message); @@ -20,7 +24,10 @@ public class ChatEvents { /** * This will be called when a game message is received, cancelled or not. * This method is called with the result of {@link Text#getString()} to avoid each listener having to call it. + * + * @implNote Not fired when {@code overlay} is {@code true}. See {@link de.hysky.skyblocker.mixins.MessageHandlerMixin#skyblocker$monitorGameMessage(Text, boolean, CallbackInfo) the mixin} for more information. */ + @SuppressWarnings("JavadocReference") public static final Event RECEIVE_STRING = EventFactory.createArrayBacked(ChatStringEvent.class, listeners -> message -> { for (ChatStringEvent listener : listeners) { listener.onMessage(message);