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 extends Selectable> selectableChildren() {
+ return children;
+ }
+
+ @Override
+ public List extends Element> 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);