aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFluboxer <36457056+Fluboxer@users.noreply.github.com>2024-12-28 02:06:12 +0300
committerGitHub <noreply@github.com>2024-12-28 07:06:12 +0800
commit4185b2cc025bf9cc55bd4fcd71650415dc234d2c (patch)
tree6e3d9d329339643121572786e1f2c010d78c39c1
parent7ce731f859febc0786be29b95fd0edc64707f9c9 (diff)
downloadSkyblocker-4185b2cc025bf9cc55bd4fcd71650415dc234d2c.tar.gz
Skyblocker-4185b2cc025bf9cc55bd4fcd71650415dc234d2c.tar.bz2
Skyblocker-4185b2cc025bf9cc55bd4fcd71650415dc234d2c.zip
Bits Helper (#939)
* just saving those before I do something silly * made adding to tooltip work * at this point it is usable * admins why * ABICASE_MODEL * now it should highlight two groups (all and quick sell) not finished yet * refactor * a bit of renaming * removed extra line. That's it * a little bit of refactor. NOT FINISHED * okay it finally works * requested changes. Currently in testing to see if anything broke * rebased + few little changes * Update src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BitsHelper.java Co-authored-by: Kevin <92656833+kevinthegreat1@users.noreply.github.com> * :clown: * Fix TooltipInfoType * Reformat BitsHelper * Clean up fields * More cleanup * Cleanup * Try to clean up fields and local variables --------- Co-authored-by: Kevin <92656833+kevinthegreat1@users.noreply.github.com>
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java11
-rw-r--r--src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java1
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BitsHelper.java396
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/info/TooltipInfoType.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/container/ContainerSolverManager.java4
-rw-r--r--src/main/resources/assets/skyblocker/lang/en_us.json3
7 files changed, 419 insertions, 3 deletions
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java
index 80d96483..94f0fc0c 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java
@@ -26,6 +26,17 @@ public class HelperCategory {
.controller(ConfigUtils::createBooleanController)
.build())
+ // Bits Helper
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("skyblocker.config.helpers.enableBitsHelper"))
+ .description(OptionDescription.of(Text.translatable("skyblocker.config.helpers.enableBitsHelper.@Tooltip")))
+ .binding(defaults.helpers.enableBitsTooltip,
+ () -> config.helpers.enableBitsTooltip,
+ newValue -> config.helpers.enableBitsTooltip = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+
+ // Wardrobe Helper
.option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.helpers.enableWardrobeHelper"))
.description(OptionDescription.of(Text.translatable("skyblocker.config.helpers.enableWardrobeHelper.@Tooltip")))
diff --git a/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java
index b4b15009..2b1944ae 100644
--- a/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java
@@ -9,6 +9,9 @@ public class HelperConfig {
public boolean enableNewYearCakesHelper = true;
@SerialEntry
+ public boolean enableBitsTooltip = true;
+
+ @SerialEntry
public boolean enableWardrobeHelper = true;
@SerialEntry
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java
index 684588ea..2566ea13 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java
@@ -28,6 +28,7 @@ public class TooltipManager {
new TrueHexDyeScreenDisplay(),
new SupercraftReminder(),
ChocolateFactorySolver.INSTANCE,
+ BitsHelper.INSTANCE,
new ReorderHelper(),
new StackingEnchantProgressTooltip(0), //Would be best to have after the lore but the tech doesn't exist for that
new NpcPriceTooltip(1),
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BitsHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BitsHelper.java
new file mode 100644
index 00000000..d3de1cc8
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BitsHelper.java
@@ -0,0 +1,396 @@
+package de.hysky.skyblocker.skyblock.item.tooltip.adders;
+
+import com.mojang.logging.LogUtils;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
+import de.hysky.skyblocker.utils.ItemUtils;
+import de.hysky.skyblocker.utils.container.SimpleContainerSolver;
+import de.hysky.skyblocker.utils.container.TooltipAdder;
+import de.hysky.skyblocker.utils.render.gui.ColorHighlight;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.*;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.screen.ingame.HandledScreen;
+import net.minecraft.item.ItemStack;
+import net.minecraft.screen.ScreenHandler;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.Util;
+import org.intellij.lang.annotations.Language;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+
+import java.text.NumberFormat;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class BitsHelper extends SimpleContainerSolver implements TooltipAdder {
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final Pattern BITS_PATTERN = Pattern.compile("Cost (?<amount>[\\d,]+) Bits");
+ private static final Pattern CATEGORY_PATTERN = Pattern.compile("Click to browse!");
+ @Language("RegExp")
+ private static final String TITLE_PATTERN = ".*(?:Community Shop|Bits Shop).*";
+ private static final NumberFormat DECIMAL_FORMAT = NumberFormat.getInstance(Locale.US);
+ private static final String LOGS_PREFIX = "[Skyblocker Bits Helper] ";
+
+ //region Constants
+ /**
+ * List of items that sell well. No, it is not automated, but that stuff is unlikely to change unless some update
+ * Definition of "good selling item": 300+ daily sales, 12+ hourly sales
+ * I would personally also demand good price per item as filling up all ah slots with catalyst would suck
+ * But newer players would find a good use of it so ima keep my preferences away
+ * Actual on 14.08.2024
+ */
+ private static final List<String> GOOD_SELLING_ITEMS = List.of(
+ "KAT_FLOWER",
+ "KAT_BOUQUET",
+ "HEAT_CORE",
+ "ULTIMATE_CARROT_CANDY_UPGRADE",
+ "TALISMAN_ENRICHMENT_SWAPPER",
+ "GOD_POTION_2",
+ "KISMET_FEATHER",
+ "MATRIARCH_PARFUM",
+ "AUTOPET_RULES_2" // it barely made it here
+ );
+
+ /**
+ * SKYBLOCK_ID, Bits Price
+ * Yes I just hardcoded it. Why work smart when you can work hard?
+ * Those are only from submenus (categories) as normal item is easy to parse
+ * It probably could've been extracted from neu repo or something
+ * Actual on 14.08.2024
+ */
+ private static final Object2IntMap<String> CAT_KAT = Util.make(new Object2IntOpenHashMap<>(), map -> {
+ map.put("KAT_FLOWER", 500);
+ map.put("KAT_BOUQUET", 2500);
+ });
+
+ private static final Object2IntMap<String> CAT_UPGRADE_COMPONENTS = Util.make(new Object2IntOpenHashMap<>(), map -> {
+ map.put("HEAT_CORE", 3000);
+ map.put("HYPER_CATALYST_UPGRADE", 300);
+ map.put("ULTIMATE_CARROT_CANDY_UPGRADE", 8000);
+ map.put("COLOSSAL_EXP_BOTTLE_UPGRADE", 1200);
+ map.put("JUMBO_BACKPACK_UPGRADE", 4000);
+ map.put("MINION_STORAGE_EXPANDER", 1500);
+ });
+
+ private static final Object2IntMap<String> CAT_SACKS = Util.make(new Object2IntOpenHashMap<>(), map -> {
+ map.put("POCKET_SACK_IN_A_SACK", 8000);
+ map.put("LARGE_DUNGEON_SACK", 14000);
+ map.put("RUNE_SACK", 14000);
+ map.put("FLOWER_SACK", 14000);
+ map.put("DWARVEN_MINES_SACK", 14000);
+ map.put("CRYSTAL_HOLLOWS_SACK", 14000);
+ });
+
+ private static final Object2IntMap<String> CAT_ABIPHONE = Util.make(new Object2IntOpenHashMap<>(), map -> {
+ map.put("TRIO_CONTACTS_ADDON", 6450);
+ map.put("ABICASE_SUMSUNG_1", 15000);
+ map.put("ABICASE_SUMSUNG_2", 25000);
+ map.put("ABICASE_REZAR", 26000);
+ map.put("ABICASE_BLUE_AQUA", 17000);
+ map.put("ABICASE_BLUE_BLUE", 17000);
+ map.put("ABICASE_BLUE_GREEN", 17000);
+ map.put("ABICASE_BLUE_RED", 17000);
+ map.put("ABICASE_BLUE_YELLOW", 17000);
+ });
+
+ private static final Object2IntMap<String> CAT_DYES = Util.make(new Object2IntOpenHashMap<>(), map -> {
+ map.put("DYE_PURE_WHITE", 250000);
+ map.put("DYE_PURE_BLACK", 250000);
+ });
+
+ private static final Object2IntMap<String> CAT_ENCHANTS = Util.make(new Object2IntOpenHashMap<>(), map -> {
+ map.put("ENCHANTMENT_HECATOMB_1", 6000);
+ map.put("ENCHANTMENT_EXPERTISE_1", 4000);
+ map.put("ENCHANTMENT_COMPACT_1", 4000);
+ map.put("ENCHANTMENT_CULTIVATING_1", 4000);
+ map.put("ENCHANTMENT_CHAMPION_1", 4000);
+ map.put("ENCHANTMENT_TOXOPHILITE_1", 4000);
+ });
+
+ private static final Object2IntMap<String> CAT_ENRICHMENTS = Util.make(new Object2IntOpenHashMap<>(), map -> {
+ map.put("TALISMAN_ENRICHMENT_SWAPPER", 200);
+ map.put("TALISMAN_ENRICHMENT_WALK_SPEED", 5000);
+ map.put("TALISMAN_ENRICHMENT_INTELLIGENCE", 5000);
+ map.put("TALISMAN_ENRICHMENT_CRITICAL_DAMAGE", 5000);
+ map.put("TALISMAN_ENRICHMENT_CRITICAL_CHANCE", 5000);
+ map.put("TALISMAN_ENRICHMENT_STRENGTH", 5000);
+ map.put("TALISMAN_ENRICHMENT_DEFENSE", 5000);
+ map.put("TALISMAN_ENRICHMENT_HEALTH", 5000);
+ map.put("TALISMAN_ENRICHMENT_MAGIC_FIND", 5000);
+ map.put("TALISMAN_ENRICHMENT_FEROCITY", 5000);
+ map.put("TALISMAN_ENRICHMENT_SEA_CREATURE_CHANCE", 5000);
+ map.put("TALISMAN_ENRICHMENT_ATTACK_SPEED", 5000);
+ });
+
+ private static final Map<String, Map<String, Integer>> CATEGORIES = Map.of(
+ "Kat Items", CAT_KAT,
+ "Upgrade Components", CAT_UPGRADE_COMPONENTS,
+ "Sacks", CAT_SACKS,
+ "Abiphone Supershop", CAT_ABIPHONE,
+ "Dyes", CAT_DYES,
+ "Stacking Enchants", CAT_ENCHANTS,
+ "Enrichments", CAT_ENRICHMENTS
+ );
+ //endregion
+
+ public static final BitsHelper INSTANCE = new BitsHelper();
+
+ private final Map<String, ObjectLongImmutablePair<String>> categoryOutput = new HashMap<>();
+ private int bestSlotIndexSelling = -1; // index of slot that has the best item from "white list" of good selling items (= items that sell quick)
+ private int bestSlotIndexAll = -1; // index of slot that has the best item overall. May has same result as bestSlotIndexSelling - it is intended
+ private boolean megamindInLogs = false; // comedy
+
+ private BitsHelper() {
+ super(TITLE_PATTERN);
+ }
+
+ @Override
+ public List<ColorHighlight> getColors(Int2ObjectMap<ItemStack> slots) {
+ List<ColorHighlight> highlights = new ArrayList<>();
+ BestItemsResult bestItemsResult = calculateBestItems(slots);
+
+ bestSlotIndexSelling = bestItemsResult.bestSlotIndexSelling;
+ bestSlotIndexAll = bestItemsResult.bestSlotIndexAll;
+
+ // If best selling = best overall, only green will appear. This is intended
+ if (bestSlotIndexSelling != -1 && bestSlotIndexSelling == bestSlotIndexAll) {
+ highlights.add(ColorHighlight.green(bestSlotIndexSelling));
+ } else {
+ if (bestSlotIndexSelling != -1) {
+ highlights.add(ColorHighlight.green(bestSlotIndexSelling));
+ }
+ if (bestSlotIndexAll != -1) {
+ highlights.add(ColorHighlight.yellow(bestSlotIndexAll));
+ }
+ }
+ return highlights;
+ }
+
+ @Override
+ public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) {
+ String lore = ItemUtils.concatenateLore(lines);
+ Matcher bitsMatcher = BITS_PATTERN.matcher(lore);
+ long coinsPerBit = 0L;
+ String itemID = "";
+ boolean isGreen = false;
+ if (focusedSlot == null) return;
+
+ if (!CATEGORY_PATTERN.matcher(lore).find()) {
+ if (!bitsMatcher.find()) return;
+
+ long bitsCost = Long.parseLong(bitsMatcher.group("amount").replace(",", ""));
+ double itemCost = ItemUtils.getItemPrice(stack).keyDouble() * stack.getCount();
+
+ if (itemCost == 0) return;
+
+ coinsPerBit = Math.round(itemCost / bitsCost);
+ } else {
+ String outerKey = stack.getName().getString();
+ ObjectLongPair<String> innerMap, innerMapGreen;
+
+ BestItemsResult bestItemsResult = calculateBestItems(getSlots());
+ bestSlotIndexSelling = bestItemsResult.bestSlotIndexSelling;
+ bestSlotIndexAll = bestItemsResult.bestSlotIndexAll;
+ if (bestSlotIndexSelling != -1 && bestSlotIndexSelling == focusedSlot.getIndex()) isGreen = true;
+ innerMap = categoryOutput.get(outerKey);
+ innerMapGreen = categoryOutput.get(outerKey + "GREEN");
+
+ if (innerMap != null) {
+ if (isGreen) { // this is here so green highlighted category won't show yellow stuff
+ itemID = innerMapGreen.left();
+ coinsPerBit = innerMapGreen.rightLong();
+ } else {
+ itemID = innerMap.left();
+ coinsPerBit = innerMap.rightLong();
+ }
+ }
+ }
+ ItemStack foundItemStack = ItemRepository.getItemStack(itemID);
+ if (itemID.isEmpty()) { // a bit dirty, but basically if itemID is empty then it is normal item and NOT category
+ lines.add(Text.empty()
+ .append(Text.literal("Bits Cost: ").formatted(Formatting.AQUA))
+ .append(Text.literal(DECIMAL_FORMAT.format(coinsPerBit) + " Coins per bit").formatted(Formatting.DARK_AQUA)));
+ } else if (foundItemStack != null && isGreen) {
+ lines.add(Text.empty()
+ .append(Text.literal("Bits Cost: ").formatted(Formatting.AQUA))
+ .append(Text.literal(DECIMAL_FORMAT.format(coinsPerBit) + " Coins per bit").formatted(Formatting.DARK_AQUA)));
+ lines.add(Text.literal("From " + foundItemStack.getName().getString()).formatted(Formatting.GREEN));
+ } else if (foundItemStack != null) {
+ lines.add(Text.empty()
+ .append(Text.literal("Bits Cost: ").formatted(Formatting.AQUA))
+ .append(Text.literal(DECIMAL_FORMAT.format(coinsPerBit) + " Coins per bit").formatted(Formatting.DARK_AQUA)));
+ lines.add(Text.literal("From " + foundItemStack.getName().getString()).formatted(Formatting.DARK_AQUA));
+ } else { // if below so it won't clog logs with that cursed enchanted book
+ if (!stack.getName().getString().equals("Stacking Enchants")) LOGGER.warn(LOGS_PREFIX + "ItemStack not found for {}", itemID);
+ lines.add(Text.empty()
+ .append(Text.literal("Bits Cost: ").formatted(Formatting.AQUA))
+ .append(Text.literal(DECIMAL_FORMAT.format(coinsPerBit) + " Coins per bit").formatted(Formatting.DARK_AQUA)));
+ lines.add(Text.literal("From " + itemID).formatted(Formatting.DARK_AQUA));
+ }
+ }
+
+ /**
+ * Why there is no simpler way to get those?
+ * upd: there is, using `focusedSlot.inventory`, but I don't wanna touch code that already works just fine
+ */
+ private Int2ObjectMap<ItemStack> getSlots() {
+ MinecraftClient client = MinecraftClient.getInstance();
+ if (client.currentScreen instanceof HandledScreen<?> screen) {
+ ScreenHandler handler = screen.getScreenHandler();
+
+ Int2ObjectMap<ItemStack> slots = new Int2ObjectOpenHashMap<>();
+ for (int i = 0; i < handler.slots.size(); i++) {
+ slots.put(i, handler.getSlot(i).getStack());
+ }
+ if (!megamindInLogs) {
+ LOGGER.info(LOGS_PREFIX + """
+ No slots?
+ ⠀⣞⢽⢪⢣⢣⢣⢫⡺⡵⣝⡮⣗⢷⢽⢽⢽⣮⡷⡽⣜⣜⢮⢺⣜⢷⢽⢝⡽⣝
+ ⠸⡸⠜⠕⠕⠁⢁⢇⢏⢽⢺⣪⡳⡝⣎⣏⢯⢞⡿⣟⣷⣳⢯⡷⣽⢽⢯⣳⣫⠇
+ ⠀⠀⢀⢀⢄⢬⢪⡪⡎⣆⡈⠚⠜⠕⠇⠗⠝⢕⢯⢫⣞⣯⣿⣻⡽⣏⢗⣗⠏⠀
+ ⠀⠪⡪⡪⣪⢪⢺⢸⢢⢓⢆⢤⢀⠀⠀⠀⠀⠈⢊⢞⡾⣿⡯⣏⢮⠷⠁⠀⠀
+ ⠀⠀⠀⠈⠊⠆⡃⠕⢕⢇⢇⢇⢇⢇⢏⢎⢎⢆⢄⠀⢑⣽⣿⢝⠲⠉⠀⠀⠀⠀
+ ⠀⠀⠀⠀⠀⡿⠂⠠⠀⡇⢇⠕⢈⣀⠀⠁⠡⠣⡣⡫⣂⣿⠯⢪⠰⠂⠀⠀⠀⠀
+ ⠀⠀⠀⠀⡦⡙⡂⢀⢤⢣⠣⡈⣾⡃⠠⠄⠀⡄⢱⣌⣶⢏⢊⠂⠀⠀⠀⠀⠀⠀
+ ⠀⠀⠀⠀⢝⡲⣜⡮⡏⢎⢌⢂⠙⠢⠐⢀⢘⢵⣽⣿⡿⠁⠁⠀⠀⠀⠀⠀⠀⠀
+ ⠀⠀⠀⠀⠨⣺⡺⡕⡕⡱⡑⡆⡕⡅⡕⡜⡼⢽⡻⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+ ⠀⠀⠀⠀⣼⣳⣫⣾⣵⣗⡵⡱⡡⢣⢑⢕⢜⢕⡝⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+ ⠀⠀⠀⣴⣿⣾⣿⣿⣿⡿⡽⡑⢌⠪⡢⡣⣣⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+ ⠀⠀⠀⡟⡾⣿⢿⢿⢵⣽⣾⣼⣘⢸⢸⣞⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+ ⠀⠀⠀⠀⠁⠇⠡⠩⡫⢿⣝⡻⡮⣒⢽⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀""");
+ megamindInLogs = true;
+ }
+ return slots;
+ }
+ return null;
+ }
+
+ private BestItemsResult calculateBestItems(Int2ObjectMap<ItemStack> slots) {
+ long bestCoinsPerBitSelling = 0L;
+ long bestCoinsPerBitAll = 0L;
+ int bestSlotIndexSelling = -1;
+ int bestSlotIndexAll = -1;
+ if (slots == null || slots.isEmpty()) return new BestItemsResult(bestSlotIndexSelling, bestSlotIndexAll, bestCoinsPerBitSelling, bestCoinsPerBitAll);
+
+ // process categories first
+ for (Int2ObjectMap.Entry<ItemStack> entry : slots.int2ObjectEntrySet()) {
+ ItemStack stack = entry.getValue();
+ if (stack == null || stack.isEmpty()) continue;
+
+ if (CATEGORY_PATTERN.matcher(ItemUtils.concatenateLore(ItemUtils.getLore(stack))).find()) {
+ ObjectLongImmutablePair<String> categoryResults = calculateBestInCategory(stack);
+
+ String itemID = categoryResults.left();
+ long coinsPerBit = categoryResults.rightLong();
+
+ if (GOOD_SELLING_ITEMS.contains(itemID) && (coinsPerBit > bestCoinsPerBitSelling)) {
+ bestCoinsPerBitSelling = coinsPerBit;
+ bestSlotIndexSelling = entry.getIntKey();
+ }
+
+ if (coinsPerBit > bestCoinsPerBitAll) {
+ bestCoinsPerBitAll = coinsPerBit;
+ bestSlotIndexAll = entry.getIntKey();
+ }
+ }
+ }
+
+ for (Int2ObjectMap.Entry<ItemStack> entry : slots.int2ObjectEntrySet()) {
+ ItemStack stack = entry.getValue();
+ if (stack == null || stack.isEmpty()) continue;
+
+ String itemId = stack.getSkyblockApiId();
+ String lore = ItemUtils.concatenateLore(ItemUtils.getLore(stack));
+ Matcher bitsMatcher = BITS_PATTERN.matcher(lore);
+ if (!bitsMatcher.find()) continue;
+
+ long bitsCost = Long.parseLong(bitsMatcher.group("amount").replace(",", ""));
+ double itemCost = ItemUtils.getItemPrice(stack).keyDouble() * stack.getCount();
+ if (itemCost == 0 || bitsCost == 0) continue;
+
+ long coinsPerBit = Math.round(itemCost / bitsCost);
+
+ if (GOOD_SELLING_ITEMS.contains(itemId) && (coinsPerBit > bestCoinsPerBitSelling)) {
+ bestCoinsPerBitSelling = coinsPerBit;
+ bestSlotIndexSelling = entry.getIntKey();
+ }
+
+ if (coinsPerBit > bestCoinsPerBitAll) {
+ bestCoinsPerBitAll = coinsPerBit;
+ bestSlotIndexAll = entry.getIntKey();
+ }
+ }
+ return new BestItemsResult(bestSlotIndexSelling, bestSlotIndexAll, bestCoinsPerBitSelling, bestCoinsPerBitAll);
+ }
+
+ private ObjectLongImmutablePair<String> calculateBestInCategory(ItemStack stack) {
+ long bestCoinsPerBitSelling = 0L;
+ long bestCoinsPerBitAll = 0L;
+ String catName = stack.getName().getString();
+ categoryOutput.put(catName, ObjectLongImmutablePair.of("", 0L));
+ categoryOutput.put(catName + "GREEN", ObjectLongImmutablePair.of("", 0L));
+ Object2LongMap<String> categoryResults = processCategory(stack);
+
+ for (Map.Entry<String, Long> categoryEntry : categoryResults.object2LongEntrySet()) {
+ String itemID = categoryEntry.getKey();
+ long coinsPerBit = categoryEntry.getValue();
+
+ if (GOOD_SELLING_ITEMS.contains(itemID) && (coinsPerBit > bestCoinsPerBitSelling)) {
+ categoryOutput.put(catName + "GREEN", ObjectLongImmutablePair.of(itemID, coinsPerBit));
+ bestCoinsPerBitSelling = coinsPerBit;
+ }
+
+ if (coinsPerBit > bestCoinsPerBitAll) {
+ categoryOutput.put(catName, ObjectLongImmutablePair.of(itemID, coinsPerBit));
+ bestCoinsPerBitAll = coinsPerBit;
+ }
+ }
+ return categoryOutput.get(catName);
+ }
+
+ private Object2LongMap<String> processCategory(ItemStack stack) {
+ String categoryName = stack.getName().getString();
+
+ if (CATEGORIES.containsKey(categoryName)) {
+ Object2LongMap<String> results = new Object2LongOpenHashMap<>();
+ Map<String, Integer> category = CATEGORIES.get(categoryName);
+
+ for (Map.Entry<String, Integer> entry : category.entrySet()) {
+ String itemID = entry.getKey();
+ Integer itemBitsPrice = entry.getValue();
+ double itemCost = ItemUtils.getItemPrice(itemID).keyDouble();
+ long coinsPerBit = Math.round(itemCost / itemBitsPrice);
+ results.put(itemID, coinsPerBit);
+ }
+ return results;
+ } else if (categoryName.contains("Fuel Blocks")) {
+ String itemID = "INFERNO_FUEL_BLOCK"; // but I don't know if only 1x offer of 64x offer gets discount too
+ int[] itemBitsPrice = {75, 3600}; // if only 1x gets discount then it doesn't matter as x64 would be ALWAYS better even with it
+ double itemCost = ItemUtils.getItemPrice(itemID).keyDouble(); // TLDR: need blaze slayer 9 players to show their prices
+ long coinsPerBit = (long) (Math.max(itemCost / itemBitsPrice[0], itemCost * 64 / itemBitsPrice[1]));
+ Object2LongMap<String> fuelBlockResult = new Object2LongOpenHashMap<>();
+ fuelBlockResult.put(itemID, coinsPerBit);
+ return fuelBlockResult;
+ } else {
+ LOGGER.warn(LOGS_PREFIX + "Can't recognize category {} from bits shop! Consider reporting this to our discord!", categoryName);
+ }
+ return new Object2LongOpenHashMap<>();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return SkyblockerConfigManager.get().helpers.enableBitsTooltip;
+ }
+
+ @Override
+ public int getPriority() {
+ return 0; // Intended to show first
+ }
+
+ private record BestItemsResult(int bestSlotIndexSelling, int bestSlotIndexAll, long bestCoinsPerBitSelling, long bestCoinsPerBitAll) {}
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/info/TooltipInfoType.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/info/TooltipInfoType.java
index e9e427b8..bff62731 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/info/TooltipInfoType.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/info/TooltipInfoType.java
@@ -23,8 +23,8 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
public interface TooltipInfoType {
DataTooltipInfoType<Object2DoubleMap<String>> NPC = ofData("https://hysky.de/api/npcprice", CodecUtils.object2DoubleMapCodec(Codec.STRING), true, Object2DoubleMap::containsKey, itemTooltip -> itemTooltip.enableNPCPrice);
- DataTooltipInfoType<Object2ObjectMap<String, BazaarProduct>> BAZAAR = ofData("https://hysky.de/api/bazaar", BazaarProduct.MAP_CODEC, false, Object2ObjectMap::containsKey, itemTooltip -> itemTooltip.enableBazaarPrice, itemTooltip -> itemTooltip.enableBazaarPrice || itemTooltip.enableCraftingCost != Craft.OFF || itemTooltip.enableEstimatedItemValue || getConfig().dungeons.dungeonChestProfit.enableProfitCalculator || getConfig().dungeons.dungeonChestProfit.croesusProfit || getConfig().uiAndVisuals.chestValue.enableChestValue || itemTooltip.showEssenceCost, EssenceShopPrice::refreshEssencePrices);
- DataTooltipInfoType<Object2DoubleMap<String>> LOWEST_BINS = ofData("https://hysky.de/api/auctions/lowestbins", CodecUtils.object2DoubleMapCodec(Codec.STRING), false, Object2DoubleMap::containsKey, itemTooltip -> itemTooltip.enableLowestBIN, itemTooltip -> itemTooltip.enableLowestBIN || itemTooltip.enableCraftingCost != Craft.OFF || itemTooltip.enableEstimatedItemValue || getConfig().dungeons.dungeonChestProfit.enableProfitCalculator || getConfig().dungeons.dungeonChestProfit.croesusProfit || getConfig().uiAndVisuals.chestValue.enableChestValue);
+ DataTooltipInfoType<Object2ObjectMap<String, BazaarProduct>> BAZAAR = ofData("https://hysky.de/api/bazaar", BazaarProduct.MAP_CODEC, false, Object2ObjectMap::containsKey, itemTooltip -> itemTooltip.enableBazaarPrice, itemTooltip -> itemTooltip.enableBazaarPrice || itemTooltip.enableCraftingCost != Craft.OFF || itemTooltip.enableEstimatedItemValue || getConfig().dungeons.dungeonChestProfit.enableProfitCalculator || getConfig().dungeons.dungeonChestProfit.croesusProfit || getConfig().uiAndVisuals.chestValue.enableChestValue || SkyblockerConfigManager.get().helpers.enableBitsTooltip || itemTooltip.showEssenceCost, EssenceShopPrice::refreshEssencePrices);
+ DataTooltipInfoType<Object2DoubleMap<String>> LOWEST_BINS = ofData("https://hysky.de/api/auctions/lowestbins", CodecUtils.object2DoubleMapCodec(Codec.STRING), false, Object2DoubleMap::containsKey, itemTooltip -> itemTooltip.enableLowestBIN, itemTooltip -> itemTooltip.enableLowestBIN || itemTooltip.enableCraftingCost != Craft.OFF || itemTooltip.enableEstimatedItemValue || getConfig().dungeons.dungeonChestProfit.enableProfitCalculator || getConfig().dungeons.dungeonChestProfit.croesusProfit || getConfig().uiAndVisuals.chestValue.enableChestValue || SkyblockerConfigManager.get().helpers.enableBitsTooltip);
DataTooltipInfoType<Object2DoubleMap<String>> ONE_DAY_AVERAGE = ofData("https://hysky.de/api/auctions/lowestbins/average/1day.json", CodecUtils.object2DoubleMapCodec(Codec.STRING), false, Object2DoubleMap::containsKey, itemTooltip -> itemTooltip.enableAvgBIN, itemTooltip -> itemTooltip.enableAvgBIN && itemTooltip.avg != GeneralConfig.Average.THREE_DAY);
DataTooltipInfoType<Object2DoubleMap<String>> THREE_DAY_AVERAGE = ofData("https://hysky.de/api/auctions/lowestbins/average/3day.json", CodecUtils.object2DoubleMapCodec(Codec.STRING), false, Object2DoubleMap::containsKey, itemTooltip -> itemTooltip.enableAvgBIN, itemTooltip -> itemTooltip.enableAvgBIN && itemTooltip.avg != GeneralConfig.Average.ONE_DAY || getConfig().uiAndVisuals.searchOverlay.enableAuctionHouse);
DataTooltipInfoType<Object2IntMap<String>> MOTES = ofData("https://hysky.de/api/motesprice", CodecUtils.object2IntMapCodec(Codec.STRING), true, Object2IntMap::containsKey, itemTooltip -> itemTooltip.enableMotesPrice, itemTooltip -> itemTooltip.enableMotesPrice && Utils.isInTheRift());
diff --git a/src/main/java/de/hysky/skyblocker/utils/container/ContainerSolverManager.java b/src/main/java/de/hysky/skyblocker/utils/container/ContainerSolverManager.java
index 668faf2c..dbe1f94f 100644
--- a/src/main/java/de/hysky/skyblocker/utils/container/ContainerSolverManager.java
+++ b/src/main/java/de/hysky/skyblocker/utils/container/ContainerSolverManager.java
@@ -18,6 +18,7 @@ import de.hysky.skyblocker.skyblock.dwarven.CommissionHighlight;
import de.hysky.skyblocker.skyblock.experiment.ChronomatronSolver;
import de.hysky.skyblocker.skyblock.experiment.SuperpairsSolver;
import de.hysky.skyblocker.skyblock.experiment.UltrasequencerSolver;
+import de.hysky.skyblocker.skyblock.item.tooltip.adders.BitsHelper;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.render.gui.ColorHighlight;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@@ -51,7 +52,8 @@ public class ContainerSolverManager {
new NewYearCakeBagHelper(),
NewYearCakesHelper.INSTANCE,
ChocolateFactorySolver.INSTANCE,
- new ReorderHelper()
+ new ReorderHelper(),
+ BitsHelper.INSTANCE
};
private static ContainerSolver currentSolver = null;
private static List<ColorHighlight> highlights;
diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json
index f980869d..f1575d02 100644
--- a/src/main/resources/assets/skyblocker/lang/en_us.json
+++ b/src/main/resources/assets/skyblocker/lang/en_us.json
@@ -381,6 +381,9 @@
"skyblocker.config.helpers.enableNewYearCakesHelper": "Enable New Year Cakes Helper",
"skyblocker.config.helpers.enableNewYearCakesHelper.@Tooltip": "Highlights the missing new year cakes green and the cakes you have already red.\n\nRequires you to open your cake bag at least once to work.",
+ "skyblocker.config.helpers.enableBitsHelper": "Highlight best Community Store (Bits Shop) offer",
+ "skyblocker.config.helpers.enableBitsHelper.@Tooltip": "Highlights best offers in Community Store (Bits Shop). Also shows coins per bit. Green if best offer that is in list of items that quickly sell. Yellow for best offer overall - may include stuff that sells once in a year. \n\nBoth may include price manipulated items - please use common sense or at least 1day/3day avg tooltip",
+
"skyblocker.config.helpers.enableWardrobeHelper": "Enable Wardrobe Helper",
"skyblocker.config.helpers.enableWardrobeHelper.@Tooltip": "Allows changing armor from the wardrobe by pressing hotbar keys 1-9 while the menu is open. The corresponding armor set in the wardrobe menu will be equipped.",