aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky/skyblocker/skyblock
diff options
context:
space:
mode:
authorKevin <92656833+kevinthegreat1@users.noreply.github.com>2024-06-10 12:12:29 +0800
committerGitHub <noreply@github.com>2024-06-10 12:12:29 +0800
commitc29b55bc64fdf8717b42f1a5f7e0d18895007fb3 (patch)
tree382a7d4f3fa4b40fcbfe2c752cf581c36382bc52 /src/main/java/de/hysky/skyblocker/skyblock
parentec1c0104a17d9e3a5741efa38528d628b53d940d (diff)
parent48430e36a87c09e033c0bd43e65b70bbac0e2664 (diff)
downloadSkyblocker-c29b55bc64fdf8717b42f1a5f7e0d18895007fb3.tar.gz
Skyblocker-c29b55bc64fdf8717b42f1a5f7e0d18895007fb3.tar.bz2
Skyblocker-c29b55bc64fdf8717b42f1a5f7e0d18895007fb3.zip
Merge pull request #735 from Emirlol/tooltips-galore
Tooltip refactors
Diffstat (limited to 'src/main/java/de/hysky/skyblocker/skyblock')
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java320
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotText.java21
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextAdder.java64
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextManager.java68
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/slottext/TextPosition.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/AttributeShardAdder.java (renamed from src/main/java/de/hysky/skyblocker/skyblock/item/AttributeShards.java)48
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CatacombsLevelAdder.java92
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EnchantmentLevelAdder.java46
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/MinionLevelAdder.java31
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java28
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PrehistoricEggAdder.java34
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/RancherBootsSpeedAdder.java36
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java34
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkyblockLevelAdder.java29
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java96
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java355
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipAdder.java48
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java72
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AccessoryTooltip.java43
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AvgBinTooltip.java63
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BazaarPriceTooltip.java57
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ColorTooltip.java135
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/DungeonQualityTooltip.java57
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LBinTooltip.java40
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LineSmoothener.java32
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MotesTooltip.java50
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java49
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java28
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ObtainedDateTooltip.java72
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/SupercraftReminder.java30
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/itemlist/ResultButtonWidget.java34
36 files changed, 1497 insertions, 642 deletions
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java b/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java
index 7b36dd78..908525e1 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java
@@ -79,7 +79,7 @@ public class ChestValue {
}
String name = stack.getName().getString();
- String id = ItemTooltip.getInternalNameFromNBT(stack, false);
+ String id = stack.getSkyblockApiId();
//Regular item price
if (id != null) {
@@ -158,7 +158,7 @@ public class ChestValue {
continue;
}
- String id = ItemTooltip.getInternalNameFromNBT(stack, false);
+ String id = stack.getSkyblockApiId();
if (id != null) {
DoubleBooleanPair priceData = ItemUtils.getItemPrice(id);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java b/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java
index 042b126b..a8155b43 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java
@@ -1,7 +1,6 @@
package de.hysky.skyblocker.skyblock;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
-import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.render.RenderHelper;
@@ -27,7 +26,7 @@ public class TeleportOverlay {
private static void render(WorldRenderContext wrc) {
if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().uiAndVisuals.teleportOverlay.enableTeleportOverlays && client.player != null && client.world != null) {
ItemStack heldItem = client.player.getMainHandStack();
- String itemId = ItemTooltip.getInternalNameFromNBT(heldItem, true);
+ String itemId = heldItem.getSkyblockId();
NbtCompound customData = ItemUtils.getCustomData(heldItem);
if (itemId != null) {
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java
index fd69d886..557cb6c9 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java
@@ -295,8 +295,8 @@ public class AuctionBrowserScreen extends AbstractCustomHypixelGUI<AuctionHouseS
String coins = split[1].replace(",", "").replace("coins", "").trim();
try {
long parsed = Long.parseLong(coins);
- String name = ItemTooltip.getInternalNameFromNBT(stack, false);
- String internalID = ItemTooltip.getInternalNameFromNBT(stack, true);
+ String name = stack.getSkyblockApiId();
+ String internalID = stack.getSkyblockId();
String neuName = name;
if (name == null || internalID == null) break;
if (name.startsWith("ISSHINY_")) {
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java
index e04e632a..d33a83e9 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java
@@ -1,20 +1,18 @@
package de.hysky.skyblocker.skyblock.chocolatefactory;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
-import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
+import de.hysky.skyblocker.skyblock.item.tooltip.adders.LineSmoothener;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.RegexUtils;
+import de.hysky.skyblocker.utils.RomanNumerals;
import de.hysky.skyblocker.utils.render.gui.ColorHighlight;
import de.hysky.skyblocker.utils.render.gui.ContainerSolver;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
-import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.screen.ingame.GenericContainerScreen;
-import net.minecraft.client.item.TooltipType;
-import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
+import net.minecraft.screen.slot.Slot;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
@@ -46,10 +44,27 @@ public class ChocolateFactorySolver extends ContainerSolver {
private static boolean canPrestige = false;
private static boolean reachedMaxPrestige = false;
private static double timeTowerMultiplier = -1.0;
+ private static boolean isTimeTowerMaxed = false;
private static boolean isTimeTowerActive = false;
+ private static int bestUpgrade = -1;
+ private static int bestAffordableUpgrade = -1;
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#,###.#", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
- private static ItemStack bestUpgrade = null;
- private static ItemStack bestAffordableUpgrade = null;
+
+ @Override
+ protected void reset() {
+ cpsIncreaseFactors.clear();
+ totalChocolate = -1L;
+ totalCps = -1.0;
+ totalCpsMultiplier = -1.0;
+ requiredUntilNextPrestige = -1L;
+ canPrestige = false;
+ reachedMaxPrestige = false;
+ timeTowerMultiplier = -1.0;
+ isTimeTowerMaxed = false;
+ isTimeTowerActive = false;
+ bestUpgrade = -1;
+ bestAffordableUpgrade = -1;
+ }
//Slots, for ease of maintenance rather than using magic numbers everywhere.
private static final byte RABBITS_START = 28;
@@ -63,8 +78,7 @@ public class ChocolateFactorySolver extends ContainerSolver {
private static final byte STRAY_RABBIT_END = 26;
public ChocolateFactorySolver() {
- super("^Chocolate Factory$");
- ItemTooltipCallback.EVENT.register(ChocolateFactorySolver::handleTooltip);
+ super("^Chocolate Factory$"); //There are multiple screens that fit the pattern `^Chocolate Factory`, so the $ is required
}
@Override
@@ -82,7 +96,7 @@ public class ChocolateFactorySolver extends ContainerSolver {
if (totalChocolate <= 0 || cpsIncreaseFactors.isEmpty()) return highlights; //Something went wrong or there's nothing we can afford.
Rabbit bestRabbit = cpsIncreaseFactors.getFirst();
- bestUpgrade = bestRabbit.itemStack;
+ bestUpgrade = bestRabbit.slot;
if (bestRabbit.cost <= totalChocolate) {
highlights.add(ColorHighlight.green(bestRabbit.slot));
return highlights;
@@ -91,7 +105,7 @@ public class ChocolateFactorySolver extends ContainerSolver {
for (Rabbit rabbit : cpsIncreaseFactors.subList(1, cpsIncreaseFactors.size())) {
if (rabbit.cost <= totalChocolate) {
- bestAffordableUpgrade = rabbit.itemStack;
+ bestAffordableUpgrade = rabbit.slot;
highlights.add(ColorHighlight.green(rabbit.slot));
break;
}
@@ -143,7 +157,8 @@ public class ChocolateFactorySolver extends ContainerSolver {
}
//Time Tower is in slot 39
- timeTowerMultiplier = romanToDecimal(StringUtils.substringAfterLast(slots.get(TIME_TOWER_SLOT).getName().getString(), ' ')) / 10.0; //The name holds the level, which is multiplier * 10 in roman numerals
+ isTimeTowerMaxed = StringUtils.substringAfterLast(slots.get(TIME_TOWER_SLOT).getName().getString(), ' ').equals("XV");
+ timeTowerMultiplier = RomanNumerals.romanToDecimal(StringUtils.substringAfterLast(slots.get(TIME_TOWER_SLOT).getName().getString(), ' ')) / 10.0; //The name holds the level, which is multiplier * 10 in roman numerals
Matcher timeTowerStatusMatcher = TIME_TOWER_STATUS_PATTERN.matcher(getConcatenatedLore(slots.get(TIME_TOWER_SLOT)));
if (timeTowerStatusMatcher.find()) {
isTimeTowerActive = timeTowerStatusMatcher.group(1).equals("ACTIVE");
@@ -153,130 +168,6 @@ public class ChocolateFactorySolver extends ContainerSolver {
cpsIncreaseFactors.sort(Comparator.comparingDouble(rabbit -> rabbit.cost() / rabbit.cpsIncrease())); //Ascending order, lower = better
}
- private static void handleTooltip(ItemStack stack, Item.TooltipContext tooltipContext, TooltipType tooltipType, List<Text> lines) {
- if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableChocolateFactoryHelper) return;
- if (!(MinecraftClient.getInstance().currentScreen instanceof GenericContainerScreen screen) || !screen.getTitle().getString().equals("Chocolate Factory")) return;
-
- int lineIndex = lines.size();
- //This boolean is used to determine if we should add a smooth line to separate the added information from the rest of the tooltip.
- //It should be set to true if there's any information added, false otherwise.
- boolean shouldAddLine = false;
-
- String lore = concatenateLore(lines);
- Matcher costMatcher = COST_PATTERN.matcher(lore);
- OptionalLong cost = RegexUtils.getLongFromMatcher(costMatcher);
- //Available on all items with a chocolate cost
- if (cost.isPresent()) shouldAddLine = addUpgradeTimerToLore(lines, cost.getAsLong());
-
- //Prestige item
- if (stack.isOf(Items.DROPPER)) {
- shouldAddLine = addPrestigeTimerToLore(lines) || shouldAddLine;
- }
- //Time tower
- else if (stack.isOf(Items.CLOCK)) {
- shouldAddLine = addTimeTowerStatsToLore(lines) || shouldAddLine;
- }
- //Rabbits
- else if (stack.isOf(Items.PLAYER_HEAD)) {
- shouldAddLine = addRabbitStatsToLore(lines, stack) || shouldAddLine;
- }
-
- //This is an ArrayList, so this operation is probably not very efficient, but logically it's pretty much the only way I can think of
- if (shouldAddLine) lines.add(lineIndex, ItemTooltip.createSmoothLine());
- }
-
- private static boolean addUpgradeTimerToLore(List<Text> lines, long cost) {
- if (totalChocolate < 0L || totalCps < 0.0) return false;
- lines.add(Text.empty()
- .append(Text.literal("Time until upgrade: ").formatted(Formatting.GRAY))
- .append(formatTime((cost - totalChocolate) / totalCps)));
- return true;
- }
-
- private static boolean addPrestigeTimerToLore(List<Text> lines) {
- if (totalCps < 0.0 || reachedMaxPrestige) return false;
- if (requiredUntilNextPrestige > 0 && !canPrestige) {
- lines.add(Text.empty()
- .append(Text.literal("Chocolate until next prestige: ").formatted(Formatting.GRAY))
- .append(Text.literal(DECIMAL_FORMAT.format(requiredUntilNextPrestige)).formatted(Formatting.GOLD)));
- }
- lines.add(Text.empty() //Keep this outside of the `if` to match the format of the upgrade tooltips, that say "Time until upgrade: Now" when it's possible
- .append(Text.literal("Time until next prestige: ").formatted(Formatting.GRAY))
- .append(formatTime(requiredUntilNextPrestige / totalCps)));
- return true;
- }
-
- private static boolean addTimeTowerStatsToLore(List<Text> lines) {
- if (totalCps < 0.0 || totalCpsMultiplier < 0.0 || timeTowerMultiplier < 0.0) return false;
- lines.add(Text.literal("Current stats:").formatted(Formatting.GRAY));
- lines.add(Text.empty()
- .append(Text.literal(" CPS increase: ").formatted(Formatting.GRAY))
- .append(Text.literal(DECIMAL_FORMAT.format(totalCps / totalCpsMultiplier * timeTowerMultiplier)).formatted(Formatting.GOLD)));
- lines.add(Text.empty()
- .append(Text.literal(" CPS when active: ").formatted(Formatting.GRAY))
- .append(Text.literal(DECIMAL_FORMAT.format(isTimeTowerActive ? totalCps : totalCps / totalCpsMultiplier * (timeTowerMultiplier + totalCpsMultiplier))).formatted(Formatting.GOLD)));
- if (timeTowerMultiplier < 1.5) {
- lines.add(Text.literal("Stats after upgrade:").formatted(Formatting.GRAY));
- lines.add(Text.empty()
- .append(Text.literal(" CPS increase: ").formatted(Formatting.GRAY))
- .append(Text.literal(DECIMAL_FORMAT.format(totalCps / (totalCpsMultiplier) * (timeTowerMultiplier + 0.1))).formatted(Formatting.GOLD)));
- lines.add(Text.empty()
- .append(Text.literal(" CPS when active: ").formatted(Formatting.GRAY))
- .append(Text.literal(DECIMAL_FORMAT.format(isTimeTowerActive ? totalCps / totalCpsMultiplier * (totalCpsMultiplier + 0.1) : totalCps / totalCpsMultiplier * (timeTowerMultiplier + 0.1 + totalCpsMultiplier))).formatted(Formatting.GOLD)));
- }
- return true;
- }
-
- private static boolean addRabbitStatsToLore(List<Text> lines, ItemStack stack) {
- if (cpsIncreaseFactors.isEmpty()) return false;
- boolean changed = false;
- for (Rabbit rabbit : cpsIncreaseFactors) {
- if (rabbit.itemStack != stack) continue;
- changed = true;
- lines.add(Text.empty()
- .append(Text.literal("CPS Increase: ").formatted(Formatting.GRAY))
- .append(Text.literal(DECIMAL_FORMAT.format(rabbit.cpsIncrease)).formatted(Formatting.GOLD)));
-
- lines.add(Text.empty()
- .append(Text.literal("Cost per CPS: ").formatted(Formatting.GRAY))
- .append(Text.literal(DECIMAL_FORMAT.format(rabbit.cost / rabbit.cpsIncrease)).formatted(Formatting.GOLD)));
-
- if (rabbit.itemStack == bestUpgrade) {
- if (rabbit.cost <= totalChocolate) {
- lines.add(Text.literal("Best upgrade").formatted(Formatting.GREEN));
- } else {
- lines.add(Text.literal("Best upgrade, can't afford").formatted(Formatting.YELLOW));
- }
- } else if (rabbit.itemStack == bestAffordableUpgrade && rabbit.cost <= totalChocolate) {
- lines.add(Text.literal("Best upgrade you can afford").formatted(Formatting.GREEN));
- }
- }
- return changed;
- }
-
- private static MutableText formatTime(double seconds) {
- seconds = Math.ceil(seconds);
- if (seconds <= 0) return Text.literal("Now").formatted(Formatting.GREEN);
-
- StringBuilder builder = new StringBuilder();
- if (seconds >= 86400) {
- builder.append((int) (seconds / 86400)).append("d ");
- seconds %= 86400;
- }
- if (seconds >= 3600) {
- builder.append((int) (seconds / 3600)).append("h ");
- seconds %= 3600;
- }
- if (seconds >= 60) {
- builder.append((int) (seconds / 60)).append("m ");
- seconds %= 60;
- }
- if (seconds >= 1) {
- builder.append((int) seconds).append("s");
- }
- return Text.literal(builder.toString()).formatted(Formatting.GOLD);
- }
-
/**
* Utility method.
*/
@@ -317,7 +208,7 @@ public class ChocolateFactorySolver extends ContainerSolver {
OptionalInt cost = RegexUtils.getIntFromMatcher(costMatcher, multiplierIncreaseMatcher.hasMatch() ? multiplierIncreaseMatcher.end() : 0); //Cost comes after the multiplier line
if (cost.isEmpty()) return Optional.empty();
- return Optional.of(new Rabbit(totalCps / totalCpsMultiplier * (nextCpsMultiplier.getAsDouble() - currentCpsMultiplier.getAsDouble()), cost.getAsInt(), COACH_SLOT, coachItem));
+ return Optional.of(new Rabbit(totalCps / totalCpsMultiplier * (nextCpsMultiplier.getAsDouble() - currentCpsMultiplier.getAsDouble()), cost.getAsInt(), COACH_SLOT));
}
private static Optional<Rabbit> getRabbit(ItemStack item, int slot) {
@@ -334,7 +225,7 @@ public class ChocolateFactorySolver extends ContainerSolver {
Matcher costMatcher = COST_PATTERN.matcher(lore);
OptionalInt cost = RegexUtils.getIntFromMatcher(costMatcher, cpsMatcher.hasMatch() ? cpsMatcher.end() : 0); //Cost comes after the cps line
if (cost.isEmpty()) return Optional.empty();
- return Optional.of(new Rabbit(nextCps.getAsInt() - currentCps.getAsInt(), cost.getAsInt(), slot, item));
+ return Optional.of(new Rabbit(nextCps.getAsInt() - currentCps.getAsInt(), cost.getAsInt(), slot));
}
private static Optional<ColorHighlight> getPrestigeHighlight() {
@@ -356,28 +247,137 @@ public class ChocolateFactorySolver extends ContainerSolver {
return highlights;
}
- private record Rabbit(double cpsIncrease, int cost, int slot, ItemStack itemStack) {
- }
+ private record Rabbit(double cpsIncrease, int cost, int slot) { }
+
+ public static final class Tooltip extends TooltipAdder {
+ public Tooltip() {
+ super("^Chocolate Factory$", 0); //The priority doesn't really matter here as this is the only tooltip adder for the Chocolate Factory.
+ }
+
+ @Override
+ public void addToTooltip(List<Text> lines, Slot focusedSlot) {
+ if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableChocolateFactoryHelper) return;
+
+ int lineIndex = lines.size();
+ //This boolean is used to determine if we should add a smooth line to separate the added information from the rest of the tooltip.
+ //It should be set to true if there's any information added, false otherwise.
+ boolean shouldAddLine = false;
+
+ String lore = concatenateLore(lines);
+ Matcher costMatcher = COST_PATTERN.matcher(lore);
+ OptionalLong cost = RegexUtils.getLongFromMatcher(costMatcher);
+ //Available on all items with a chocolate cost
+ if (cost.isPresent()) shouldAddLine |= addUpgradeTimerToLore(lines, cost.getAsLong());
+
+ int index = focusedSlot.id;
+
+ //Prestige item
+ if (index == PRESTIGE_SLOT) {
+ shouldAddLine |= addPrestigeTimerToLore(lines);
+ }
+ //Time tower
+ else if (index == TIME_TOWER_SLOT) {
+ shouldAddLine |= addTimeTowerStatsToLore(lines);
+ }
+ //Rabbits
+ else if (index == COACH_SLOT || (index >= RABBITS_START && index <= RABBITS_END)) {
+ shouldAddLine |= addRabbitStatsToLore(lines, index);
+ }
+
+ //This is an ArrayList, so this operation is probably not very efficient, but logically it's pretty much the only way I can think of
+ if (shouldAddLine) lines.add(lineIndex, LineSmoothener.createSmoothLine());
+ }
+
+ private static boolean addUpgradeTimerToLore(List<Text> lines, long cost) {
+ if (totalChocolate < 0L || totalCps < 0.0) return false;
+ lines.add(Text.empty()
+ .append(Text.literal("Time until upgrade: ").formatted(Formatting.GRAY))
+ .append(formatTime((cost - totalChocolate) / totalCps)));
+ return true;
+ }
- //Perhaps the part below can go to a separate file later on, but I couldn't find a proper name for the class, so they're staying here.
- private static final Map<Character, Integer> romanMap = Map.of(
- 'I', 1,
- 'V', 5,
- 'X', 10,
- 'L', 50,
- 'C', 100,
- 'D', 500,
- 'M', 1000
- );
-
- public static int romanToDecimal(String romanNumeral) {
- int decimal = 0;
- int lastNumber = 0;
- for (int i = romanNumeral.length() - 1; i >= 0; i--) {
- char ch = romanNumeral.charAt(i);
- decimal = romanMap.get(ch) >= lastNumber ? decimal + romanMap.get(ch) : decimal - romanMap.get(ch);
- lastNumber = romanMap.get(ch);
+ private static boolean addPrestigeTimerToLore(List<Text> lines) {
+ if (totalCps < 0.0 || reachedMaxPrestige) return false;
+ if (requiredUntilNextPrestige > 0 && !canPrestige) {
+ lines.add(Text.empty()
+ .append(Text.literal("Chocolate until next prestige: ").formatted(Formatting.GRAY))
+ .append(Text.literal(DECIMAL_FORMAT.format(requiredUntilNextPrestige)).formatted(Formatting.GOLD)));
+ }
+ lines.add(Text.empty() //Keep this outside of the `if` to match the format of the upgrade tooltips, that say "Time until upgrade: Now" when it's possible
+ .append(Text.literal("Time until next prestige: ").formatted(Formatting.GRAY))
+ .append(formatTime(requiredUntilNextPrestige / totalCps)));
+ return true;
+ }
+
+ private static boolean addTimeTowerStatsToLore(List<Text> lines) {
+ if (totalCps < 0.0 || totalCpsMultiplier < 0.0 || timeTowerMultiplier < 0.0) return false;
+ lines.add(Text.literal("Current stats:").formatted(Formatting.GRAY));
+ lines.add(Text.empty()
+ .append(Text.literal(" CPS increase: ").formatted(Formatting.GRAY))
+ .append(Text.literal(DECIMAL_FORMAT.format(totalCps / totalCpsMultiplier * timeTowerMultiplier)).formatted(Formatting.GOLD)));
+ lines.add(Text.empty()
+ .append(Text.literal(" CPS when active: ").formatted(Formatting.GRAY))
+ .append(Text.literal(DECIMAL_FORMAT.format(isTimeTowerActive ? totalCps : totalCps / totalCpsMultiplier * (timeTowerMultiplier + totalCpsMultiplier))).formatted(Formatting.GOLD)));
+ if (!isTimeTowerMaxed) {
+ lines.add(Text.literal("Stats after upgrade:").formatted(Formatting.GRAY));
+ lines.add(Text.empty()
+ .append(Text.literal(" CPS increase: ").formatted(Formatting.GRAY))
+ .append(Text.literal(DECIMAL_FORMAT.format(totalCps / (totalCpsMultiplier) * (timeTowerMultiplier + 0.1))).formatted(Formatting.GOLD)));
+ lines.add(Text.empty()
+ .append(Text.literal(" CPS when active: ").formatted(Formatting.GRAY))
+ .append(Text.literal(DECIMAL_FORMAT.format(isTimeTowerActive ? totalCps / totalCpsMultiplier * (totalCpsMultiplier + 0.1) : totalCps / totalCpsMultiplier * (timeTowerMultiplier + 0.1 + totalCpsMultiplier))).formatted(Formatting.GOLD)));
+ }
+ return true;
+ }
+
+ private static boolean addRabbitStatsToLore(List<Text> lines, int slot) {
+ if (cpsIncreaseFactors.isEmpty()) return false;
+ for (Rabbit rabbit : cpsIncreaseFactors) {
+ if (rabbit.slot == slot) {
+ lines.add(Text.empty()
+ .append(Text.literal("CPS Increase: ").formatted(Formatting.GRAY))
+ .append(Text.literal(DECIMAL_FORMAT.format(rabbit.cpsIncrease)).formatted(Formatting.GOLD)));
+
+ lines.add(Text.empty()
+ .append(Text.literal("Cost per CPS: ").formatted(Formatting.GRAY))
+ .append(Text.literal(DECIMAL_FORMAT.format(rabbit.cost / rabbit.cpsIncrease)).formatted(Formatting.GOLD)));
+
+ if (rabbit.slot == bestUpgrade) {
+ if (rabbit.cost <= totalChocolate) {
+ lines.add(Text.literal("Best upgrade").formatted(Formatting.GREEN));
+ } else {
+ lines.add(Text.literal("Best upgrade, can't afford").formatted(Formatting.YELLOW));
+ }
+ } else if (rabbit.slot == bestAffordableUpgrade && rabbit.cost <= totalChocolate) {
+ lines.add(Text.literal("Best upgrade you can afford").formatted(Formatting.GREEN));
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static MutableText formatTime(double seconds) {
+ seconds = Math.ceil(seconds);
+ if (seconds <= 0) return Text.literal("Now").formatted(Formatting.GREEN);
+
+ StringBuilder builder = new StringBuilder();
+ if (seconds >= 86400) {
+ builder.append((int) (seconds / 86400)).append("d ");
+ seconds %= 86400;
+ }
+ if (seconds >= 3600) {
+ builder.append((int) (seconds / 3600)).append("h ");
+ seconds %= 3600;
+ }
+ if (seconds >= 60) {
+ builder.append((int) (seconds / 60)).append("m ");
+ seconds %= 60;
+ }
+ if (seconds >= 1) {
+ builder.append((int) seconds).append("s");
+ }
+ return Text.literal(builder.toString()).formatted(Formatting.GOLD);
}
- return decimal;
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotText.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotText.java
new file mode 100644
index 00000000..66c02ca1
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotText.java
@@ -0,0 +1,21 @@
+package de.hysky.skyblocker.skyblock.item.slottext;
+
+import net.minecraft.text.Text;
+
+public record SlotText(Text text, TextPosition position) {
+ public static SlotText bottomLeft(Text text) {
+ return new SlotText(text, TextPosition.BOTTOM_LEFT);
+ }
+
+ public static SlotText bottomRight(Text text) {
+ return new SlotText(text, TextPosition.BOTTOM_RIGHT);
+ }
+
+ public static SlotText topLeft(Text text) {
+ return new SlotText(text, TextPosition.TOP_LEFT);
+ }
+
+ public static SlotText topRight(Text text) {
+ return new SlotText(text, TextPosition.TOP_RIGHT);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextAdder.java
new file mode 100644
index 00000000..18bf1dc1
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextAdder.java
@@ -0,0 +1,64 @@
+package de.hysky.skyblocker.skyblock.item.slottext;
+
+import de.hysky.skyblocker.skyblock.ChestValue;
+import net.minecraft.screen.slot.Slot;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Extend this class and add it to {@link SlotTextManager#adders} to add text to any arbitrary slot.
+ */
+public abstract class SlotTextAdder {
+ /**
+ * The title of the screen must match this pattern for this adder to be applied. Null means it will be applied to all screens.
+ * @implNote Don't end your regex with a {@code $} as {@link ChestValue} appends text to the end of the title,
+ * so the regex will stop matching if the player uses it.
+ */
+ public final @Nullable Pattern titlePattern;
+
+ /**
+ * Utility constructor that will compile the given string into a pattern.
+ *
+ * @see #SlotTextAdder(Pattern)
+ */
+ protected SlotTextAdder(@NotNull String titlePattern) {
+ this(Pattern.compile(titlePattern));
+ }
+
+ /**
+ * Creates a SlotTextAdder that will be applied to screens with titles that match the given pattern.
+ *
+ * @param titlePattern The pattern to match the screen title against.
+ */
+ protected SlotTextAdder(@NotNull Pattern titlePattern) {
+ this.titlePattern = titlePattern;
+ }
+
+ /**
+ * Creates a SlotTextAdder that will be applied to all screens.
+ */
+ protected SlotTextAdder() {
+ this.titlePattern = null;
+ }
+
+ /**
+ * This method will be called for each rendered slot. Consider using a switch statement on {@link Slot#id} if you wish to add different text to different slots.
+ *
+ * @return A list of positioned text to be rendered. Return {@link List#of()} if no text should be rendered.
+ * @implNote By minecraft's design, scaled text inexplicably moves around.
+ * So, limit your text to 3 characters (or roughly less than 20 width) if you want it to not look horrible.
+ */
+ public abstract @NotNull List<SlotText> getText(Slot slot);
+
+ /**
+ * Override this method to add conditions to enable or disable this adder.
+ * @return Whether this adder is enabled.
+ * @implNote The slot text adders only work while in skyblock, so no need to check for that again.
+ */
+ public boolean isEnabled() {
+ return true;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextManager.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextManager.java
new file mode 100644
index 00000000..7b4b34cf
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextManager.java
@@ -0,0 +1,68 @@
+package de.hysky.skyblocker.skyblock.item.slottext;
+
+import de.hysky.skyblocker.skyblock.item.slottext.adders.*;
+import de.hysky.skyblocker.utils.Utils;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.screen.ingame.HandledScreen;
+import net.minecraft.screen.slot.Slot;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SlotTextManager {
+ private static final SlotTextAdder[] adders = new SlotTextAdder[]{
+ new EnchantmentLevelAdder(),
+ new MinionLevelAdder(),
+ new PetLevelAdder(),
+ new SkyblockLevelAdder(),
+ new SkillLevelAdder(),
+ new CatacombsLevelAdder.Dungeoneering(),
+ new CatacombsLevelAdder.DungeonClasses(),
+ new CatacombsLevelAdder.ReadyUp(),
+ new RancherBootsSpeedAdder(),
+ new AttributeShardAdder(),
+ new PrehistoricEggAdder()
+ };
+ private static final ArrayList<SlotTextAdder> currentScreenAdders = new ArrayList<>();
+
+ private SlotTextManager() {
+ }
+
+ public static void init() {
+ ScreenEvents.AFTER_INIT.register((client, screen, width, height) -> {
+ if (screen instanceof HandledScreen<?> && Utils.isOnSkyblock()) {
+ onScreenChange(screen);
+ ScreenEvents.remove(screen).register(ignored -> currentScreenAdders.clear());
+ }
+ });
+ }
+
+ private static void onScreenChange(Screen screen) {
+ final String title = screen.getTitle().getString();
+ for (SlotTextAdder adder : adders) {
+ if (!adder.isEnabled()) continue;
+ if (adder.titlePattern == null || adder.titlePattern.matcher(title).find()) {
+ currentScreenAdders.add(adder);
+ }
+ }
+ }
+
+ /**
+ * The returned text is rendered on top of the slot. The text will be scaled if it doesn't fit in the slot,
+ * but 3 characters should be seen as the maximum to keep it readable and in place as it tends to move around when scaled.
+ *
+ * @implNote Only the first adder that returns a non-null text will be used.
+ * The order of the adders remains the same as they were added to the {@link SlotTextManager#adders} array.
+ */
+ @NotNull
+ public static List<SlotText> getText(Slot slot) {
+ if (currentScreenAdders.isEmpty()) return List.of();
+ for (SlotTextAdder adder : currentScreenAdders) {
+ List<SlotText> text = adder.getText(slot);
+ if (!text.isEmpty()) return text;
+ }
+ return List.of();
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/TextPosition.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/TextPosition.java
new file mode 100644
index 00000000..052b228d
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/TextPosition.java
@@ -0,0 +1,8 @@
+package de.hysky.skyblocker.skyblock.item.slottext;
+
+public enum TextPosition {
+ TOP_LEFT,
+ TOP_RIGHT,
+ BOTTOM_LEFT,
+ BOTTOM_RIGHT
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/AttributeShards.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/AttributeShardAdder.java
index ed650e26..811677d7 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/AttributeShards.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/AttributeShardAdder.java
@@ -1,9 +1,22 @@
-package de.hysky.skyblocker.skyblock.item;
+package de.hysky.skyblocker.skyblock.item.slottext.adders;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder;
+import de.hysky.skyblocker.utils.ItemUtils;
+import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import org.jetbrains.annotations.NotNull;
-public class AttributeShards {
- private static final Object2ObjectOpenHashMap<String, String> ID_2_SHORT_NAME = new Object2ObjectOpenHashMap<>();
+import java.util.List;
+
+public class AttributeShardAdder extends SlotTextAdder {
+ private static final Object2ObjectMap<String, String> ID_2_SHORT_NAME = new Object2ObjectOpenHashMap<>();
static {
//Weapons
@@ -50,10 +63,35 @@ public class AttributeShards {
ID_2_SHORT_NAME.put("fishing_speed", "FS");
ID_2_SHORT_NAME.put("hunter", "H");
ID_2_SHORT_NAME.put("trophy_hunter", "TH");
+ }
+
+ public AttributeShardAdder() {
+ super();
+ }
+
+ @Override
+ public @NotNull List<SlotText> getText(Slot slot) {
+ final ItemStack stack = slot.getStack();
+ NbtCompound customData = ItemUtils.getCustomData(stack);
+
+ if (!ItemUtils.getItemId(stack).equals("ATTRIBUTE_SHARD")) return List.of();
+
+ NbtCompound attributesTag = customData.getCompound("attributes");
+ String[] attributes = attributesTag.getKeys().toArray(String[]::new);
+
+ if (attributes.length != 1) return List.of();
+ String attributeId = attributes[0];
+ int attributeLevel = attributesTag.getInt(attributeId);
+ String attributeInitials = ID_2_SHORT_NAME.getOrDefault(attributeId, "");
+ return List.of(
+ SlotText.bottomRight(Text.literal(String.valueOf(attributeLevel)).withColor(0x34eb77)),
+ SlotText.topLeft(Text.literal(attributeInitials).formatted(Formatting.AQUA))
+ );
}
- public static String getShortName(String id) {
- return ID_2_SHORT_NAME.getOrDefault(id, "");
+ @Override
+ public boolean isEnabled() {
+ return SkyblockerConfigManager.get().general.itemInfoDisplay.attributeShardInfo;
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CatacombsLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CatacombsLevelAdder.java
new file mode 100644
index 00000000..31e0d110
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CatacombsLevelAdder.java
@@ -0,0 +1,92 @@
+package de.hysky.skyblocker.skyblock.item.slottext.adders;
+
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder;
+import net.minecraft.item.ItemStack;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+//This class is split into 3 inner classes as there are multiple screens for showing catacombs levels, each with different slot ids or different style of showing the level.
+//It's still kept in 1 main class for organization purposes.
+public class CatacombsLevelAdder {
+ private CatacombsLevelAdder() {
+ }
+
+ public static class Dungeoneering extends SlotTextAdder {
+ public Dungeoneering() {
+ super("^Dungeoneering");
+ }
+
+ @Override
+ public @NotNull List<SlotText> getText(Slot slot) {
+ switch (slot.id) {
+ case 12, 29, 30, 31, 32, 33 -> {
+ String name = slot.getStack().getName().getString();
+ int lastIndex = name.lastIndexOf(' ');
+ if (lastIndex == -1) return List.of(SlotText.bottomLeft(Text.literal("0").formatted(Formatting.RED)));
+ String level = name.substring(lastIndex + 1);
+ if (!NumberUtils.isDigits(level)) return List.of(); //Sanity check, just in case.
+ return List.of(SlotText.bottomRight(Text.literal(level).formatted(Formatting.RED)));
+ }
+ default -> {
+ return List.of();
+ }
+ }
+ }
+ }
+
+ public static class DungeonClasses extends SlotTextAdder {
+
+ public DungeonClasses() {
+ super("^Dungeon Classes"); //Applies to both screens as they are same in both the placement and the style of the level text.
+ }
+
+ @Override
+ public @NotNull List<SlotText> getText(Slot slot) {
+ switch (slot.id) {
+ case 11, 12, 13, 14, 15 -> {
+ String level = getBracketedLevelFromName(slot.getStack());
+ if (!NumberUtils.isDigits(level)) return List.of();
+ return List.of(SlotText.bottomLeft(Text.literal(level).formatted(Formatting.RED)));
+ }
+ default -> {
+ return List.of();
+ }
+ }
+ }
+ }
+
+ public static class ReadyUp extends SlotTextAdder {
+
+ public ReadyUp() {
+ super("^Ready Up");
+ }
+
+ @Override
+ public @NotNull List<SlotText> getText(Slot slot) {
+ switch (slot.id) {
+ case 29, 30, 31, 32, 33 -> {
+ String level = getBracketedLevelFromName(slot.getStack());
+ if (!NumberUtils.isDigits(level)) return List.of();
+ return List.of(SlotText.bottomLeft(Text.literal(level).formatted(Formatting.RED)));
+ }
+ default -> {
+ return List.of();
+ }
+ }
+ }
+ }
+
+ public static String getBracketedLevelFromName(ItemStack itemStack) {
+ String name = itemStack.getName().getString();
+ if (!name.startsWith("[Lvl ")) return null;
+ int index = name.indexOf(']');
+ if (index == -1) return null;
+ return name.substring(5, index);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EnchantmentLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EnchantmentLevelAdder.java
new file mode 100644
index 00000000..9c85ae61
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EnchantmentLevelAdder.java
@@ -0,0 +1,46 @@
+package de.hysky.skyblocker.skyblock.item.slottext.adders;
+
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder;
+import de.hysky.skyblocker.utils.ItemUtils;
+import de.hysky.skyblocker.utils.RomanNumerals;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.nbt.NbtElement;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public class EnchantmentLevelAdder extends SlotTextAdder {
+ public EnchantmentLevelAdder() {
+ super();
+ }
+
+ @Override
+ public @NotNull List<SlotText> getText(Slot slot) {
+ final ItemStack itemStack = slot.getStack();
+ if (!itemStack.isOf(Items.ENCHANTED_BOOK)) return List.of();
+ String name = itemStack.getName().getString();
+ if (name.equals("Enchanted Book")) {
+ NbtCompound nbt = ItemUtils.getCustomData(itemStack);
+ if (nbt.isEmpty() || !nbt.contains("enchantments", NbtElement.COMPOUND_TYPE)) return List.of();
+ NbtCompound enchantments = nbt.getCompound("enchantments");
+ if (enchantments.getSize() != 1) return List.of(); //Only makes sense to display the level when there's one enchant.
+ int level = enchantments.getInt(enchantments.getKeys().iterator().next());
+ return List.of(SlotText.bottomLeft(Text.literal(String.valueOf(level)).formatted(Formatting.GREEN)));
+ } else { //In bazaar, the books have the enchantment level in the name
+ int level = getEnchantLevelFromString(name);
+ if (level == 0) return List.of();
+ return List.of(SlotText.bottomLeft(Text.literal(String.valueOf(level)).formatted(Formatting.GREEN)));
+ }
+ }
+
+ private static int getEnchantLevelFromString(String str) {
+ String romanNumeral = str.substring(str.lastIndexOf(' ') + 1); //+1 because we don't need the space itself
+ return RomanNumerals.romanToDecimal(romanNumeral); //Temporary line. The method will be moved out later.
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/MinionLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/MinionLevelAdder.java
new file mode 100644
index 00000000..b54b6a89
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/MinionLevelAdder.java
@@ -0,0 +1,31 @@
+package de.hysky.skyblocker.skyblock.item.slottext.adders;
+
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder;
+import de.hysky.skyblocker.utils.RomanNumerals;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public class MinionLevelAdder extends SlotTextAdder {
+ public MinionLevelAdder() {
+ super();
+ }
+
+ @Override
+ public @NotNull List<SlotText> getText(Slot slot) {
+ ItemStack itemStack = slot.getStack();
+ if (!itemStack.isOf(Items.PLAYER_HEAD)) return List.of();
+ String name = itemStack.getName().getString();
+ if (!name.contains("Minion")) return List.of();
+ String romanNumeral = name.substring(name.lastIndexOf(' ') + 1); //+1 because we don't need the space itself
+ int level = RomanNumerals.romanToDecimal(romanNumeral);
+ if (level == 0) return List.of();
+ return List.of(SlotText.topRight(Text.literal(String.valueOf(level)).formatted(Formatting.AQUA)));
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java
new file mode 100644
index 00000000..3813563a
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java
@@ -0,0 +1,28 @@
+package de.hysky.skyblocker.skyblock.item.slottext.adders;
+
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public class PetLevelAdder extends SlotTextAdder {
+ public PetLevelAdder() {
+ super();
+ }
+
+ @Override
+ public @NotNull List<SlotText> getText(Slot slot) {
+ ItemStack itemStack = slot.getStack();
+ if (!itemStack.isOf(Items.PLAYER_HEAD)) return List.of();
+ String level = CatacombsLevelAdder.getBracketedLevelFromName(itemStack);
+ if (!NumberUtils.isDigits(level)) return List.of();
+ return List.of(SlotText.topLeft(Text.literal(level).formatted(Formatting.GOLD)));
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PrehistoricEggAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PrehistoricEggAdder.java
new file mode 100644
index 00000000..a157efee
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PrehistoricEggAdder.java
@@ -0,0 +1,34 @@
+package de.hysky.skyblocker.skyblock.item.slottext.adders;
+
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder;
+import de.hysky.skyblocker.utils.ItemUtils;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.nbt.NbtElement;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public class PrehistoricEggAdder extends SlotTextAdder {
+ @Override
+ public @NotNull List<SlotText> getText(Slot slot) {
+ final ItemStack stack = slot.getStack();
+ if (!stack.isOf(Items.PLAYER_HEAD) || !StringUtils.equals(stack.getSkyblockId(), "PREHISTORIC_EGG")) return List.of();
+ NbtCompound nbt = ItemUtils.getCustomData(stack);
+ if (!nbt.contains("blocks_walked", NbtElement.INT_TYPE)) return List.of();
+ int walked = nbt.getInt("blocks_walked");
+
+ String walkedstr;
+ if (walked < 1000) walkedstr = String.valueOf(walked);
+ else if (walked < 10000) walkedstr = String.format("%.1fk", walked/1000.0f);
+ else walkedstr = walked / 1000 + "k";
+
+ return List.of(SlotText.bottomLeft(Text.literal(walkedstr).formatted(Formatting.GOLD)));
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/RancherBootsSpeedAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/RancherBootsSpeedAdder.java
new file mode 100644
index 00000000..1f92fb8a
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/RancherBootsSpeedAdder.java
@@ -0,0 +1,36 @@
+package de.hysky.skyblocker.skyblock.item.slottext.adders;
+
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder;
+import de.hysky.skyblocker.utils.ItemUtils;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class RancherBootsSpeedAdder extends SlotTextAdder {
+ private static final Pattern SPEED_PATTERN = Pattern.compile("Current Speed Cap: (\\d+) ?(\\d+)?");
+
+ public RancherBootsSpeedAdder() {
+ super();
+ }
+
+ @Override
+ public @NotNull List<SlotText> getText(Slot slot) {
+ final ItemStack itemStack = slot.getStack();
+ // V null-safe equals.
+ if (!itemStack.isOf(Items.LEATHER_BOOTS) && !StringUtils.equals(itemStack.getSkyblockId(), "RANCHERS_BOOTS")) return List.of();
+ Matcher matcher = ItemUtils.getLoreLineIfMatch(slot.getStack(), SPEED_PATTERN);
+ if (matcher == null) return List.of();
+ String speed = matcher.group(2);
+ if (speed == null) speed = matcher.group(1); //2nd group only matches when the speed cap is set to a number beyond the player's actual speed cap.
+ return List.of(SlotText.bottomLeft(Text.literal(speed).formatted(Formatting.GREEN)));
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java
new file mode 100644
index 00000000..095982af
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java
@@ -0,0 +1,34 @@
+package de.hysky.skyblocker.skyblock.item.slottext.adders;
+
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder;
+import de.hysky.skyblocker.utils.RomanNumerals;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public class SkillLevelAdder extends SlotTextAdder {
+ public SkillLevelAdder() {
+ super("^Your Skills");
+ }
+
+ @Override
+ public @NotNull List<SlotText> getText(Slot slot) {
+ switch (slot.id) {
+ case 19, 20, 21, 22, 23, 24, 25, 29, 30, 31, 32 -> { //These are the slots that contain the skill items. Note that they aren't continuous, as there are 2 rows.
+ String name = slot.getStack().getName().getString();
+ int lastIndex = name.lastIndexOf(' ');
+ if (lastIndex == -1) return List.of(SlotText.bottomLeft(Text.literal("0").formatted(Formatting.LIGHT_PURPLE))); //Skills without any levels don't display any roman numerals. Probably because 0 doesn't exist.
+ String romanNumeral = name.substring(lastIndex + 1); //+1 because we don't need the space itself
+ //The "romanNumeral" might be a latin numeral, too. There's a skyblock setting for this, so we have to do it this way V
+ return List.of(SlotText.bottomLeft(Text.literal(String.valueOf(RomanNumerals.isValidRomanNumeral(romanNumeral) ? RomanNumerals.romanToDecimal(romanNumeral) : Integer.parseInt(romanNumeral))).formatted(Formatting.LIGHT_PURPLE)));
+ }
+ default -> {
+ return List.of();
+ }
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkyblockLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkyblockLevelAdder.java
new file mode 100644
index 00000000..8b528508
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkyblockLevelAdder.java
@@ -0,0 +1,29 @@
+package de.hysky.skyblocker.skyblock.item.slottext.adders;
+
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder;
+import de.hysky.skyblocker.utils.ItemUtils;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public class SkyblockLevelAdder extends SlotTextAdder {
+ public SkyblockLevelAdder() {
+ super("^SkyBlock Menu");
+ }
+
+ @Override
+ public @NotNull List<SlotText> getText(Slot slot) {
+ if (slot.getIndex() != 22) return List.of();
+ List<Text> lore = ItemUtils.getLore(slot.getStack());
+ if (lore.isEmpty()) return List.of();
+ List<Text> siblings = lore.getFirst().getSiblings();
+ if (siblings.size() < 3) return List.of();
+ Text levelText = siblings.get(2); //The 3rd child is the level text itself
+ if (!NumberUtils.isDigits(levelText.getString())) return List.of();
+ return List.of(SlotText.bottomLeft(levelText));
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java
index 8798a139..992206ad 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java
@@ -108,7 +108,7 @@ public class AccessoriesHelper {
.put(page, new ObjectOpenHashSet<>(accessoryIds));
}
- static Pair<AccessoryReport, String> calculateReport4Accessory(String accessoryId) {
+ public static Pair<AccessoryReport, String> calculateReport4Accessory(String accessoryId) {
if (!ACCESSORY_DATA.containsKey(accessoryId) || Utils.getProfileId().isEmpty()) return Pair.of(AccessoryReport.INELIGIBLE, null);
Accessory accessory = ACCESSORY_DATA.get(accessoryId);
@@ -208,7 +208,7 @@ public class AccessoriesHelper {
}
}
- enum AccessoryReport {
+ public enum AccessoryReport {
HAS_HIGHEST_TIER, //You've collected the highest tier - Collected
IS_GREATER_TIER, //This accessory is an upgrade from the one in the same family that you already have - Upgrade -- Shows you what tier this accessory is in its family
HAS_GREATER_TIER, //This accessory has a higher tier upgrade - Upgradable -- Shows you the highest tier accessory you've collected in that family
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java
index c5279c61..b93ca77a 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java
@@ -7,7 +7,6 @@ import it.unimi.dsi.fastutil.ints.IntIntPair;
import it.unimi.dsi.fastutil.ints.IntObjectPair;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
-import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.tooltip.HoveredTooltipPositioner;
import net.minecraft.client.gui.tooltip.TooltipComponent;
import net.minecraft.item.ItemStack;
@@ -34,8 +33,7 @@ public class CompactorDeletorPreview {
public static final Pattern NAME = Pattern.compile("PERSONAL_(?<type>COMPACTOR|DELETOR)_(?<size>\\d+)");
private static final MinecraftClient client = MinecraftClient.getInstance();
- public static boolean drawPreview(DrawContext context, ItemStack stack, String type, String size, int x, int y) {
- List<Text> tooltips = Screen.getTooltipFromItem(client, stack);
+ public static boolean drawPreview(DrawContext context, ItemStack stack, List<Text> tooltips, String type, String size, int x, int y) {
int targetIndex = getTargetIndex(tooltips);
if (targetIndex == -1) return false;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java
deleted file mode 100644
index 46babc8b..00000000
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java
+++ /dev/null
@@ -1,96 +0,0 @@
-package de.hysky.skyblocker.skyblock.item.tooltip;
-
-import de.hysky.skyblocker.utils.Constants;
-import net.minecraft.nbt.NbtCompound;
-import net.minecraft.text.MutableText;
-import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
-import net.minecraft.util.StringIdentifiable;
-
-public class ExoticTooltip {
- public static String getExpectedHex(String id) {
- String color = TooltipInfoType.COLOR.getData().get(id).getAsString();
- if (color != null) {
- String[] RGBValues = color.split(",");
- return String.format("%02X%02X%02X", Integer.parseInt(RGBValues[0]), Integer.parseInt(RGBValues[1]), Integer.parseInt(RGBValues[2]));
- } else {
- ItemTooltip.LOGGER.warn("[Skyblocker Exotics] No expected color data found for id {}", id);
- return null;
- }
- }
-
- public static boolean isException(String id, String hex) {
- if (id.startsWith("LEATHER") || id.equals("GHOST_BOOTS") || Constants.SEYMOUR_IDS.contains(id)) {
- return true;
- }
- if (id.startsWith("RANCHER")) {
- return Constants.RANCHERS.contains(hex);
- }
- if (id.contains("ADAPTIVE_CHESTPLATE")) {
- return Constants.ADAPTIVE_CHEST.contains(hex);
- } else if (id.contains("ADAPTIVE")) {
- return Constants.ADAPTIVE.contains(hex);
- }
- if (id.startsWith("REAPER")) {
- return Constants.REAPER.contains(hex);
- }
- if (id.startsWith("FAIRY")) {
- return Constants.FAIRY_HEXES.contains(hex);
- }
- if (id.startsWith("CRYSTAL")) {
- return Constants.CRYSTAL_HEXES.contains(hex);
- }
- if (id.contains("SPOOK")) {
- return Constants.SPOOK.contains(hex);
- }
- return false;
- }
-
- public static DyeType checkDyeType(String hex) {
- if (Constants.CRYSTAL_HEXES.contains(hex)) {
- return DyeType.CRYSTAL;
- }
- if (Constants.FAIRY_HEXES.contains(hex)) {
- return DyeType.FAIRY;
- }
- if (Constants.OG_FAIRY_HEXES.contains(hex)) {
- return DyeType.OG_FAIRY;
- }
- if (Constants.SPOOK.contains(hex)) {
- return DyeType.SPOOK;
- }
- if (Constants.GLITCHED.contains(hex)) {
- return DyeType.GLITCHED;
- }
- return DyeType.EXOTIC;
- }
-
- public static boolean intendedDyed(NbtCompound customData) {
- return customData.contains("dye_item");
- }
-
- public enum DyeType implements StringIdentifiable {
- CRYSTAL("crystal", Formatting.AQUA),
- FAIRY("fairy", Formatting.LIGHT_PURPLE),
- OG_FAIRY("og_fairy", Formatting.DARK_PURPLE),
- SPOOK("spook", Formatting.RED),
- GLITCHED("glitched", Formatting.BLUE),
- EXOTIC("exotic", Formatting.GOLD);
- private final String name;
- private final Formatting formatting;
-
- DyeType(String name, Formatting formatting) {
- this.name = name;
- this.formatting = formatting;
- }
-
- @Override
- public String asString() {
- return name;
- }
-
- public MutableText getTranslatedText() {
- return Text.translatable("skyblocker.exotic." + name).formatted(formatting);
- }
- }
-}
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 031817ac..e6a364e4 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
@@ -1,260 +1,28 @@
package de.hysky.skyblocker.skyblock.item.tooltip;
-import com.google.gson.JsonObject;
-import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.config.configs.GeneralConfig;
-import de.hysky.skyblocker.skyblock.item.MuseumItemCache;
-import de.hysky.skyblocker.skyblock.item.tooltip.AccessoriesHelper.AccessoryReport;
import de.hysky.skyblocker.utils.Constants;
-import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
-import it.unimi.dsi.fastutil.Pair;
import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.item.TooltipType;
-import net.minecraft.component.DataComponentTypes;
-import net.minecraft.component.type.DyedColorComponent;
-import net.minecraft.item.Item;
-import net.minecraft.item.ItemStack;
-import net.minecraft.nbt.NbtCompound;
-import net.minecraft.nbt.NbtElement;
-import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
-
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
import java.util.concurrent.CompletableFuture;
public class ItemTooltip {
protected static final Logger LOGGER = LoggerFactory.getLogger(ItemTooltip.class.getName());
private static final MinecraftClient client = MinecraftClient.getInstance();
- protected static final GeneralConfig.ItemTooltip config = SkyblockerConfigManager.get().general.itemTooltip;
+ public static final GeneralConfig.ItemTooltip config = SkyblockerConfigManager.get().general.itemTooltip;
private static volatile boolean sentNullWarning = false;
- public static void getTooltip(ItemStack stack, Item.TooltipContext tooltipContext, TooltipType tooltipType, List<Text> lines) {
- if (!Utils.isOnSkyblock() || client.player == null) return;
-
- smoothenLines(lines);
-
- String name = getInternalNameFromNBT(stack, false);
- String internalID = getInternalNameFromNBT(stack, true);
- String neuName = name;
- if (name == null || internalID == null) return;
-
- if (name.startsWith("ISSHINY_")) {
- name = "SHINY_" + internalID;
- neuName = internalID;
- }
-
- if (lines.isEmpty()) {
- return;
- }
-
- int count = stack.getCount();
- boolean bazaarOpened = lines.stream().anyMatch(each -> each.getString().contains("Buy price:") || each.getString().contains("Sell price:"));
-
- if (TooltipInfoType.NPC.isTooltipEnabledAndHasOrNullWarning(internalID)) {
- lines.add(Text.literal(String.format("%-21s", "NPC Sell Price:"))
- .formatted(Formatting.YELLOW)
- .append(getCoinsMessage(TooltipInfoType.NPC.getData().get(internalID).getAsDouble(), count)));
- }
-
- boolean bazaarExist = false;
-
- if (TooltipInfoType.BAZAAR.isTooltipEnabledAndHasOrNullWarning(name) && !bazaarOpened) {
- JsonObject getItem = TooltipInfoType.BAZAAR.getData().getAsJsonObject(name);
- lines.add(Text.literal(String.format("%-18s", "Bazaar buy Price:"))
- .formatted(Formatting.GOLD)
- .append(getItem.get("buyPrice").isJsonNull()
- ? Text.literal("No data").formatted(Formatting.RED)
- : getCoinsMessage(getItem.get("buyPrice").getAsDouble(), count)));
- lines.add(Text.literal(String.format("%-19s", "Bazaar sell Price:"))
- .formatted(Formatting.GOLD)
- .append(getItem.get("sellPrice").isJsonNull()
- ? Text.literal("No data").formatted(Formatting.RED)
- : getCoinsMessage(getItem.get("sellPrice").getAsDouble(), count)));
- bazaarExist = true;
- }
-
- // bazaarOpened & bazaarExist check for lbin, because Skytils keeps some bazaar item data in lbin api
- boolean lbinExist = false;
- if (TooltipInfoType.LOWEST_BINS.isTooltipEnabledAndHasOrNullWarning(name) && !bazaarOpened && !bazaarExist) {
- lines.add(Text.literal(String.format("%-19s", "Lowest BIN Price:"))
- .formatted(Formatting.GOLD)
- .append(getCoinsMessage(TooltipInfoType.LOWEST_BINS.getData().get(name).getAsDouble(), count)));
- lbinExist = true;
- }
-
- if (SkyblockerConfigManager.get().general.itemTooltip.enableAvgBIN) {
- if (TooltipInfoType.ONE_DAY_AVERAGE.getData() == null || TooltipInfoType.THREE_DAY_AVERAGE.getData() == null) {
- nullWarning();
- } else {
- /*
- We are skipping check average prices for potions, runes
- and enchanted books because there is no data for their in API.
- */
- neuName = getNeuName(internalID, neuName);
-
- if (!neuName.isEmpty() && lbinExist) {
- GeneralConfig.Average type = config.avg;
-
- // "No data" line because of API not keeping old data, it causes NullPointerException
- if (type == GeneralConfig.Average.ONE_DAY || type == GeneralConfig.Average.BOTH) {
- lines.add(
- Text.literal(String.format("%-19s", "1 Day Avg. Price:"))
- .formatted(Formatting.GOLD)
- .append(TooltipInfoType.ONE_DAY_AVERAGE.getData().get(neuName) == null
- ? Text.literal("No data").formatted(Formatting.RED)
- : getCoinsMessage(TooltipInfoType.ONE_DAY_AVERAGE.getData().get(neuName).getAsDouble(), count)
- )
- );
- }
- if (type == GeneralConfig.Average.THREE_DAY || type == GeneralConfig.Average.BOTH) {
- lines.add(
- Text.literal(String.format("%-19s", "3 Day Avg. Price:"))
- .formatted(Formatting.GOLD)
- .append(TooltipInfoType.THREE_DAY_AVERAGE.getData().get(neuName) == null
- ? Text.literal("No data").formatted(Formatting.RED)
- : getCoinsMessage(TooltipInfoType.THREE_DAY_AVERAGE.getData().get(neuName).getAsDouble(), count)
- )
- );
- }
- }
- }
- }
-
- final Map<Integer, String> itemTierFloors = Map.ofEntries(
- Map.entry(0, "E"),
- Map.entry(1, "F1"),
- Map.entry(2, "F2"),
- Map.entry(3, "F3"),
- Map.entry(4, "F4/M1"),
- Map.entry(5, "F5/M2"),
- Map.entry(6, "F6/M3"),
- Map.entry(7, "F7/M4"),
- Map.entry(8, "M5"),
- Map.entry(9, "M6"),
- Map.entry(10, "M7")
- );
-
- if (SkyblockerConfigManager.get().general.itemTooltip.dungeonQuality) {
- NbtCompound customData = ItemUtils.getCustomData(stack);
- if (customData != null && customData.contains("baseStatBoostPercentage")) {
- int baseStatBoostPercentage = customData.getInt("baseStatBoostPercentage");
- boolean maxQuality = baseStatBoostPercentage == 50;
- if (maxQuality) {
- lines.add(Text.literal(String.format("%-17s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.RED).formatted(Formatting.BOLD));
- } else {
- lines.add(Text.literal(String.format("%-21s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.BLUE));
- }
- if (customData.contains("item_tier")) { // sometimes it just isn't here?
- int itemTier = customData.getInt("item_tier");
- if (maxQuality) {
- lines.add(Text.literal(String.format("%-17s", "Floor Tier:") + itemTier + " (" + itemTierFloors.get(itemTier) + ")").formatted(Formatting.RED).formatted(Formatting.BOLD));
- } else {
- lines.add(Text.literal(String.format("%-21s", "Floor Tier:") + itemTier + " (" + itemTierFloors.get(itemTier) + ")").formatted(Formatting.BLUE));
- }
- }
- }
- }
-
- if (TooltipInfoType.MOTES.isTooltipEnabledAndHasOrNullWarning(internalID)) {
- lines.add(Text.literal(String.format("%-20s", "Motes Price:"))
- .formatted(Formatting.LIGHT_PURPLE)
- .append(getMotesMessage(TooltipInfoType.MOTES.getData().get(internalID).getAsInt(), count)));
- }
-
- if (TooltipInfoType.OBTAINED.isTooltipEnabled()) {
- String timestamp = ItemUtils.getTimestamp(stack);
-
- if (!timestamp.isEmpty()) {
- lines.add(Text.literal(String.format("%-21s", "Obtained: "))
- .formatted(Formatting.LIGHT_PURPLE)
- .append(Text.literal(timestamp).formatted(Formatting.RED)));
- }
- }
-
- if (TooltipInfoType.MUSEUM.isTooltipEnabledAndHasOrNullWarning(internalID) && !bazaarOpened) {
- String itemCategory = TooltipInfoType.MUSEUM.getData().get(internalID).getAsString();
- String format = switch (itemCategory) {
- case "Weapons" -> "%-18s";
- case "Armor" -> "%-19s";
- default -> "%-20s";
- };
-
- //Special case the special category so that it doesn't always display not donated
- if (itemCategory.equals("Special")) {
- lines.add(Text.literal(String.format(format, "Museum: (" + itemCategory + ")"))
- .formatted(Formatting.LIGHT_PURPLE));
- } else {
- NbtCompound customData = ItemUtils.getCustomData(stack);
- boolean isInMuseum = (customData.contains("donated_museum") && customData.getBoolean("donated_museum")) || MuseumItemCache.hasItemInMuseum(internalID);
-
- Formatting donatedIndicatorFormatting = isInMuseum ? Formatting.GREEN : Formatting.RED;
-
- lines.add(Text.literal(String.format(format, "Museum (" + itemCategory + "):"))
- .formatted(Formatting.LIGHT_PURPLE)
- .append(Text.literal(isInMuseum ? "✔" : "✖").formatted(donatedIndicatorFormatting, Formatting.BOLD))
- .append(Text.literal(isInMuseum ? " Donated" : " Not Donated").formatted(donatedIndicatorFormatting)));
- }
- }
-
- if (TooltipInfoType.COLOR.isTooltipEnabledAndHasOrNullWarning(internalID) && stack.contains(DataComponentTypes.DYED_COLOR)) {
- String uuid = ItemUtils.getItemUuid(stack);
- boolean hasCustomDye = SkyblockerConfigManager.get().general.customDyeColors.containsKey(uuid) || SkyblockerConfigManager.get().general.customAnimatedDyes.containsKey(uuid);
- //DyedColorComponent#getColor returns ARGB so we mask out the alpha bits
- int dyeColor = DyedColorComponent.getColor(stack, 0);
-
- // dyeColor will have alpha = 255 if it's dyed, and alpha = 0 if it's not dyed,
- if (!hasCustomDye && dyeColor != 0) {
- dyeColor = dyeColor & 0x00FFFFFF;
- String colorHex = String.format("%06X", dyeColor);
- String expectedHex = ExoticTooltip.getExpectedHex(internalID);
-
- boolean correctLine = false;
- for (Text text : lines) {
- String existingTooltip = text.getString() + " ";
- if (existingTooltip.startsWith("Color: ")) {
- correctLine = true;
-
- addExoticTooltip(lines, internalID, ItemUtils.getCustomData(stack), colorHex, expectedHex, existingTooltip);
- break;
- }
- }
-
- if (!correctLine) {
- addExoticTooltip(lines, internalID, ItemUtils.getCustomData(stack), colorHex, expectedHex, "");
- }
- }
- }
-
- if (TooltipInfoType.ACCESSORIES.isTooltipEnabledAndHasOrNullWarning(internalID)) {
- Pair<AccessoryReport, String> report = AccessoriesHelper.calculateReport4Accessory(internalID);
-
- if (report.left() != AccessoryReport.INELIGIBLE) {
- MutableText title = Text.literal(String.format("%-19s", "Accessory: ")).withColor(0xf57542);
-
- Text stateText = switch (report.left()) {
- case HAS_HIGHEST_TIER -> Text.literal("✔ Collected").formatted(Formatting.GREEN);
- case IS_GREATER_TIER -> Text.literal("✦ Upgrade ").withColor(0x218bff).append(Text.literal(report.right()).withColor(0xf8f8ff));
- case HAS_GREATER_TIER -> Text.literal("↑ Upgradable ").withColor(0xf8d048).append(Text.literal(report.right()).withColor(0xf8f8ff));
- case OWNS_BETTER_TIER -> Text.literal("↓ Downgrade ").formatted(Formatting.GRAY).append(Text.literal(report.right()).withColor(0xf8f8ff));
- case MISSING -> Text.literal("✖ Missing ").formatted(Formatting.RED).append(Text.literal(report.right()).withColor(0xf8f8ff));
-
- //Should never be the case
- default -> Text.literal("? Unknown").formatted(Formatting.GRAY);
- };
-
- lines.add(title.append(stateText));
- }
- }
- }
-
@NotNull
public static String getNeuName(String internalID, String neuName) {
switch (internalID) {
@@ -281,13 +49,6 @@ public class ItemTooltip {
return neuName;
}
- private static void addExoticTooltip(List<Text> lines, String internalID, NbtCompound customData, String colorHex, String expectedHex, String existingTooltip) {
- if (expectedHex != null && !colorHex.equalsIgnoreCase(expectedHex) && !ExoticTooltip.isException(internalID, colorHex) && !ExoticTooltip.intendedDyed(customData)) {
- final ExoticTooltip.DyeType type = ExoticTooltip.checkDyeType(colorHex);
- lines.add(1, Text.literal(existingTooltip + Formatting.DARK_GRAY + "(").append(type.getTranslatedText()).append(Formatting.DARK_GRAY + ")"));
- }
- }
-
public static void nullWarning() {
if (!sentNullWarning && client.player != null) {
LOGGER.warn(Constants.PREFIX.get().append(Text.translatable("skyblocker.itemTooltip.nullMessage")).getString());
@@ -295,69 +56,7 @@ public class ItemTooltip {
}
}
- // TODO What in the world is this?
- public static String getInternalNameFromNBT(ItemStack stack, boolean internalIDOnly) {
- NbtCompound customData = ItemUtils.getCustomData(stack);
-
- if (customData == null || !customData.contains(ItemUtils.ID, NbtElement.STRING_TYPE)) {
- return null;
- }
- String internalName = customData.getString(ItemUtils.ID);
-
- if (internalIDOnly) {
- return internalName;
- }
-
- // Transformation to API format.
- if (customData.contains("is_shiny")) {
- return "ISSHINY_" + internalName;
- }
-
- switch (internalName) {
- case "ENCHANTED_BOOK" -> {
- if (customData.contains("enchantments")) {
- NbtCompound enchants = customData.getCompound("enchantments");
- Optional<String> firstEnchant = enchants.getKeys().stream().findFirst();
- String enchant = firstEnchant.orElse("");
- return "ENCHANTMENT_" + enchant.toUpperCase(Locale.ENGLISH) + "_" + enchants.getInt(enchant);
- }
- }
- case "PET" -> {
- if (customData.contains("petInfo")) {
- JsonObject petInfo = SkyblockerMod.GSON.fromJson(customData.getString("petInfo"), JsonObject.class);
- return "LVL_1_" + petInfo.get("tier").getAsString() + "_" + petInfo.get("type").getAsString();
- }
- }
- case "POTION" -> {
- String enhanced = customData.contains("enhanced") ? "_ENHANCED" : "";
- String extended = customData.contains("extended") ? "_EXTENDED" : "";
- String splash = customData.contains("splash") ? "_SPLASH" : "";
- if (customData.contains("potion") && customData.contains("potion_level")) {
- return (customData.getString("potion") + "_" + internalName + "_" + customData.getInt("potion_level")
- + enhanced + extended + splash).toUpperCase(Locale.ENGLISH);
- }
- }
- case "RUNE" -> {
- if (customData.contains("runes")) {
- NbtCompound runes = customData.getCompound("runes");
- Optional<String> firstRunes = runes.getKeys().stream().findFirst();
- String rune = firstRunes.orElse("");
- return rune.toUpperCase(Locale.ENGLISH) + "_RUNE_" + runes.getInt(rune);
- }
- }
- case "ATTRIBUTE_SHARD" -> {
- if (customData.contains("attributes")) {
- NbtCompound shards = customData.getCompound("attributes");
- Optional<String> firstShards = shards.getKeys().stream().findFirst();
- String shard = firstShards.orElse("");
- return internalName + "-" + shard.toUpperCase(Locale.ENGLISH) + "_" + shards.getInt(shard);
- }
- }
- }
- return internalName;
- }
-
- private static Text getCoinsMessage(double price, int count) {
+ public static Text getCoinsMessage(double price, int count) {
// Format the price string once
String priceString = String.format(Locale.ENGLISH, "%1$,.1f", price);
@@ -368,47 +67,9 @@ public class ItemTooltip {
// If count is greater than 1, include the "each" information
String priceStringTotal = String.format(Locale.ENGLISH, "%1$,.1f", price * count);
- MutableText message = Text.literal(priceStringTotal + " Coins ").formatted(Formatting.DARK_AQUA);
- message.append(Text.literal("(" + priceString + " each)").formatted(Formatting.GRAY));
-
- return message;
- }
-
- private static Text getMotesMessage(int price, int count) {
- float motesMultiplier = SkyblockerConfigManager.get().otherLocations.rift.mcGrubberStacks * 0.05f + 1;
-
- // Calculate the total price
- int totalPrice = price * count;
- String totalPriceString = String.format(Locale.ENGLISH, "%1$,.1f", totalPrice * motesMultiplier);
-
- // If count is 1, return a simple message
- if (count == 1) {
- return Text.literal(totalPriceString.replace(".0", "") + " Motes").formatted(Formatting.DARK_AQUA);
- }
-
- // If count is greater than 1, include the "each" information
- String eachPriceString = String.format(Locale.ENGLISH, "%1$,.1f", price * motesMultiplier);
- MutableText message = Text.literal(totalPriceString.replace(".0", "") + " Motes ").formatted(Formatting.DARK_AQUA);
- message.append(Text.literal("(" + eachPriceString.replace(".0", "") + " each)").formatted(Formatting.GRAY));
-
- return message;
- }
-
- //This is static to not create a new text object for each line in every item
- private static final Text BUMPY_LINE = Text.literal("-----------------").formatted(Formatting.DARK_GRAY, Formatting.STRIKETHROUGH);
-
- private static void smoothenLines(List<Text> lines) {
- for (int i = 0; i < lines.size(); i++) {
- List<Text> lineSiblings = lines.get(i).getSiblings();
- //Compare the first sibling rather than the whole object as the style of the root object can change while visually staying the same
- if (lineSiblings.size() == 1 && lineSiblings.getFirst().equals(BUMPY_LINE)) {
- lines.set(i, createSmoothLine());
- }
- }
- }
- public static Text createSmoothLine() {
- return Text.literal(" ").formatted(Formatting.DARK_GRAY, Formatting.STRIKETHROUGH, Formatting.BOLD);
+ return Text.literal(priceStringTotal + " Coins ").formatted(Formatting.DARK_AQUA)
+ .append(Text.literal("(" + priceString + " each)").formatted(Formatting.GRAY));
}
// If these options is true beforehand, the client will get first data of these options while loading.
@@ -456,4 +117,4 @@ public class ItemTooltip {
});
}, 1200, true);
}
-}
+} \ No newline at end of file
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipAdder.java
new file mode 100644
index 00000000..7c43957e
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipAdder.java
@@ -0,0 +1,48 @@
+package de.hysky.skyblocker.skyblock.item.tooltip;
+
+import de.hysky.skyblocker.skyblock.ChestValue;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Extend this class and add it to {@link TooltipManager#adders} to add additional text to tooltips.
+ */
+public abstract class TooltipAdder {
+ /**
+ * The title of the screen must match this pattern for this adder to be applied. Null means it will be applied to all screens.
+ * @implNote Don't end your regex with a {@code $} as {@link ChestValue} appends text to the end of the title,
+ * so the regex will stop matching if the player uses it.
+ */
+ public final Pattern titlePattern;
+ /**
+ * The priority of this adder. Lower priority means it will be applied first.
+ * @apiNote Consider taking this value on your class' constructor and setting it from {@link TooltipManager#adders} to make it easy to read and maintain.
+ */
+ public final int priority;
+
+ protected TooltipAdder(String titlePattern, int priority) {
+ this(Pattern.compile(titlePattern), priority);
+ }
+
+ protected TooltipAdder(Pattern titlePattern, int priority) {
+ this.titlePattern = titlePattern;
+ this.priority = priority;
+ }
+
+ /**
+ * Creates a TooltipAdder that will be applied to all screens.
+ */
+ protected TooltipAdder(int priority) {
+ this.titlePattern = null;
+ this.priority = priority;
+ }
+
+ /**
+ * @implNote The first element of the lines list holds the item's display name,
+ * as it's a list of all lines that will be displayed in the tooltip.
+ */
+ public abstract void addToTooltip(List<Text> lines, Slot focusedSlot);
+}
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
new file mode 100644
index 00000000..319df71a
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java
@@ -0,0 +1,72 @@
+package de.hysky.skyblocker.skyblock.item.tooltip;
+
+import de.hysky.skyblocker.skyblock.chocolatefactory.ChocolateFactorySolver;
+import de.hysky.skyblocker.skyblock.item.tooltip.adders.*;
+import de.hysky.skyblocker.utils.Utils;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+public class TooltipManager {
+ private static final TooltipAdder[] adders = new TooltipAdder[]{
+ new LineSmoothener(), // Applies before anything else
+ new SupercraftReminder(),
+ new ChocolateFactorySolver.Tooltip(),
+ new NpcPriceTooltip(1),
+ new BazaarPriceTooltip(2),
+ new LBinTooltip(3),
+ new AvgBinTooltip(4),
+ new DungeonQualityTooltip(5),
+ new MotesTooltip(6),
+ new ObtainedDateTooltip(7),
+ new MuseumTooltip(8),
+ new ColorTooltip(9),
+ new AccessoryTooltip(10),
+ };
+ private static final ArrayList<TooltipAdder> currentScreenAdders = new ArrayList<>();
+
+ private TooltipManager() {
+ }
+
+ public static void init() {
+ ScreenEvents.AFTER_INIT.register((client, screen, width, height) -> {
+ onScreenChange(screen);
+ ScreenEvents.remove(screen).register(ignored -> currentScreenAdders.clear());
+ });
+ }
+
+ private static void onScreenChange(Screen screen) {
+ final String title = screen.getTitle().getString();
+ for (TooltipAdder adder : adders) {
+ if (adder.titlePattern == null || adder.titlePattern.matcher(title).find()) {
+ currentScreenAdders.add(adder);
+ }
+ }
+ currentScreenAdders.sort(Comparator.comparingInt(adder -> adder.priority));
+ }
+
+ /**
+ * <p>Adds additional text from all adders that are applicable to the current screen.
+ * This method is run on each tooltip render, so don't do any heavy calculations here.</p>
+ *
+ * <p>If you want to add info to the tooltips of multiple items, consider using a switch statement with {@code focusedSlot.getIndex()}</p>
+ *
+ * @param lines The tooltip lines of the focused item. This includes the display name, as it's a part of the tooltip (at index 0).
+ * @param focusedSlot The slot that is currently focused by the cursor.
+ * @return The lines list itself after all adders have added their text.
+ * @deprecated This method is public only for the sake of the mixin. Don't call directly, not that there is any point to it.
+ */
+ @Deprecated
+ public static List<Text> addToTooltip(List<Text> lines, Slot focusedSlot) {
+ if (!Utils.isOnSkyblock()) return lines;
+ for (TooltipAdder adder : currentScreenAdders) {
+ adder.addToTooltip(lines, focusedSlot);
+ }
+ return lines;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AccessoryTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AccessoryTooltip.java
new file mode 100644
index 00000000..3b150488
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AccessoryTooltip.java
@@ -0,0 +1,43 @@
+package de.hysky.skyblocker.skyblock.item.tooltip.adders;
+
+import de.hysky.skyblocker.skyblock.item.tooltip.AccessoriesHelper;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType;
+import it.unimi.dsi.fastutil.Pair;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.List;
+
+public class AccessoryTooltip extends TooltipAdder {
+ public AccessoryTooltip(int priority) {
+ super(priority);
+ }
+
+ @Override
+ public void addToTooltip(List<Text> lines, Slot focusedSlot) {
+ final String internalID = focusedSlot.getStack().getSkyblockId();
+ if (TooltipInfoType.ACCESSORIES.isTooltipEnabledAndHasOrNullWarning(internalID)) {
+ Pair<AccessoriesHelper.AccessoryReport, String> report = AccessoriesHelper.calculateReport4Accessory(internalID);
+
+ if (report.left() != AccessoriesHelper.AccessoryReport.INELIGIBLE) {
+ MutableText title = Text.literal(String.format("%-19s", "Accessory: ")).withColor(0xf57542);
+
+ Text stateText = switch (report.left()) {
+ case HAS_HIGHEST_TIER -> Text.literal("✔ Collected").formatted(Formatting.GREEN);
+ case IS_GREATER_TIER -> Text.literal("✦ Upgrade ").withColor(0x218bff).append(Text.literal(report.right()).withColor(0xf8f8ff));
+ case HAS_GREATER_TIER -> Text.literal("↑ Upgradable ").withColor(0xf8d048).append(Text.literal(report.right()).withColor(0xf8f8ff));
+ case OWNS_BETTER_TIER -> Text.literal("↓ Downgrade ").formatted(Formatting.GRAY).append(Text.literal(report.right()).withColor(0xf8f8ff));
+ case MISSING -> Text.literal("✖ Missing ").formatted(Formatting.RED).append(Text.literal(report.right()).withColor(0xf8f8ff));
+
+ //Should never be the case
+ default -> Text.literal("? Unknown").formatted(Formatting.GRAY);
+ };
+
+ lines.add(title.append(stateText));
+ }
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AvgBinTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AvgBinTooltip.java
new file mode 100644
index 00000000..a36f30e9
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AvgBinTooltip.java
@@ -0,0 +1,63 @@
+package de.hysky.skyblocker.skyblock.item.tooltip.adders;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.config.configs.GeneralConfig;
+import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType;
+import net.minecraft.item.ItemStack;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.List;
+
+public class AvgBinTooltip extends TooltipAdder {
+ public AvgBinTooltip(int priority) {
+ super(priority);
+ }
+
+ @Override
+ public void addToTooltip(List<Text> lines, Slot focusedSlot) {
+ final ItemStack itemStack = focusedSlot.getStack();
+ String neuName = itemStack.getNeuName();
+ String internalID = itemStack.getSkyblockId();
+ if (neuName == null || internalID == null) return;
+
+ if (SkyblockerConfigManager.get().general.itemTooltip.enableAvgBIN) {
+ if (TooltipInfoType.ONE_DAY_AVERAGE.getData() == null || TooltipInfoType.THREE_DAY_AVERAGE.getData() == null) {
+ ItemTooltip.nullWarning();
+ } else {
+ /*
+ We are skipping check average prices for potions, runes
+ and enchanted books because there is no data for their in API.
+ */
+ if (!neuName.isEmpty() && LBinTooltip.lbinExist) {
+ GeneralConfig.Average type = ItemTooltip.config.avg;
+
+ // "No data" line because of API not keeping old data, it causes NullPointerException
+ if (type == GeneralConfig.Average.ONE_DAY || type == GeneralConfig.Average.BOTH) {
+ lines.add(
+ Text.literal(String.format("%-19s", "1 Day Avg. Price:"))
+ .formatted(Formatting.GOLD)
+ .append(TooltipInfoType.ONE_DAY_AVERAGE.getData().get(neuName) == null
+ ? Text.literal("No data").formatted(Formatting.RED)
+ : ItemTooltip.getCoinsMessage(TooltipInfoType.ONE_DAY_AVERAGE.getData().get(neuName).getAsDouble(), itemStack.getCount())
+ )
+ );
+ }
+ if (type == GeneralConfig.Average.THREE_DAY || type == GeneralConfig.Average.BOTH) {
+ lines.add(
+ Text.literal(String.format("%-19s", "3 Day Avg. Price:"))
+ .formatted(Formatting.GOLD)
+ .append(TooltipInfoType.THREE_DAY_AVERAGE.getData().get(neuName) == null
+ ? Text.literal("No data").formatted(Formatting.RED)
+ : ItemTooltip.getCoinsMessage(TooltipInfoType.THREE_DAY_AVERAGE.getData().get(neuName).getAsDouble(), itemStack.getCount())
+ )
+ );
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BazaarPriceTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BazaarPriceTooltip.java
new file mode 100644
index 00000000..0aab21c0
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BazaarPriceTooltip.java
@@ -0,0 +1,57 @@
+package de.hysky.skyblocker.skyblock.item.tooltip.adders;
+
+import com.google.gson.JsonObject;
+import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType;
+import net.minecraft.item.ItemStack;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import org.apache.commons.lang3.math.NumberUtils;
+
+import java.util.List;
+
+public class BazaarPriceTooltip extends TooltipAdder {
+ public static boolean bazaarExist = false;
+
+ public BazaarPriceTooltip(int priority) {
+ super(priority);
+ }
+
+ @Override
+ public void addToTooltip(List<Text> lines, Slot focusedSlot) {
+ bazaarExist = false;
+ final ItemStack itemStack = focusedSlot.getStack();
+ final String internalID = itemStack.getSkyblockId();
+ if (internalID == null) return;
+ String name = itemStack.getSkyblockApiId();
+ if (name == null) return;
+
+ if (name.startsWith("ISSHINY_")) name = "SHINY_" + internalID;
+
+ if (TooltipInfoType.BAZAAR.isTooltipEnabledAndHasOrNullWarning(name)) {
+ int amount;
+ if (lines.get(1).getString().endsWith("Sack")) {
+ //The amount is in the 2nd sibling of the 3rd line of the lore. here V
+ //Example line: empty[style={color=dark_purple,!italic}, siblings=[literal{Stored: }[style={color=gray}], literal{0}[style={color=dark_gray}], literal{/20k}[style={color=gray}]]
+ String line = lines.get(3).getSiblings().get(1).getString().replace(",", "");
+ amount = NumberUtils.isParsable(line) && !line.equals("0") ? Integer.parseInt(line) : itemStack.getCount();
+ } else {
+ amount = itemStack.getCount();
+ }
+ JsonObject getItem = TooltipInfoType.BAZAAR.getData().getAsJsonObject(name);
+ lines.add(Text.literal(String.format("%-18s", "Bazaar buy Price:"))
+ .formatted(Formatting.GOLD)
+ .append(getItem.get("buyPrice").isJsonNull()
+ ? Text.literal("No data").formatted(Formatting.RED)
+ : ItemTooltip.getCoinsMessage(getItem.get("buyPrice").getAsDouble(), amount)));
+ lines.add(Text.literal(String.format("%-19s", "Bazaar sell Price:"))
+ .formatted(Formatting.GOLD)
+ .append(getItem.get("sellPrice").isJsonNull()
+ ? Text.literal("No data").formatted(Formatting.RED)
+ : ItemTooltip.getCoinsMessage(getItem.get("sellPrice").getAsDouble(), amount)));
+ bazaarExist = true;
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ColorTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ColorTooltip.java
new file mode 100644
index 00000000..2b576be6
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ColorTooltip.java
@@ -0,0 +1,135 @@
+package de.hysky.skyblocker.skyblock.item.tooltip.adders;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType;
+import de.hysky.skyblocker.utils.Constants;
+import de.hysky.skyblocker.utils.ItemUtils;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.DyedColorComponent;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.StringIdentifiable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+public class ColorTooltip extends TooltipAdder {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ColorTooltip.class);
+
+ public ColorTooltip(int priority) {
+ super(priority);
+ }
+
+ @Override
+ public void addToTooltip(List<Text> lines, Slot focusedSlot) {
+ final ItemStack itemStack = focusedSlot.getStack();
+ final String internalID = itemStack.getSkyblockId();
+ if (TooltipInfoType.COLOR.isTooltipEnabledAndHasOrNullWarning(internalID) && itemStack.contains(DataComponentTypes.DYED_COLOR)) {
+ String uuid = ItemUtils.getItemUuid(itemStack);
+ boolean hasCustomDye = SkyblockerConfigManager.get().general.customDyeColors.containsKey(uuid) || SkyblockerConfigManager.get().general.customAnimatedDyes.containsKey(uuid);
+ //DyedColorComponent#getColor returns ARGB so we mask out the alpha bits
+ int dyeColor = DyedColorComponent.getColor(itemStack, 0);
+
+ // dyeColor will have alpha = 255 if it's dyed, and alpha = 0 if it's not dyed,
+ if (!hasCustomDye && dyeColor != 0) {
+ dyeColor = dyeColor & 0x00FFFFFF;
+ String colorHex = String.format("%06X", dyeColor);
+ String expectedHex = getExpectedHex(internalID);
+
+ boolean correctLine = false;
+ for (Text text : lines) {
+ String existingTooltip = text.getString() + " ";
+ if (existingTooltip.startsWith("Color: ")) {
+ correctLine = true;
+
+ addExoticTooltip(lines, internalID, ItemUtils.getCustomData(itemStack), colorHex, expectedHex, existingTooltip);
+ break;
+ }
+ }
+
+ if (!correctLine) {
+ addExoticTooltip(lines, internalID, ItemUtils.getCustomData(itemStack), colorHex, expectedHex, "");
+ }
+ }
+ }
+ }
+
+ private static void addExoticTooltip(List<Text> lines, String internalID, NbtCompound customData, String colorHex, String expectedHex, String existingTooltip) {
+ if (expectedHex != null && !colorHex.equalsIgnoreCase(expectedHex) && !isException(internalID, colorHex) && !intendedDyed(customData)) {
+ final DyeType type = checkDyeType(colorHex);
+ lines.add(1, Text.literal(existingTooltip + Formatting.DARK_GRAY + "(").append(type.getTranslatedText()).append(Formatting.DARK_GRAY + ")"));
+ }
+ }
+
+ public static String getExpectedHex(String id) {
+ String color = TooltipInfoType.COLOR.getData().get(id).getAsString();
+ if (color != null) {
+ String[] RGBValues = color.split(",");
+ return String.format("%02X%02X%02X", Integer.parseInt(RGBValues[0]), Integer.parseInt(RGBValues[1]), Integer.parseInt(RGBValues[2]));
+ } else {
+ LOGGER.warn("[Skyblocker Exotics] No expected color data found for id {}", id);
+ return null;
+ }
+ }
+
+ public static boolean isException(String id, String hex) {
+ return switch (id) {
+ case String it when it.startsWith("LEATHER") || it.equals("GHOST_BOOTS") || Constants.SEYMOUR_IDS.contains(it) -> true;
+ case String it when it.startsWith("RANCHER") -> Constants.RANCHERS.contains(hex);
+ case String it when it.contains("ADAPTIVE_CHESTPLATE") -> Constants.ADAPTIVE_CHEST.contains(hex);
+ case String it when it.contains("ADAPTIVE") -> Constants.ADAPTIVE.contains(hex);
+ case String it when it.contains("REAPER") -> Constants.REAPER.contains(hex);
+ case String it when it.contains("FAIRY") -> Constants.FAIRY_HEXES.contains(hex);
+ case String it when it.contains("CRYSTAL") -> Constants.CRYSTAL_HEXES.contains(hex);
+ case String it when it.contains("SPOOK") -> Constants.SPOOK.contains(hex);
+ default -> false;
+ };
+ }
+
+ public static DyeType checkDyeType(String hex) {
+ return switch (hex) {
+ case String it when Constants.CRYSTAL_HEXES.contains(it) -> DyeType.CRYSTAL;
+ case String it when Constants.FAIRY_HEXES.contains(it) -> DyeType.FAIRY;
+ case String it when Constants.OG_FAIRY_HEXES.contains(it) -> DyeType.OG_FAIRY;
+ case String it when Constants.SPOOK.contains(it) -> DyeType.SPOOK;
+ case String it when Constants.GLITCHED.contains(it) -> DyeType.GLITCHED;
+ default -> DyeType.EXOTIC;
+ };
+ }
+
+ public static boolean intendedDyed(NbtCompound customData) {
+ return customData.contains("dye_item");
+ }
+
+ public enum DyeType implements StringIdentifiable {
+ CRYSTAL("crystal", Formatting.AQUA),
+ FAIRY("fairy", Formatting.LIGHT_PURPLE),
+ OG_FAIRY("og_fairy", Formatting.DARK_PURPLE),
+ SPOOK("spook", Formatting.RED),
+ GLITCHED("glitched", Formatting.BLUE),
+ EXOTIC("exotic", Formatting.GOLD);
+ private final String name;
+ private final Formatting formatting;
+
+ DyeType(String name, Formatting formatting) {
+ this.name = name;
+ this.formatting = formatting;
+ }
+
+ @Override
+ public String asString() {
+ return name;
+ }
+
+ public MutableText getTranslatedText() {
+ return Text.translatable("skyblocker.exotic." + name).formatted(formatting);
+ }
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/DungeonQualityTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/DungeonQualityTooltip.java
new file mode 100644
index 00000000..05e9887c
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/DungeonQualityTooltip.java
@@ -0,0 +1,57 @@
+package de.hysky.skyblocker.skyblock.item.tooltip.adders;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder;
+import de.hysky.skyblocker.utils.ItemUtils;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.List;
+
+public class DungeonQualityTooltip extends TooltipAdder {
+ public DungeonQualityTooltip(int priority) {
+ super(priority);
+ }
+
+ @Override
+ public void addToTooltip(List<Text> lines, Slot focusedSlot) {
+ if (!SkyblockerConfigManager.get().general.itemTooltip.dungeonQuality) return;
+ NbtCompound customData = ItemUtils.getCustomData(focusedSlot.getStack());
+ if (customData == null || !customData.contains("baseStatBoostPercentage")) return;
+ int baseStatBoostPercentage = customData.getInt("baseStatBoostPercentage");
+ boolean maxQuality = baseStatBoostPercentage == 50;
+ if (maxQuality) {
+ lines.add(Text.literal(String.format("%-17s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.RED).formatted(Formatting.BOLD));
+ } else {
+ lines.add(Text.literal(String.format("%-21s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.BLUE));
+ }
+
+ if (customData.contains("item_tier")) { // sometimes it just isn't here?
+ int itemTier = customData.getInt("item_tier");
+ if (maxQuality) {
+ lines.add(Text.literal(String.format("%-17s", "Floor Tier:") + itemTier + " (" + getItemTierFloor(itemTier) + ")").formatted(Formatting.RED).formatted(Formatting.BOLD));
+ } else {
+ lines.add(Text.literal(String.format("%-21s", "Floor Tier:") + itemTier + " (" + getItemTierFloor(itemTier) + ")").formatted(Formatting.BLUE));
+ }
+ }
+ }
+
+ final String getItemTierFloor(int tier) {
+ return switch (tier) {
+ case 0 -> "E";
+ case 1 -> "F1";
+ case 2 -> "F2";
+ case 3 -> "F3";
+ case 4 -> "F4/M1";
+ case 5 -> "F5/M2";
+ case 6 -> "F6/M3";
+ case 7 -> "F7/M4";
+ case 8 -> "M5";
+ case 9 -> "M6";
+ case 10 -> "M7";
+ default -> "Unknown";
+ };
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LBinTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LBinTooltip.java
new file mode 100644
index 00000000..45cfe3e4
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LBinTooltip.java
@@ -0,0 +1,40 @@
+package de.hysky.skyblocker.skyblock.item.tooltip.adders;
+
+import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType;
+import net.minecraft.item.ItemStack;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.List;
+
+public class LBinTooltip extends TooltipAdder {
+ public static boolean lbinExist = false;
+
+ public LBinTooltip(int priority) {
+ super(priority);
+ }
+
+ @Override
+ public void addToTooltip(List<Text> lines, Slot focusedSlot) {
+ lbinExist = false;
+ final ItemStack itemStack = focusedSlot.getStack();
+ final String internalID = itemStack.getSkyblockId();
+ if (internalID == null) return;
+ String name = itemStack.getSkyblockApiId();
+ if (name == null) return;
+
+ if (name.startsWith("ISSHINY_")) name = "SHINY_" + internalID;
+
+ // bazaarOpened & bazaarExist check for lbin, because Skytils keeps some bazaar item data in lbin api
+
+ if (TooltipInfoType.LOWEST_BINS.isTooltipEnabledAndHasOrNullWarning(name) && !BazaarPriceTooltip.bazaarExist) {
+ lines.add(Text.literal(String.format("%-19s", "Lowest BIN Price:"))
+ .formatted(Formatting.GOLD)
+ .append(ItemTooltip.getCoinsMessage(TooltipInfoType.LOWEST_BINS.getData().get(name).getAsDouble(), itemStack.getCount())));
+ lbinExist = true;
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LineSmoothener.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LineSmoothener.java
new file mode 100644
index 00000000..e3ce12df
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LineSmoothener.java
@@ -0,0 +1,32 @@
+package de.hysky.skyblocker.skyblock.item.tooltip.adders;
+
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.List;
+
+public class LineSmoothener extends TooltipAdder {
+ //This is static to not create a new text object for each line in every item
+ private static final Text BUMPY_LINE = Text.literal("-----------------").formatted(Formatting.DARK_GRAY, Formatting.STRIKETHROUGH);
+
+ public static Text createSmoothLine() {
+ return Text.literal(" ").formatted(Formatting.DARK_GRAY, Formatting.STRIKETHROUGH, Formatting.BOLD);
+ }
+
+ public LineSmoothener() {
+ super(Integer.MIN_VALUE);
+ }
+
+ @Override
+ public void addToTooltip(List<Text> lines, Slot focusedSlot) {
+ for (int i = 0; i < lines.size(); i++) {
+ List<Text> lineSiblings = lines.get(i).getSiblings();
+ //Compare the first sibling rather than the whole object as the style of the root object can change while visually staying the same
+ if (lineSiblings.size() == 1 && lineSiblings.getFirst().equals(BUMPY_LINE)) {
+ lines.set(i, createSmoothLine());
+ }
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MotesTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MotesTooltip.java
new file mode 100644
index 00000000..64640b95
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MotesTooltip.java
@@ -0,0 +1,50 @@
+package de.hysky.skyblocker.skyblock.item.tooltip.adders;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType;
+import net.minecraft.item.ItemStack;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.List;
+import java.util.Locale;
+
+public class MotesTooltip extends TooltipAdder {
+ public MotesTooltip(int priority) {
+ super(priority);
+ }
+
+ @Override
+ public void addToTooltip(List<Text> lines, Slot focusedSlot) {
+ final ItemStack itemStack = focusedSlot.getStack();
+ final String internalID = itemStack.getSkyblockId();
+ if (internalID != null && TooltipInfoType.MOTES.isTooltipEnabledAndHasOrNullWarning(internalID)) {
+ lines.add(Text.literal(String.format("%-20s", "Motes Price:"))
+ .formatted(Formatting.LIGHT_PURPLE)
+ .append(getMotesMessage(TooltipInfoType.MOTES.getData().get(internalID).getAsInt(), itemStack.getCount())));
+ }
+ }
+
+ private static Text getMotesMessage(int price, int count) {
+ float motesMultiplier = SkyblockerConfigManager.get().otherLocations.rift.mcGrubberStacks * 0.05f + 1;
+
+ // Calculate the total price
+ int totalPrice = price * count;
+ String totalPriceString = String.format(Locale.ENGLISH, "%1$,.1f", totalPrice * motesMultiplier);
+
+ // If count is 1, return a simple message
+ if (count == 1) {
+ return Text.literal(totalPriceString.replace(".0", "") + " Motes").formatted(Formatting.DARK_AQUA);
+ }
+
+ // If count is greater than 1, include the "each" information
+ String eachPriceString = String.format(Locale.ENGLISH, "%1$,.1f", price * motesMultiplier);
+ MutableText message = Text.literal(totalPriceString.replace(".0", "") + " Motes ").formatted(Formatting.DARK_AQUA);
+ message.append(Text.literal("(" + eachPriceString.replace(".0", "") + " each)").formatted(Formatting.GRAY));
+
+ return message;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java
new file mode 100644
index 00000000..1c64760a
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java
@@ -0,0 +1,49 @@
+package de.hysky.skyblocker.skyblock.item.tooltip.adders;
+
+import de.hysky.skyblocker.skyblock.item.MuseumItemCache;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType;
+import de.hysky.skyblocker.utils.ItemUtils;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.List;
+
+public class MuseumTooltip extends TooltipAdder {
+ public MuseumTooltip(int priority) {
+ super(priority);
+ }
+
+ @Override
+ public void addToTooltip(List<Text> lines, Slot focusedSlot) {
+ final ItemStack itemStack = focusedSlot.getStack();
+ final String internalID = itemStack.getSkyblockId();
+ if (TooltipInfoType.MUSEUM.isTooltipEnabledAndHasOrNullWarning(internalID)) {
+ String itemCategory = TooltipInfoType.MUSEUM.getData().get(internalID).getAsString();
+ String format = switch (itemCategory) {
+ case "Weapons" -> "%-18s";
+ case "Armor" -> "%-19s";
+ default -> "%-20s";
+ };
+
+ //Special case the special category so that it doesn't always display not donated
+ if (itemCategory.equals("Special")) {
+ lines.add(Text.literal(String.format(format, "Museum: (" + itemCategory + ")"))
+ .formatted(Formatting.LIGHT_PURPLE));
+ } else {
+ NbtCompound customData = ItemUtils.getCustomData(itemStack);
+ boolean isInMuseum = (customData.contains("donated_museum") && customData.getBoolean("donated_museum")) || MuseumItemCache.hasItemInMuseum(internalID);
+
+ Formatting donatedIndicatorFormatting = isInMuseum ? Formatting.GREEN : Formatting.RED;
+
+ lines.add(Text.literal(String.format(format, "Museum (" + itemCategory + "):"))
+ .formatted(Formatting.LIGHT_PURPLE)
+ .append(Text.literal(isInMuseum ? "✔" : "✖").formatted(donatedIndicatorFormatting, Formatting.BOLD))
+ .append(Text.literal(isInMuseum ? " Donated" : " Not Donated").formatted(donatedIndicatorFormatting)));
+ }
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java
new file mode 100644
index 00000000..3ac7d298
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java
@@ -0,0 +1,28 @@
+package de.hysky.skyblocker.skyblock.item.tooltip.adders;
+
+import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType;
+import net.minecraft.item.ItemStack;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.List;
+
+public class NpcPriceTooltip extends TooltipAdder {
+ public NpcPriceTooltip(int priority) {
+ super(priority);
+ }
+
+ @Override
+ public void addToTooltip(List<Text> lines, Slot focusedSlot) {
+ final ItemStack stack = focusedSlot.getStack();
+ final String internalID = stack.getSkyblockId();
+ if (internalID != null && TooltipInfoType.NPC.isTooltipEnabledAndHasOrNullWarning(internalID)) {
+ lines.add(Text.literal(String.format("%-21s", "NPC Sell Price:"))
+ .formatted(Formatting.YELLOW)
+ .append(ItemTooltip.getCoinsMessage(TooltipInfoType.NPC.getData().get(internalID).getAsDouble(), stack.getCount())));
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ObtainedDateTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ObtainedDateTooltip.java
new file mode 100644
index 00000000..9cc03b4d
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ObtainedDateTooltip.java
@@ -0,0 +1,72 @@
+package de.hysky.skyblocker.skyblock.item.tooltip.adders;
+
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType;
+import de.hysky.skyblocker.utils.ItemUtils;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.nbt.NbtElement;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.TemporalAccessor;
+import java.util.List;
+import java.util.Locale;
+
+public class ObtainedDateTooltip extends TooltipAdder {
+ private static final DateTimeFormatter OBTAINED_DATE_FORMATTER = DateTimeFormatter.ofPattern("MMMM d, yyyy").withZone(ZoneId.systemDefault()).localizedBy(Locale.ENGLISH);
+ private static final DateTimeFormatter OLD_OBTAINED_DATE_FORMAT = DateTimeFormatter.ofPattern("M/d/yy h:m a").withZone(ZoneId.of("UTC")).localizedBy(Locale.ENGLISH);
+
+ public ObtainedDateTooltip(int priority) {
+ super(priority);
+ }
+
+ @Override
+ public void addToTooltip(List<Text> lines, Slot focusedSlot) {
+ if (TooltipInfoType.OBTAINED.isTooltipEnabled()) {
+ String timestamp = getTimestamp(focusedSlot.getStack());
+
+ if (!timestamp.isEmpty()) {
+ lines.add(Text.empty()
+ .append(Text.literal(String.format("%-21s", "Obtained: ")).formatted(Formatting.LIGHT_PURPLE))
+ .append(Text.literal(timestamp).formatted(Formatting.RED)));
+ }
+ }
+ }
+
+ /**
+ * This method converts the "timestamp" variable into the same date format as Hypixel represents it in the museum.
+ * Currently, there are two types of string timestamps the legacy which is built like this
+ * "dd/MM/yy hh:mm" ("25/04/20 16:38") and the current which is built like this
+ * "MM/dd/yy hh:mm aa" ("12/24/20 11:08 PM"). Since Hypixel transforms the two formats into one format without
+ * taking into account of their formats, we do the same. The final result looks like this
+ * "MMMM dd, yyyy" (December 24, 2020).
+ * Since the legacy format has a 25 as "month" SimpleDateFormat converts the 25 into 2 years and 1 month and makes
+ * "25/04/20 16:38" -> "January 04, 2022" instead of "April 25, 2020".
+ * This causes the museum rank to be much worse than it should be.
+ * <p>
+ * This also handles the long timestamp format introduced in January 2024 where the timestamp is in epoch milliseconds.
+ *
+ * @param stack the item under the pointer
+ * @return if the item have a "Timestamp" it will be shown formated on the tooltip
+ */
+ public static String getTimestamp(ItemStack stack) {
+ NbtCompound customData = ItemUtils.getCustomData(stack);
+
+ if (customData != null && customData.contains("timestamp", NbtElement.LONG_TYPE)) {
+ Instant date = Instant.ofEpochMilli(customData.getLong("timestamp"));
+ return OBTAINED_DATE_FORMATTER.format(date);
+ }
+
+ if (customData != null && customData.contains("timestamp", NbtElement.STRING_TYPE)) {
+ TemporalAccessor date = OLD_OBTAINED_DATE_FORMAT.parse(customData.getString("timestamp"));
+ return OBTAINED_DATE_FORMATTER.format(date);
+ }
+
+ return "";
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/SupercraftReminder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/SupercraftReminder.java
new file mode 100644
index 00000000..8a8f198c
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/SupercraftReminder.java
@@ -0,0 +1,30 @@
+package de.hysky.skyblocker.skyblock.item.tooltip.adders;
+
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder;
+import de.hysky.skyblocker.utils.ItemUtils;
+import net.minecraft.item.Items;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class SupercraftReminder extends TooltipAdder {
+ private static final byte SUPERCRAFT_SLOT = 32;
+ private static final byte RECIPE_RESULT_SLOT = 25;
+
+ public SupercraftReminder() {
+ super(Pattern.compile("^.+ Recipe$"), Integer.MIN_VALUE);
+ }
+
+ @Override
+ public void addToTooltip(List<Text> lines, Slot focusedSlot) {
+ if (focusedSlot.id != SUPERCRAFT_SLOT || !focusedSlot.getStack().isOf(Items.GOLDEN_PICKAXE)) return;
+ String uuid = ItemUtils.getItemUuid(focusedSlot.inventory.getStack(RECIPE_RESULT_SLOT));
+ if (!uuid.isEmpty()) return; //Items with UUID can't be stacked, and therefore the shift-click feature doesn't matter
+ int index = lines.size() - 1;
+ if (lines.get(lines.size() - 2).getString().equals("Recipe not unlocked!")) index--; //Place it right below the "Right-Click to set amount" line
+ lines.add(index, Text.literal("Shift-Click to maximize the amount!").formatted(Formatting.GOLD));
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ResultButtonWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ResultButtonWidget.java
index d2d463c7..9e2ec0b3 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ResultButtonWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ResultButtonWidget.java
@@ -1,30 +1,29 @@
package de.hysky.skyblocker.skyblock.itemlist;
-import java.util.List;
-import java.util.ArrayList;
-
+import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
import net.minecraft.client.gui.widget.ClickableWidget;
import net.minecraft.item.ItemStack;
-import net.minecraft.item.Items;
-import net.minecraft.text.OrderedText;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
+import java.util.List;
+
public class ResultButtonWidget extends ClickableWidget {
private static final Identifier BACKGROUND_TEXTURE = new Identifier("recipe_book/slot_craftable");
protected ItemStack itemStack = null;
public ResultButtonWidget(int x, int y) {
- super(x, y, 25, 25, Text.of(""));
+ super(x, y, 25, 25, Text.literal(""));
}
protected void setItemStack(ItemStack itemStack) {
- this.active = !itemStack.getItem().equals(Items.AIR);
+ this.active = !itemStack.isEmpty();
this.visible = true;
this.itemStack = itemStack;
}
@@ -37,29 +36,18 @@ public class ResultButtonWidget extends ClickableWidget {
@Override
public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
MinecraftClient client = MinecraftClient.getInstance();
- // this.drawTexture(matrices, this.x, this.y, 29, 206, this.width, this.height);
context.drawGuiTexture(BACKGROUND_TEXTURE, this.getX(), this.getY(), this.getWidth(), this.getHeight());
- // client.getItemRenderer().renderInGui(this.itemStack, this.x + 4, this.y + 4);
context.drawItem(this.itemStack, this.getX() + 4, this.getY() + 4);
- // client.getItemRenderer().renderGuiItemOverlay(client.textRenderer, itemStack, this.x + 4, this.y + 4);
context.drawItemInSlot(client.textRenderer, itemStack, this.getX() + 4, this.getY() + 4);
}
public void renderTooltip(DrawContext context, int mouseX, int mouseY) {
MinecraftClient client = MinecraftClient.getInstance();
- List<Text> tooltip = Screen.getTooltipFromItem(client, this.itemStack);
- List<OrderedText> orderedTooltip = new ArrayList<>();
-
- for(int i = 0; i < tooltip.size(); i++) {
- orderedTooltip.add(tooltip.get(i).asOrderedText());
- }
-
- client.currentScreen.setTooltip(orderedTooltip);
+ if (client.currentScreen == null) return;
+ List<Text> tooltip = client.currentScreen instanceof HandledScreen<?> handledScreen ? ((HandledScreenAccessor) handledScreen).invokeGetTooltipFromItem(this.itemStack) : Screen.getTooltipFromItem(client, this.itemStack);
+ client.currentScreen.setTooltip(tooltip.stream().map(Text::asOrderedText).toList());
}
- @Override
- protected void appendClickableNarrations(NarrationMessageBuilder builder) {
- // TODO Auto-generated method stub
-
- }
+ @Override
+ protected void appendClickableNarrations(NarrationMessageBuilder builder) {}
}