aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de
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
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')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java5
-rw-r--r--src/main/java/de/hysky/skyblocker/injected/SkyblockerStack.java20
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/DrawContextMixin.java52
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java543
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java110
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/accessors/HandledScreenAccessor.java8
-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
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/ItemUtils.java23
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/RomanNumerals.java54
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Utils.java3
45 files changed, 1987 insertions, 970 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 19eb395a..7b379495 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -31,11 +31,12 @@ import de.hysky.skyblocker.skyblock.garden.FarmingHud;
import de.hysky.skyblocker.skyblock.garden.LowerSensitivity;
import de.hysky.skyblocker.skyblock.garden.VisitorHelper;
import de.hysky.skyblocker.skyblock.item.*;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotTextManager;
import de.hysky.skyblocker.skyblock.item.tooltip.AccessoriesHelper;
import de.hysky.skyblocker.skyblock.item.tooltip.BackpackPreview;
import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipManager;
import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
-import de.hysky.skyblocker.skyblock.quicknav.QuickNav;
import de.hysky.skyblocker.skyblock.rift.TheRift;
import de.hysky.skyblocker.skyblock.searchoverlay.SearchOverManager;
import de.hysky.skyblocker.skyblock.shortcut.Shortcuts;
@@ -187,6 +188,8 @@ public class SkyblockerMod implements ClientModInitializer {
EggFinder.init();
TimeTowerReminder.init();
SkyblockTime.init();
+ TooltipManager.init();
+ SlotTextManager.init();
Scheduler.INSTANCE.scheduleCyclic(Utils::update, 20);
Scheduler.INSTANCE.scheduleCyclic(DiscordRPCManager::updateDataAndPresence, 200);
diff --git a/src/main/java/de/hysky/skyblocker/injected/SkyblockerStack.java b/src/main/java/de/hysky/skyblocker/injected/SkyblockerStack.java
new file mode 100644
index 00000000..2f54917b
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/injected/SkyblockerStack.java
@@ -0,0 +1,20 @@
+package de.hysky.skyblocker.injected;
+
+import org.jetbrains.annotations.Nullable;
+
+public interface SkyblockerStack {
+ @Nullable
+ default String getSkyblockId() {
+ return "";
+ }
+
+ @Nullable
+ default String getSkyblockApiId() {
+ return "";
+ }
+
+ @Nullable
+ default String getNeuName() {
+ return "";
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/mixins/DrawContextMixin.java b/src/main/java/de/hysky/skyblocker/mixins/DrawContextMixin.java
index 7964b114..9f09c37a 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/DrawContextMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/DrawContextMixin.java
@@ -2,67 +2,15 @@ package de.hysky.skyblocker.mixins;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
-import com.llamalad7.mixinextras.sugar.ref.LocalRef;
-import de.hysky.skyblocker.config.SkyblockerConfigManager;
-import de.hysky.skyblocker.skyblock.item.AttributeShards;
import de.hysky.skyblocker.skyblock.item.ItemCooldowns;
-import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
-import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
-import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.item.ItemStack;
-import net.minecraft.nbt.NbtCompound;
-import net.minecraft.util.Formatting;
-import org.jetbrains.annotations.Nullable;
-import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(DrawContext.class)
public abstract class DrawContextMixin {
- @Shadow
- @Final
- private MatrixStack matrices;
-
- @Shadow
- public abstract int drawText(TextRenderer textRenderer, @Nullable String text, int x, int y, int color, boolean shadow);
-
- @Inject(method = "drawItemInSlot(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V", at = @At("HEAD"))
- private void skyblocker$renderAttributeShardDisplay(CallbackInfo ci, @Local(argsOnly = true) TextRenderer textRenderer, @Local(argsOnly = true) ItemStack stack, @Local(argsOnly = true, ordinal = 0) int x, @Local(argsOnly = true, ordinal = 1) int y, @Local(argsOnly = true) LocalRef<String> countOverride) {
- if (!SkyblockerConfigManager.get().general.itemInfoDisplay.attributeShardInfo) return;
-
- if (Utils.isOnSkyblock()) {
- NbtCompound customData = ItemUtils.getCustomData(stack);
-
- if (ItemUtils.getItemId(stack).equals("ATTRIBUTE_SHARD")) {
- NbtCompound attributesTag = customData.getCompound("attributes");
- String[] attributes = attributesTag.getKeys().toArray(String[]::new);
-
- if (attributes.length != 0) {
- String attributeId = attributes[0];
- int attributeLevel = attributesTag.getInt(attributeId);
-
- //Set item count
- countOverride.set(Integer.toString(attributeLevel));
-
- //Draw the attribute name
- this.matrices.push();
- this.matrices.translate(0f, 0f, 200f);
-
- String attributeInitials = AttributeShards.getShortName(attributeId);
-
- this.drawText(textRenderer, attributeInitials, x, y, Formatting.AQUA.getColorValue(), true);
-
- this.matrices.pop();
- }
- }
- }
- }
-
@ModifyExpressionValue(method = "drawItemInSlot(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/ItemCooldownManager;getCooldownProgress(Lnet/minecraft/item/Item;F)F"))
private float skyblocker$modifyItemCooldown(float cooldownProgress, @Local(argsOnly = true) ItemStack stack) {
diff --git a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java
index f662be7c..35b91639 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java
@@ -1,5 +1,6 @@
package de.hysky.skyblocker.mixins;
+import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.systems.RenderSystem;
import de.hysky.skyblocker.SkyblockerMod;
@@ -12,8 +13,11 @@ import de.hysky.skyblocker.skyblock.garden.VisitorHelper;
import de.hysky.skyblocker.skyblock.item.ItemProtection;
import de.hysky.skyblocker.skyblock.item.ItemRarityBackgrounds;
import de.hysky.skyblocker.skyblock.item.WikiLookup;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotTextManager;
import de.hysky.skyblocker.skyblock.item.tooltip.BackpackPreview;
import de.hysky.skyblocker.skyblock.item.tooltip.CompactorDeletorPreview;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipManager;
import de.hysky.skyblocker.skyblock.quicknav.QuickNav;
import de.hysky.skyblocker.skyblock.quicknav.QuickNavButton;
import de.hysky.skyblocker.utils.ItemUtils;
@@ -22,6 +26,7 @@ import de.hysky.skyblocker.utils.render.gui.ContainerSolver;
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.util.math.MatrixStack;
import net.minecraft.inventory.SimpleInventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
@@ -33,10 +38,7 @@ import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import org.spongepowered.asm.mixin.Final;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Shadow;
-import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
@@ -50,251 +52,290 @@ import java.util.regex.Matcher;
@Mixin(HandledScreen.class)
public abstract class HandledScreenMixin<T extends ScreenHandler> extends Screen {
- /**
- * This is the slot id returned for when a click is outside the screen's bounds
- */
- @Unique
- private static final int OUT_OF_BOUNDS_SLOT = -999;
-
- @Unique
- private static final Identifier ITEM_PROTECTION = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/item_protection.png");
-
- @Unique
- private static final Set<String> FILLER_ITEMS = Set.of(
- " ", // Empty menu item
- "Locked Page",
- "Quick Crafting Slot",
- "Locked Backpack Slot 2", //Regular expressions won't be utilized here since the search by contains is based on plain text rather than regex syntax
- "Locked Backpack Slot 3",
- "Locked Backpack Slot 4",
- "Locked Backpack Slot 5",
- "Locked Backpack Slot 6",
- "Locked Backpack Slot 7",
- "Locked Backpack Slot 8",
- "Locked Backpack Slot 9",
- "Locked Backpack Slot 10",
- "Locked Backpack Slot 11",
- "Locked Backpack Slot 12",
- "Locked Backpack Slot 13",
- "Locked Backpack Slot 14",
- "Locked Backpack Slot 15",
- "Locked Backpack Slot 16",
- "Locked Backpack Slot 17",
- "Locked Backpack Slot 18",
- "Preparing"
- );
-
- @Shadow
- @Nullable
- protected Slot focusedSlot;
-
- @Shadow
- @Final
- protected T handler;
-
- @Unique
- private List<QuickNavButton> quickNavButtons;
-
- protected HandledScreenMixin(Text title) {
- super(title);
- }
-
- @Inject(method = "init", at = @At("RETURN"))
- private void skyblocker$initQuickNav(CallbackInfo ci) {
- if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().quickNav.enableQuickNav && client != null && client.player != null && !client.player.isCreative()) {
- for (QuickNavButton quickNavButton : quickNavButtons = QuickNav.init(getTitle().getString().trim())) {
- addSelectableChild(quickNavButton);
- }
- }
- }
-
- @Inject(at = @At("HEAD"), method = "keyPressed")
- public void skyblocker$keyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable<Boolean> cir) {
- if (this.client != null && this.focusedSlot != null && keyCode != 256) {
- //wiki lookup
- if (!this.client.options.inventoryKey.matchesKey(keyCode, scanCode) && WikiLookup.wikiLookup.matchesKey(keyCode, scanCode) && client.player != null) {
- WikiLookup.openWiki(this.focusedSlot, client.player);
- }
- //item protection
- if (!this.client.options.inventoryKey.matchesKey(keyCode, scanCode) && ItemProtection.itemProtection.matchesKey(keyCode, scanCode)) {
- ItemProtection.handleKeyPressed(this.focusedSlot.getStack());
- }
- }
- }
-
- @Inject(at = @At("HEAD"), method = "mouseClicked")
- public void skyblocker$mouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) {
- if (SkyblockerConfigManager.get().farming.garden.visitorHelper && (Utils.getLocationRaw().equals("garden") && !getTitle().getString().contains("Logbook") || getTitle().getString().startsWith("Bazaar"))) {
- VisitorHelper.onMouseClicked(mouseX, mouseY, button, this.textRenderer);
- }
- }
-
- /**
- * Draws the unselected tabs in front of the background blur, but behind the main inventory, similar to creative inventory tabs
- */
- @Inject(method = "renderBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawBackground(Lnet/minecraft/client/gui/DrawContext;FII)V"))
- private void skyblocker$drawUnselectedQuickNavButtons(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
- if (quickNavButtons != null) for (QuickNavButton quickNavButton : quickNavButtons) {
- if (!quickNavButton.toggled()) {
- quickNavButton.render(context, mouseX, mouseY, delta);
- }
- }
- }
-
- /**
- * Draws the selected tab in front of the background blur and the main inventory, similar to creative inventory tabs
- */
- @Inject(method = "renderBackground", at = @At("RETURN"))
- private void skyblocker$drawSelectedQuickNavButtons(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
- if (quickNavButtons != null) for (QuickNavButton quickNavButton : quickNavButtons) {
- if (quickNavButton.toggled()) {
- quickNavButton.render(context, mouseX, mouseY, delta);
- }
- }
- }
-
- @SuppressWarnings("DataFlowIssue")
- // makes intellij be quiet about this.focusedSlot maybe being null. It's already null checked in mixined method.
- @Inject(method = "drawMouseoverTooltip", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTooltip(Lnet/minecraft/client/font/TextRenderer;Ljava/util/List;Ljava/util/Optional;II)V"), cancellable = true)
- public void skyblocker$drawMouseOverTooltip(DrawContext context, int x, int y, CallbackInfo ci, @Local(ordinal = 0) ItemStack stack) {
- if (!Utils.isOnSkyblock()) return;
-
- // Hide Empty Tooltips
- if (SkyblockerConfigManager.get().uiAndVisuals.hideEmptyTooltips && stack.getName().getString().equals(" ")) {
- ci.cancel();
- }
-
- // Backpack Preview
- boolean shiftDown = SkyblockerConfigManager.get().uiAndVisuals.backpackPreviewWithoutShift ^ Screen.hasShiftDown();
- if (shiftDown && getTitle().getString().equals("Storage") && focusedSlot.inventory != client.player.getInventory() && BackpackPreview.renderPreview(context, this, focusedSlot.getIndex(), x, y)) {
- ci.cancel();
- }
-
- // Compactor Preview
- if (SkyblockerConfigManager.get().uiAndVisuals.compactorDeletorPreview) {
- Matcher matcher = CompactorDeletorPreview.NAME.matcher(ItemUtils.getItemId(stack));
- if (matcher.matches() && CompactorDeletorPreview.drawPreview(context, stack, matcher.group("type"), matcher.group("size"), x, y)) {
- ci.cancel();
- }
- }
- }
-
- @ModifyVariable(method = "drawMouseoverTooltip", at = @At(value = "LOAD", ordinal = 0))
- private ItemStack skyblocker$experimentSolvers$replaceTooltipDisplayStack(ItemStack stack) {
- return skyblocker$experimentSolvers$getStack(focusedSlot, stack);
- }
-
- @ModifyVariable(method = "drawSlot", at = @At(value = "LOAD", ordinal = 3), ordinal = 0)
- private ItemStack skyblocker$experimentSolvers$replaceDisplayStack(ItemStack stack, DrawContext context, Slot slot) {
- return skyblocker$experimentSolvers$getStack(slot, stack);
- }
-
- /**
- * Redirects getStack calls to account for different stacks in experiment solvers.
- */
- @Unique
- private ItemStack skyblocker$experimentSolvers$getStack(Slot slot, @NotNull ItemStack stack) {
- ContainerSolver currentSolver = SkyblockerMod.getInstance().containerSolverManager.getCurrentSolver();
- if ((currentSolver instanceof SuperpairsSolver || currentSolver instanceof UltrasequencerSolver) && ((ExperimentSolver) currentSolver).getState() == ExperimentSolver.State.SHOW && slot.inventory instanceof SimpleInventory) {
- ItemStack itemStack = ((ExperimentSolver) currentSolver).getSlots().get(slot.getIndex());
- return itemStack == null ? stack : itemStack;
- }
- return stack;
- }
-
- /**
- * The naming of this method in yarn is half true, its mostly to handle slot/item interactions (which are mouse or keyboard clicks)
- * For example, using the drop key bind while hovering over an item will invoke this method to drop the players item
- *
- * @implNote This runs before {@link ScreenHandler#onSlotClick(int, int, SlotActionType, net.minecraft.entity.player.PlayerEntity)}
- */
- @Inject(method = "onMouseClick(Lnet/minecraft/screen/slot/Slot;IILnet/minecraft/screen/slot/SlotActionType;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;clickSlot(IIILnet/minecraft/screen/slot/SlotActionType;Lnet/minecraft/entity/player/PlayerEntity;)V"), cancellable = true)
- private void skyblocker$onSlotClick(Slot slot, int slotId, int button, SlotActionType actionType, CallbackInfo ci) {
- if (!Utils.isOnSkyblock()) return;
-
- // Item Protection
- // When you try and drop the item by picking it up then clicking outside the screen
- if (slotId == OUT_OF_BOUNDS_SLOT && ItemProtection.isItemProtected(this.handler.getCursorStack())) {
- ci.cancel();
- return;
- }
-
- if (slot == null) return;
- String title = getTitle().getString();
- ItemStack stack = skyblocker$experimentSolvers$getStack(slot, slot.getStack());
- ContainerSolver currentSolver = SkyblockerMod.getInstance().containerSolverManager.getCurrentSolver();
-
- // Prevent clicks on filler items
- if (SkyblockerConfigManager.get().uiAndVisuals.hideEmptyTooltips && FILLER_ITEMS.contains(stack.getName().getString()) &&
- // Allow clicks in Ultrasequencer and Superpairs
- (!UltrasequencerSolver.INSTANCE.getName().matcher(title).matches() || SkyblockerConfigManager.get().helpers.experiments.enableUltrasequencerSolver)) {
- ci.cancel();
- return;
- }
- // Item Protection
- // When you click your drop key while hovering over an item
- if (actionType == SlotActionType.THROW && ItemProtection.isItemProtected(stack)) {
- ci.cancel();
- return;
- }
- // Prevent salvaging
- if (title.equals("Salvage Items") && ItemProtection.isItemProtected(stack)) {
- ci.cancel();
- return;
- }
- if (this.handler instanceof GenericContainerScreenHandler genericContainerScreenHandler && genericContainerScreenHandler.getRows() == 6) {
- VisitorHelper.onSlotClick(slot, slotId, title, genericContainerScreenHandler.getSlot(13).getStack());
-
- // Prevent selling to NPC shops
- ItemStack sellStack = this.handler.slots.get(49).getStack();
- if (sellStack.getName().getString().equals("Sell Item") || ItemUtils.getLoreLineIf(sellStack, text -> text.contains("buyback")) != null) {
- if (slotId != 49 && ItemProtection.isItemProtected(stack)) {
- ci.cancel();
- return;
- }
- }
- }
-
- if (currentSolver != null) {
- boolean disallowed = SkyblockerMod.getInstance().containerSolverManager.onSlotClick(slotId, stack);
-
- if (disallowed) ci.cancel();
- }
-
- // Experiment Solvers
- if (currentSolver instanceof ExperimentSolver experimentSolver && experimentSolver.getState() == ExperimentSolver.State.SHOW && slot.inventory instanceof SimpleInventory) {
- switch (experimentSolver) {
- case ChronomatronSolver chronomatronSolver -> {
- Item item = chronomatronSolver.getChronomatronSlots().get(chronomatronSolver.getChronomatronCurrentOrdinal());
- if ((stack.isOf(item) || ChronomatronSolver.TERRACOTTA_TO_GLASS.get(stack.getItem()) == item) && chronomatronSolver.incrementChronomatronCurrentOrdinal() >= chronomatronSolver.getChronomatronSlots().size()) {
- chronomatronSolver.setState(ExperimentSolver.State.END);
- }
- }
-
- case SuperpairsSolver superpairsSolver -> {
- superpairsSolver.setSuperpairsPrevClickedSlot(slot.getIndex());
- superpairsSolver.setSuperpairsCurrentSlot(ItemStack.EMPTY);
- }
-
- case UltrasequencerSolver ultrasequencerSolver when slot.getIndex() == ultrasequencerSolver.getUltrasequencerNextSlot() -> {
- int count = ultrasequencerSolver.getSlots().get(ultrasequencerSolver.getUltrasequencerNextSlot()).getCount() + 1;
- ultrasequencerSolver.getSlots().entrySet().stream().filter(entry -> entry.getValue().getCount() == count).findAny().map(Map.Entry::getKey).ifPresentOrElse(ultrasequencerSolver::setUltrasequencerNextSlot, () -> ultrasequencerSolver.setState(ExperimentSolver.State.END));
- }
-
- default -> { /*Do Nothing*/ }
- }
- }
- }
-
- @Inject(method = "drawSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/item/ItemStack;III)V"))
- private void skyblocker$drawItemRarityBackground(DrawContext context, Slot slot, CallbackInfo ci) {
- if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().general.itemInfoDisplay.itemRarityBackgrounds)
- ItemRarityBackgrounds.tryDraw(slot.getStack(), context, slot.x, slot.y);
- // Item protection
- if (ItemProtection.isItemProtected(slot.getStack())) {
- RenderSystem.enableBlend();
- context.drawTexture(ITEM_PROTECTION, slot.x, slot.y, 0, 0, 16, 16, 16, 16);
- RenderSystem.disableBlend();
- }
- }
+ /**
+ * This is the slot id returned for when a click is outside the screen's bounds
+ */
+ @Unique
+ private static final int OUT_OF_BOUNDS_SLOT = -999;
+
+ @Unique
+ private static final Identifier ITEM_PROTECTION = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/item_protection.png");
+
+ @Unique
+ private static final Set<String> FILLER_ITEMS = Set.of(
+ " ", // Empty menu item
+ "Locked Page",
+ "Quick Crafting Slot",
+ "Locked Backpack Slot 2", //Regular expressions won't be utilized here since the search by contains is based on plain text rather than regex syntax
+ "Locked Backpack Slot 3",
+ "Locked Backpack Slot 4",
+ "Locked Backpack Slot 5",
+ "Locked Backpack Slot 6",
+ "Locked Backpack Slot 7",
+ "Locked Backpack Slot 8",
+ "Locked Backpack Slot 9",
+ "Locked Backpack Slot 10",
+ "Locked Backpack Slot 11",
+ "Locked Backpack Slot 12",
+ "Locked Backpack Slot 13",
+ "Locked Backpack Slot 14",
+ "Locked Backpack Slot 15",
+ "Locked Backpack Slot 16",
+ "Locked Backpack Slot 17",
+ "Locked Backpack Slot 18",
+ "Preparing"
+ );
+
+ @Shadow
+ @Nullable
+ protected Slot focusedSlot;
+
+ @Shadow
+ @Final
+ protected T handler;
+
+ @Shadow
+ protected abstract List<Text> getTooltipFromItem(ItemStack stack);
+
+ @Unique
+ private List<QuickNavButton> quickNavButtons;
+
+ protected HandledScreenMixin(Text title) {
+ super(title);
+ }
+
+ @Inject(method = "init", at = @At("RETURN"))
+ private void skyblocker$initQuickNav(CallbackInfo ci) {
+ if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().quickNav.enableQuickNav && client != null && client.player != null && !client.player.isCreative()) {
+ for (QuickNavButton quickNavButton : quickNavButtons = QuickNav.init(getTitle().getString().trim())) {
+ addSelectableChild(quickNavButton);
+ }
+ }
+ }
+
+ @Inject(at = @At("HEAD"), method = "keyPressed")
+ public void skyblocker$keyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable<Boolean> cir) {
+ if (this.client != null && this.focusedSlot != null && keyCode != 256) {
+ //wiki lookup
+ if (!this.client.options.inventoryKey.matchesKey(keyCode, scanCode) && WikiLookup.wikiLookup.matchesKey(keyCode, scanCode) && client.player != null) {
+ WikiLookup.openWiki(this.focusedSlot, client.player);
+ }
+ //item protection
+ if (!this.client.options.inventoryKey.matchesKey(keyCode, scanCode) && ItemProtection.itemProtection.matchesKey(keyCode, scanCode)) {
+ ItemProtection.handleKeyPressed(this.focusedSlot.getStack());
+ }
+ }
+ }
+
+ @Inject(at = @At("HEAD"), method = "mouseClicked")
+ public void skyblocker$mouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) {
+ if (SkyblockerConfigManager.get().farming.garden.visitorHelper && (Utils.getLocationRaw().equals("garden") && !getTitle().getString().contains("Logbook") || getTitle().getString().startsWith("Bazaar"))) {
+ VisitorHelper.onMouseClicked(mouseX, mouseY, button, this.textRenderer);
+ }
+ }
+
+ /**
+ * Draws the unselected tabs in front of the background blur, but behind the main inventory, similar to creative inventory tabs
+ */
+ @Inject(method = "renderBackground", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawBackground(Lnet/minecraft/client/gui/DrawContext;FII)V"))
+ private void skyblocker$drawUnselectedQuickNavButtons(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
+ if (quickNavButtons != null) for (QuickNavButton quickNavButton : quickNavButtons) {
+ if (!quickNavButton.toggled()) {
+ quickNavButton.render(context, mouseX, mouseY, delta);
+ }
+ }
+ }
+
+ /**
+ * Draws the selected tab in front of the background blur and the main inventory, similar to creative inventory tabs
+ */
+ @Inject(method = "renderBackground", at = @At("RETURN"))
+ private void skyblocker$drawSelectedQuickNavButtons(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
+ if (quickNavButtons != null) for (QuickNavButton quickNavButton : quickNavButtons) {
+ if (quickNavButton.toggled()) {
+ quickNavButton.render(context, mouseX, mouseY, delta);
+ }
+ }
+ }
+
+ @SuppressWarnings("DataFlowIssue")
+ // makes intellij be quiet about this.focusedSlot maybe being null. It's already null checked in mixined method.
+ @Inject(method = "drawMouseoverTooltip", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTooltip(Lnet/minecraft/client/font/TextRenderer;Ljava/util/List;Ljava/util/Optional;II)V"), cancellable = true)
+ public void skyblocker$drawMouseOverTooltip(DrawContext context, int x, int y, CallbackInfo ci, @Local(ordinal = 0) ItemStack stack) {
+ if (!Utils.isOnSkyblock()) return;
+
+ // Hide Empty Tooltips
+ if (SkyblockerConfigManager.get().uiAndVisuals.hideEmptyTooltips && stack.getName().getString().equals(" ")) {
+ ci.cancel();
+ }
+
+ // Backpack Preview
+ boolean shiftDown = SkyblockerConfigManager.get().uiAndVisuals.backpackPreviewWithoutShift ^ Screen.hasShiftDown();
+ if (shiftDown && getTitle().getString().equals("Storage") && focusedSlot.inventory != client.player.getInventory() && BackpackPreview.renderPreview(context, this, focusedSlot.getIndex(), x, y)) {
+ ci.cancel();
+ }
+
+ // Compactor Preview
+ if (SkyblockerConfigManager.get().uiAndVisuals.compactorDeletorPreview) {
+ Matcher matcher = CompactorDeletorPreview.NAME.matcher(ItemUtils.getItemId(stack));
+ if (matcher.matches() && CompactorDeletorPreview.drawPreview(context, stack, getTooltipFromItem(stack), matcher.group("type"), matcher.group("size"), x, y)) {
+ ci.cancel();
+ }
+ }
+ }
+
+ @ModifyVariable(method = "drawMouseoverTooltip", at = @At(value = "LOAD", ordinal = 0))
+ private ItemStack skyblocker$experimentSolvers$replaceTooltipDisplayStack(ItemStack stack) {
+ return skyblocker$experimentSolvers$getStack(focusedSlot, stack);
+ }
+
+ @SuppressWarnings("deprecation")
+ @ModifyReturnValue(method = "getTooltipFromItem", at = @At("RETURN"))
+ private List<Text> skyblocker$tooltips$addToTooltip(List<Text> tooltip) {
+ return TooltipManager.addToTooltip(tooltip, focusedSlot);
+ }
+
+ @ModifyVariable(method = "drawSlot", at = @At(value = "LOAD", ordinal = 3), ordinal = 0)
+ private ItemStack skyblocker$experimentSolvers$replaceDisplayStack(ItemStack stack, DrawContext context, Slot slot) {
+ return skyblocker$experimentSolvers$getStack(slot, stack);
+ }
+
+ /**
+ * Redirects getStack calls to account for different stacks in experiment solvers.
+ */
+ @Unique
+ private ItemStack skyblocker$experimentSolvers$getStack(Slot slot, @NotNull ItemStack stack) {
+ ContainerSolver currentSolver = SkyblockerMod.getInstance().containerSolverManager.getCurrentSolver();
+ if ((currentSolver instanceof SuperpairsSolver || currentSolver instanceof UltrasequencerSolver) && ((ExperimentSolver) currentSolver).getState() == ExperimentSolver.State.SHOW && slot.inventory instanceof SimpleInventory) {
+ ItemStack itemStack = ((ExperimentSolver) currentSolver).getSlots().get(slot.getIndex());
+ return itemStack == null ? stack : itemStack;
+ }
+ return stack;
+ }
+
+ /**
+ * The naming of this method in yarn is half true, its mostly to handle slot/item interactions (which are mouse or keyboard clicks)
+ * For example, using the drop key bind while hovering over an item will invoke this method to drop the players item
+ *
+ * @implNote This runs before {@link ScreenHandler#onSlotClick(int, int, SlotActionType, net.minecraft.entity.player.PlayerEntity)}
+ */
+ @Inject(method = "onMouseClick(Lnet/minecraft/screen/slot/Slot;IILnet/minecraft/screen/slot/SlotActionType;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;clickSlot(IIILnet/minecraft/screen/slot/SlotActionType;Lnet/minecraft/entity/player/PlayerEntity;)V"), cancellable = true)
+ private void skyblocker$onSlotClick(Slot slot, int slotId, int button, SlotActionType actionType, CallbackInfo ci) {
+ if (!Utils.isOnSkyblock()) return;
+
+ // Item Protection
+ // When you try and drop the item by picking it up then clicking outside the screen
+ if (slotId == OUT_OF_BOUNDS_SLOT && ItemProtection.isItemProtected(this.handler.getCursorStack())) {
+ ci.cancel();
+ return;
+ }
+
+ if (slot == null) return;
+ String title = getTitle().getString();
+ ItemStack stack = skyblocker$experimentSolvers$getStack(slot, slot.getStack());
+ ContainerSolver currentSolver = SkyblockerMod.getInstance().containerSolverManager.getCurrentSolver();
+
+ // Prevent clicks on filler items
+ if (SkyblockerConfigManager.get().uiAndVisuals.hideEmptyTooltips && FILLER_ITEMS.contains(stack.getName().getString()) &&
+ // Allow clicks in Ultrasequencer and Superpairs
+ (!UltrasequencerSolver.INSTANCE.getName().matcher(title).matches() || SkyblockerConfigManager.get().helpers.experiments.enableUltrasequencerSolver)) {
+ ci.cancel();
+ return;
+ }
+ // Item Protection
+ // When you click your drop key while hovering over an item
+ if (actionType == SlotActionType.THROW && ItemProtection.isItemProtected(stack)) {
+ ci.cancel();
+ return;
+ }
+ // Prevent salvaging
+ if (title.equals("Salvage Items") && ItemProtection.isItemProtected(stack)) {
+ ci.cancel();
+ return;
+ }
+ if (this.handler instanceof GenericContainerScreenHandler genericContainerScreenHandler && genericContainerScreenHandler.getRows() == 6) {
+ VisitorHelper.onSlotClick(slot, slotId, title, genericContainerScreenHandler.getSlot(13).getStack());
+
+ // Prevent selling to NPC shops
+ ItemStack sellStack = this.handler.slots.get(49).getStack();
+ if (sellStack.getName().getString().equals("Sell Item") || ItemUtils.getLoreLineIf(sellStack, text -> text.contains("buyback")) != null) {
+ if (slotId != 49 && ItemProtection.isItemProtected(stack)) {
+ ci.cancel();
+ return;
+ }
+ }
+ }
+
+ if (currentSolver != null) {
+ boolean disallowed = SkyblockerMod.getInstance().containerSolverManager.onSlotClick(slotId, stack);
+
+ if (disallowed) ci.cancel();
+ }
+
+ // Experiment Solvers
+ if (currentSolver instanceof ExperimentSolver experimentSolver && experimentSolver.getState() == ExperimentSolver.State.SHOW && slot.inventory instanceof SimpleInventory) {
+ switch (experimentSolver) {
+ case ChronomatronSolver chronomatronSolver -> {
+ Item item = chronomatronSolver.getChronomatronSlots().get(chronomatronSolver.getChronomatronCurrentOrdinal());
+ if ((stack.isOf(item) || ChronomatronSolver.TERRACOTTA_TO_GLASS.get(stack.getItem()) == item) && chronomatronSolver.incrementChronomatronCurrentOrdinal() >= chronomatronSolver.getChronomatronSlots().size()) {
+ chronomatronSolver.setState(ExperimentSolver.State.END);
+ }
+ }
+
+ case SuperpairsSolver superpairsSolver -> {
+ superpairsSolver.setSuperpairsPrevClickedSlot(slot.getIndex());
+ superpairsSolver.setSuperpairsCurrentSlot(ItemStack.EMPTY);
+ }
+
+ case UltrasequencerSolver ultrasequencerSolver when slot.getIndex() == ultrasequencerSolver.getUltrasequencerNextSlot() -> {
+ int count = ultrasequencerSolver.getSlots().get(ultrasequencerSolver.getUltrasequencerNextSlot()).getCount() + 1;
+ ultrasequencerSolver.getSlots().entrySet().stream().filter(entry -> entry.getValue().getCount() == count).findAny().map(Map.Entry::getKey).ifPresentOrElse(ultrasequencerSolver::setUltrasequencerNextSlot, () -> ultrasequencerSolver.setState(ExperimentSolver.State.END));
+ }
+
+ default -> { /*Do Nothing*/ }
+ }
+ }
+ }
+
+ @Inject(method = "drawSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/item/ItemStack;III)V"))
+ private void skyblocker$drawItemRarityBackground(DrawContext context, Slot slot, CallbackInfo ci) {
+ if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().general.itemInfoDisplay.itemRarityBackgrounds)
+ ItemRarityBackgrounds.tryDraw(slot.getStack(), context, slot.x, slot.y);
+ // Item protection
+ if (ItemProtection.isItemProtected(slot.getStack())) {
+ RenderSystem.enableBlend();
+ context.drawTexture(ITEM_PROTECTION, slot.x, slot.y, 0, 0, 16, 16, 16, 16);
+ RenderSystem.disableBlend();
+ }
+ }
+
+ @Inject(method = "drawSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItemInSlot(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V"))
+ private void skyblocker$drawSlotText(DrawContext context, Slot slot, CallbackInfo ci) {
+ List<SlotText> textList = SlotTextManager.getText(slot);
+ if (textList.isEmpty()) return;
+ MatrixStack matrices = context.getMatrices();
+
+ for (SlotText slotText : textList) {
+ matrices.push();
+ matrices.translate(0.0f, 0.0f, 200.0f);
+ int length = textRenderer.getWidth(slotText.text());
+ if (length > 16) {
+ matrices.scale(16.0f / length, 16.0f / length, 1.0f); //Make them fit in the slot. FYI, a slot is sized 16×16.
+ final float x = (slot.x * length / 16.0f) - slot.x; //Save in a variable to not recalculate
+ switch (slotText.position()) {
+ case TOP_LEFT, TOP_RIGHT -> matrices.translate(x, (slot.y * length / 16.0f) - slot.y, 0.0f);
+ case BOTTOM_LEFT, BOTTOM_RIGHT -> matrices.translate(x, ((slot.y + 16f - textRenderer.fontHeight + 2f + 0.7f) * length / 16.0f) - slot.y, 0.0f);
+ }
+ } else {
+ switch (slotText.position()) {
+ case TOP_LEFT -> { /*Do Nothing*/ }
+ case TOP_RIGHT -> matrices.translate(16f - length, 0.0f, 0.0f);
+ case BOTTOM_LEFT -> matrices.translate(0.0f, 16f - textRenderer.fontHeight + 2f, 0.0f);
+ case BOTTOM_RIGHT -> matrices.translate(16f - length, 16f - textRenderer.fontHeight + 2f, 0.0f);
+ }
+ }
+ context.drawText(textRenderer, slotText.text(), slot.x, slot.y, 0xFFFFFF, true);
+ matrices.pop();
+ }
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java
index 878a93ac..b57ee32a 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java
@@ -1,14 +1,21 @@
package de.hysky.skyblocker.mixins;
+import com.google.gson.JsonObject;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
+import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.injected.SkyblockerStack;
+import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
import it.unimi.dsi.fastutil.ints.IntIntPair;
import net.minecraft.component.type.ItemEnchantmentsComponent;
import net.minecraft.item.ItemStack;
import net.minecraft.item.TooltipAppender;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.nbt.NbtElement;
import net.minecraft.text.Text;
+import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@@ -17,8 +24,11 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import java.util.Locale;
+import java.util.Optional;
+
@Mixin(ItemStack.class)
-public abstract class ItemStackMixin {
+public abstract class ItemStackMixin implements SkyblockerStack {
@Shadow
public abstract int getDamage();
@@ -29,6 +39,15 @@ public abstract class ItemStackMixin {
@Unique
private int maxDamage;
+ @Unique
+ private String skyblockId;
+
+ @Unique
+ private String skyblockApiId;
+
+ @Unique
+ private String neuName;
+
@ModifyReturnValue(method = "getName", at = @At("RETURN"))
private Text skyblocker$customItemNames(Text original) {
if (Utils.isOnSkyblock()) {
@@ -103,4 +122,93 @@ public abstract class ItemStackMixin {
setDamage(durability.rightInt() - durability.leftInt());
return true;
}
+
+ @Override
+ @Nullable
+ public String getSkyblockId() {
+ if (skyblockId != null && !skyblockId.isEmpty()) return skyblockId;
+ return skyblockId = skyblocker$getSkyblockId(true);
+ }
+
+ @Override
+ @Nullable
+ public String getSkyblockApiId() {
+ if (skyblockApiId != null && !skyblockApiId.isEmpty()) return skyblockApiId;
+ return skyblockApiId = skyblocker$getSkyblockId(false);
+ }
+
+ @Override
+ @Nullable
+ public String getNeuName() {
+ if (neuName != null && !neuName.isEmpty()) return neuName;
+ String apiId = getSkyblockApiId();
+ String id = getSkyblockId();
+ if (apiId == null || id == null) return null;
+
+ if (apiId.startsWith("ISSHINY_")) apiId = id;
+
+ return neuName = ItemTooltip.getNeuName(id, apiId);
+ }
+
+ @Unique
+ private String skyblocker$getSkyblockId(boolean internalIDOnly) {
+ NbtCompound customData = ItemUtils.getCustomData((ItemStack) (Object) this);
+
+ if (customData == null || !customData.contains(ItemUtils.ID, NbtElement.STRING_TYPE)) {
+ return null;
+ }
+ String customDataString = customData.getString(ItemUtils.ID);
+
+ if (internalIDOnly) {
+ return customDataString;
+ }
+
+ // Transformation to API format.
+ if (customData.contains("is_shiny")) {
+ return "ISSHINY_" + customDataString;
+ }
+
+ switch (customDataString) {
+ 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") + "_" + customDataString + "_" + 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 customDataString + "-" + shard.toUpperCase(Locale.ENGLISH) + "_" + shards.getInt(shard);
+ }
+ }
+ }
+ return customDataString;
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/mixins/accessors/HandledScreenAccessor.java b/src/main/java/de/hysky/skyblocker/mixins/accessors/HandledScreenAccessor.java
index 9a2a8311..41323a5e 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/accessors/HandledScreenAccessor.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/accessors/HandledScreenAccessor.java
@@ -1,11 +1,16 @@
package de.hysky.skyblocker.mixins.accessors;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
+import net.minecraft.item.ItemStack;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
+import org.spongepowered.asm.mixin.gen.Invoker;
+
+import java.util.List;
@Mixin(HandledScreen.class)
public interface HandledScreenAccessor {
@@ -27,4 +32,7 @@ public interface HandledScreenAccessor {
@Accessor("focusedSlot")
Slot getFocusedSlot();
+
+ @Invoker
+ List<Text> invokeGetTooltipFromItem(ItemStack stack);
}
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) {}
}
diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java
index 8a6127c7..b1708404 100644
--- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java
@@ -10,6 +10,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.codecs.RecordCodecBuilder;
+import de.hysky.skyblocker.skyblock.item.tooltip.adders.ObtainedDateTooltip;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType;
import it.unimi.dsi.fastutil.doubles.DoubleBooleanPair;
@@ -25,7 +26,6 @@ import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
-import net.minecraft.nbt.NbtElement;
import net.minecraft.registry.Registries;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.text.Text;
@@ -34,13 +34,7 @@ import net.minecraft.util.dynamic.Codecs;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
-import java.time.temporal.TemporalAccessor;
-import java.util.Iterator;
import java.util.List;
-import java.util.Locale;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.regex.Matcher;
@@ -51,8 +45,6 @@ import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.lit
public class ItemUtils {
public static final String ID = "id";
public static final String UUID = "uuid";
- 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 static final Pattern NOT_DURABILITY = Pattern.compile("[^0-9 /]");
public static final Predicate<String> FUEL_PREDICATE = line -> line.contains("Fuel: ");
private static final Codec<RegistryEntry<Item>> EMPTY_ALLOWING_ITEM_CODEC = Registries.ITEM.getEntryCodec();
@@ -166,21 +158,12 @@ public class ItemUtils {
*
* @param stack the item under the pointer
* @return if the item have a "Timestamp" it will be shown formated on the tooltip
+ * @deprecated use {@link ObtainedDateTooltip#getTimestamp(ItemStack)} instead
*/
public static String getTimestamp(ItemStack stack) {
NbtCompound customData = 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 "";
+ return ObtainedDateTooltip.getTimestamp(stack);
}
public static boolean hasCustomDurability(@NotNull ItemStack stack) {
diff --git a/src/main/java/de/hysky/skyblocker/utils/RomanNumerals.java b/src/main/java/de/hysky/skyblocker/utils/RomanNumerals.java
new file mode 100644
index 00000000..007cb0b1
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/RomanNumerals.java
@@ -0,0 +1,54 @@
+package de.hysky.skyblocker.utils;
+
+public class RomanNumerals {
+ private RomanNumerals() {
+ }
+ private static int getDecimalValue(char romanChar) {
+ return switch (romanChar) {
+ case 'I' -> 1;
+ case 'V' -> 5;
+ case 'X' -> 10;
+ case 'L' -> 50;
+ case 'C' -> 100;
+ case 'D' -> 500;
+ case 'M' -> 1000;
+ default -> 0;
+ };
+ }
+
+ /**
+ * Checks if a string is a valid roman numeral.
+ * It's the caller's responsibility to clean up the string before calling this method (such as trimming it).
+ * @param romanNumeral The roman numeral to check.
+ * @return True if the string is a valid roman numeral, false otherwise.
+ * @implNote This will only check if the string contains valid roman numeral characters. It won't check if the numeral is well-formed.
+ */
+ public static boolean isValidRomanNumeral(String romanNumeral) {
+ if (romanNumeral == null || romanNumeral.isEmpty()) return false;
+ for (int i = 0; i < romanNumeral.length(); i++) {
+ if (getDecimalValue(romanNumeral.charAt(i)) == 0) return false;
+ }
+ return true;
+ }
+
+ /**
+ * Converts a roman numeral to a decimal number.
+ *
+ * @param romanNumeral The roman numeral to convert.
+ * @return The decimal number, or 0 if the roman numeral string has non-roman characters in it, or if the string is empty or null.
+ */
+ public static int romanToDecimal(String romanNumeral) {
+ if (romanNumeral == null || romanNumeral.isEmpty()) return 0;
+ romanNumeral = romanNumeral.trim().toUpperCase();
+ int decimal = 0;
+ int lastNumber = 0;
+ for (int i = romanNumeral.length() - 1; i >= 0; i--) {
+ char ch = romanNumeral.charAt(i);
+ int number = getDecimalValue(ch);
+ if (number == 0) return 0; //Malformed roman numeral
+ decimal = number >= lastNumber ? decimal + number : decimal - number;
+ lastNumber = number;
+ }
+ return decimal;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java
index 7c28294f..925879b8 100644
--- a/src/main/java/de/hysky/skyblocker/utils/Utils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java
@@ -5,11 +5,9 @@ import com.google.gson.JsonParser;
import de.hysky.skyblocker.events.SkyblockEvents;
import de.hysky.skyblocker.mixins.accessors.MessageHandlerAccessor;
import de.hysky.skyblocker.skyblock.item.MuseumItemCache;
-import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
import de.hysky.skyblocker.utils.scheduler.MessageScheduler;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
-import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback;
import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
@@ -235,7 +233,6 @@ public class Utils {
if (!isOnSkyblock) {
if (!isInjected) {
isInjected = true;
- ItemTooltipCallback.EVENT.register(ItemTooltip::getTooltip);
}
isOnSkyblock = true;
SkyblockEvents.JOIN.invoker().onSkyblockJoin();