From 3cecf0281ac6dee03058c7dfdc1110c94aef99f3 Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Sun, 24 Nov 2024 19:28:06 +0100 Subject: [PATCH 01/17] Museum HUD --- .../categories/UIAndVisualsCategory.java | 10 +- .../config/configs/UIAndVisualsConfig.java | 3 + .../skyblocker/mixins/HandledScreenMixin.java | 30 +- .../mixins/accessors/ScreenAccessor.java | 7 + .../skyblock/item/MuseumItemCache.java | 154 +++++++-- .../skyblocker/skyblock/item/WikiLookup.java | 6 +- .../tooltip/adders/CraftPriceTooltip.java | 2 +- .../skyblock/museum/ArmorPiece.java | 40 +++ .../skyblocker/skyblock/museum/Donation.java | 105 ++++++ .../skyblock/museum/DonationButton.java | 159 +++++++++ .../skyblock/museum/FormatingUtils.java | 70 ++++ .../skyblock/museum/ItemFilter.java | 110 +++++++ .../skyblock/museum/ItemSorter.java | 147 +++++++++ .../skyblock/museum/MuseumManager.java | 301 ++++++++++++++++++ .../de/hysky/skyblocker/utils/ItemUtils.java | 15 +- .../assets/skyblocker/lang/en_us.json | 3 + 16 files changed, 1128 insertions(+), 34 deletions(-) create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/museum/ArmorPiece.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/museum/FormatingUtils.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java diff --git a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java index fa9bf37648..6a769781e2 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java @@ -76,8 +76,16 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig newValue -> config.uiAndVisuals.showEquipmentInInventory = newValue) .controller(ConfigUtils::createBooleanController) .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.uiAndVisuals.museumOverlay")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.museumOverlay.@Tooltip"))) + .binding(defaults.uiAndVisuals.museumOverlay, + () -> config.uiAndVisuals.museumOverlay, + newValue -> config.uiAndVisuals.museumOverlay = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) - //Chest Value FIXME change dropdown to color controller + //Chest Value FIXME change dropdown to color controller .group(OptionGroup.createBuilder() .name(Text.translatable("skyblocker.config.uiAndVisuals.chestValue")) .collapsed(true) diff --git a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java index 598cbf5498..04ea958f2e 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java @@ -31,6 +31,9 @@ public class UIAndVisualsConfig { @SerialEntry public boolean showEquipmentInInventory = true; + @SerialEntry + public boolean museumOverlay = true; + @SerialEntry public ChestValue chestValue = new ChestValue(); diff --git a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java index 579c8773d0..271e748f72 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java @@ -15,6 +15,7 @@ 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.museum.MuseumManager; import de.hysky.skyblocker.skyblock.quicknav.QuickNav; import de.hysky.skyblocker.skyblock.quicknav.QuickNavButton; import de.hysky.skyblocker.utils.ItemUtils; @@ -97,6 +98,18 @@ public abstract class HandledScreenMixin extends Screen @Shadow protected abstract List getTooltipFromItem(ItemStack stack); + @Shadow + public abstract T getScreenHandler(); + + @Shadow + protected int backgroundWidth; + + @Shadow + protected int x; + + @Shadow + protected int y; + @Unique private List quickNavButtons; @@ -113,13 +126,28 @@ protected HandledScreenMixin(Text title) { } } + @Inject(method = "init", at = @At("TAIL")) + private void skyblocker$initMuseumOverlay(CallbackInfo ci) { + if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().uiAndVisuals.museumOverlay && client != null && client.player != null && !client.player.isCreative() && getTitle().getString().contains("Museum")) { + new MuseumManager(this, this.x, this.y, this.backgroundWidth); + } + } + + @Inject(method = "close", at = @At("HEAD")) + private void skyblocker$removeMuseumOverlay(CallbackInfo ci) { + if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().uiAndVisuals.museumOverlay && client != null && client.player != null && !client.player.isCreative() && getTitle().getString().contains("Museum")) { + // Reset Overlay variables when no longer in Museum inventory + MuseumManager.reset(); + } + } + @Inject(at = @At("HEAD"), method = "keyPressed") public void skyblocker$keyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable cir) { if (this.client != null && this.client.player != null && this.focusedSlot != null && keyCode != 256 && !this.client.options.inventoryKey.matchesKey(keyCode, scanCode) && Utils.isOnSkyblock()) { SkyblockerConfig config = SkyblockerConfigManager.get(); //wiki lookup if (config.general.wikiLookup.enableWikiLookup && WikiLookup.wikiLookup.matchesKey(keyCode, scanCode)) { - WikiLookup.openWiki(this.focusedSlot, client.player); + WikiLookup.openWiki(this.focusedSlot.getStack(), client.player); } //item protection if (ItemProtection.itemProtection.matchesKey(keyCode, scanCode)) { diff --git a/src/main/java/de/hysky/skyblocker/mixins/accessors/ScreenAccessor.java b/src/main/java/de/hysky/skyblocker/mixins/accessors/ScreenAccessor.java index c354430276..e73cef1019 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/accessors/ScreenAccessor.java +++ b/src/main/java/de/hysky/skyblocker/mixins/accessors/ScreenAccessor.java @@ -1,14 +1,21 @@ package de.hysky.skyblocker.mixins.accessors; +import net.minecraft.client.gui.Drawable; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; import net.minecraft.client.gui.screen.Screen; 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; @Mixin(Screen.class) public interface ScreenAccessor { @Accessor @Mutable void setTitle(Text title); + + @Invoker("addDrawableChild") + T callAddDrawableChild(T drawableElement); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java b/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java index 7adda9577d..897cc7c906 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java @@ -1,5 +1,6 @@ package de.hysky.skyblocker.skyblock.item; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -10,11 +11,12 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.annotations.Init; -import de.hysky.skyblocker.utils.Constants; -import de.hysky.skyblocker.utils.Http; +import de.hysky.skyblocker.skyblock.museum.ArmorPiece; +import de.hysky.skyblocker.skyblock.museum.Donation; +import de.hysky.skyblocker.skyblock.museum.FormatingUtils; +import de.hysky.skyblocker.utils.*; import de.hysky.skyblocker.utils.Http.ApiResponse; -import de.hysky.skyblocker.utils.ItemUtils; -import de.hysky.skyblocker.utils.Utils; +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -24,7 +26,6 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.command.CommandRegistryAccess; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.*; import net.minecraft.screen.slot.Slot; import net.minecraft.text.Text; import net.minecraft.util.collection.DefaultedList; @@ -33,12 +34,13 @@ import java.io.BufferedReader; import java.io.BufferedWriter; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import java.util.Base64; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; @@ -48,12 +50,15 @@ public class MuseumItemCache { private static final Logger LOGGER = LoggerFactory.getLogger(MuseumItemCache.class); private static final Path CACHE_FILE = SkyblockerMod.CONFIG_DIR.resolve("museum_item_cache.json"); + public static final Map ARMOR_NAMES = new Object2ObjectArrayMap<>(); private static final Map> MUSEUM_ITEM_CACHE = new Object2ObjectOpenHashMap<>(); private static final String ERROR_LOG_TEMPLATE = "[Skyblocker] Failed to refresh museum item data for profile {}"; public static final String DONATION_CONFIRMATION_SCREEN_TITLE = "Confirm Donation"; private static final int CONFIRM_DONATION_BUTTON_SLOT = 20; - + public static final Map MAPPED_IDS = new Object2ObjectArrayMap<>(); private static CompletableFuture loaded; + private static final Path MUSEUM_INFO = NEURepoManager.NEU_REPO.file("constants/museum.json").getFsPath(); + private static final List MUSEUM_DONATIONS = new ArrayList<>(); @Init public static void init() { @@ -83,6 +88,7 @@ private static void load(MinecraftClient client) { MUSEUM_ITEM_CACHE.putAll(cachedData); LOGGER.info("[Skyblocker] Loaded museum items cache"); + loadMuseumItems(); } catch (NoSuchFileException ignored) { } catch (IOException e) { LOGGER.error("[Skyblocker] Failed to load cached museum items", e); @@ -100,6 +106,115 @@ private static void save() { }); } + /** + * Loads museum data from a JSON file, processes it, and updates internal data structures. + */ + public static void loadMuseumItems() { + MUSEUM_DONATIONS.clear(); + + try (BufferedReader reader = Files.newBufferedReader(MUSEUM_INFO)) { + // Parse the JSON file + JsonObject json = JsonParser.parseReader(reader).getAsJsonObject(); + + Map setExceptions = json.get("set_exceptions").getAsJsonObject().asMap(); + Map mappedIds = json.get("mapped_ids").getAsJsonObject().asMap(); + Map itemToXp = json.get("itemToXp").getAsJsonObject().asMap(); + Map setsToItems = json.get("sets_to_items").getAsJsonObject().asMap(); + Map children = json.get("children").getAsJsonObject().asMap(); + + Map allDonations = Map.of( + "weapons", json.get("weapons").getAsJsonArray(), + "armor", json.get("armor").getAsJsonArray(), + "rarities", json.get("rarities").getAsJsonArray() + ); + + mappedIds.forEach((s, jsonElement) -> MAPPED_IDS.put(s, jsonElement.getAsString())); + + for (Map.Entry entry : allDonations.entrySet()) { + String category = entry.getKey(); + JsonArray array = entry.getValue(); + + for (JsonElement element : array) { + String itemID = element.getAsString(); + List set = new ArrayList<>(List.of()); + if (category.equals("armor")) { + boolean isEquipment = true; + for (JsonElement jsonElement : setsToItems.get(itemID).getAsJsonArray()) { + if (isEquipment) isEquipment = ItemUtils.isEquipment(jsonElement.getAsString()); + set.add(new ArmorPiece(jsonElement.getAsString())); + } + String realId = itemID; + for (Map.Entry exception : setExceptions.entrySet()) { + if (exception.getValue().getAsString().equals(itemID)) { + realId = exception.getKey(); + break; + } + } + ARMOR_NAMES.put(itemID, FormatingUtils.formatArmorName(realId, isEquipment)); + } + int itemXP = itemToXp.get(itemID).getAsInt(); + List upgrades = getUpgrades(children, itemID); + MUSEUM_DONATIONS.add(new Donation(category, itemID, set, itemXP, upgrades)); + } + } + + LOGGER.info("[Skyblocker] Loaded museum data"); + } catch (Exception e) { + LOGGER.error("[Skyblocker] Failed to load donations data", e); + } + } + + /** + * Gets a list of all upgrades (parent items) for a given item. + */ + public static List getUpgrades(Map children, String item) { + List upgrades = new ArrayList<>(); + String currentItem = item; + + while (true) { + // Find the parent item + String finalCurrentItem = currentItem; + String parentItem = children.entrySet().stream() + .filter(e -> e.getValue().getAsString().equals(finalCurrentItem)) + .map(Map.Entry::getKey) + .findFirst() + .orElse(null); + + if (parentItem == null) { + break; // No more parents found, exit the loop + } + + upgrades.add(parentItem); // Add the parent to the upgrades list + currentItem = parentItem; // Move to the parent and repeat + } + + return upgrades; + } + + /** + * Retrieves a list of donations that the player has not yet contributed. + */ + public static List getDonations() { + List uncontributedItems = new ArrayList<>(); + + String uuid = Utils.getUndashedUuid(); + String profileId = Utils.getProfileId(); + + if (MUSEUM_ITEM_CACHE.containsKey(uuid) && MUSEUM_ITEM_CACHE.get(uuid).containsKey(profileId)) { + ObjectOpenHashSet items = MUSEUM_ITEM_CACHE.get(uuid).get(profileId).collectedItemIds(); + for (Donation donation : MUSEUM_DONATIONS) { + // Check if the donation id is not present in the collected items + if (!items.contains(donation.getId()) && items.stream().noneMatch(donation.getUpgrades()::contains)) { + donation.initPriceData(); + uncontributedItems.add(donation); + } + } + } + + uncontributedItems.sort(Comparator.comparing(Donation::getId)); + return uncontributedItems; + } + public static void handleClick(Slot slot, int slotId, DefaultedList slots) { if (slotId == CONFIRM_DONATION_BUTTON_SLOT) { //Slots 0 to 17 can have items, well not all but thats the general range @@ -111,16 +226,17 @@ public static void handleClick(Slot slot, int slotId, DefaultedList slots) String profileId = Utils.getProfileId(); if (!itemId.isEmpty() && !profileId.isEmpty()) { + if (MAPPED_IDS.containsKey(itemId)) itemId = MAPPED_IDS.get(itemId); String uuid = Utils.getUndashedUuid(); //Be safe about access to avoid NPEs - Map playerData = MUSEUM_ITEM_CACHE.computeIfAbsent(uuid, _uuid -> new Object2ObjectOpenHashMap<>()); + Map playerData = MUSEUM_ITEM_CACHE.computeIfAbsent(uuid, _uuid -> new Object2ObjectOpenHashMap<>()); playerData.putIfAbsent(profileId, ProfileMuseumData.EMPTY.get()); playerData.get(profileId).collectedItemIds().add(itemId); - save(); } } } + save(); } } @@ -138,28 +254,12 @@ private static void updateData4ProfileMember(String uuid, String profileId, Fabr if (members.has(uuid)) { JsonObject memberData = members.get(uuid).getAsJsonObject(); - //We call them sets because it could either be a singular item or an entire armour set Map donatedSets = memberData.get("items").getAsJsonObject().asMap(); - //Set of all found item ids on profile ObjectOpenHashSet itemIds = new ObjectOpenHashSet<>(); - for (Map.Entry donatedSet : donatedSets.entrySet()) { - //Item is plural here because the nbt is a list - String itemsData = donatedSet.getValue().getAsJsonObject().get("items").getAsJsonObject().get("data").getAsString(); - NbtList items = NbtIo.readCompressed(new ByteArrayInputStream(Base64.getDecoder().decode(itemsData)), NbtSizeTracker.ofUnlimitedBytes()).getList("i", NbtElement.COMPOUND_TYPE); - - for (int i = 0; i < items.size(); i++) { - NbtCompound tag = items.getCompound(i).getCompound("tag"); - - if (tag.contains("ExtraAttributes")) { - NbtCompound extraAttributes = tag.getCompound("ExtraAttributes"); - - if (extraAttributes.contains("id")) itemIds.add(extraAttributes.getString("id")); - } - } - } + donatedSets.forEach((s, jsonElement) -> itemIds.add(s)); MUSEUM_ITEM_CACHE.get(uuid).put(profileId, new ProfileMuseumData(System.currentTimeMillis(), itemIds)); save(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java b/src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java index 0e1c9d0e74..3030abd1fb 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java @@ -8,7 +8,7 @@ import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.screen.slot.Slot; +import net.minecraft.item.ItemStack; import net.minecraft.text.Text; import net.minecraft.util.Util; import org.jetbrains.annotations.NotNull; @@ -32,8 +32,8 @@ public static void init() { )); } - public static void openWiki(@NotNull Slot slot, @NotNull PlayerEntity player) { - ItemUtils.getItemIdOptional(slot.getStack()) + public static void openWiki(@NotNull ItemStack itemStack, @NotNull PlayerEntity player) { + ItemUtils.getItemIdOptional(itemStack) .map(ItemRepository::getWikiLink) .ifPresentOrElse(wikiLink -> CompletableFuture.runAsync(() -> Util.getOperatingSystem().open(wikiLink)).exceptionally(e -> { LOGGER.error("[Skyblocker] Error while retrieving wiki article...", e); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java index 943e855dca..c5e644eea8 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java @@ -67,7 +67,7 @@ public void addToTooltip(@Nullable Slot focusedSloFt, ItemStack stack, List= MAX_RECURSION_DEPTH) return -1; double totalCraftCost = 0; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/ArmorPiece.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/ArmorPiece.java new file mode 100644 index 0000000000..2783400eb5 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/ArmorPiece.java @@ -0,0 +1,40 @@ +package de.hysky.skyblocker.skyblock.museum; + +public class ArmorPiece { + private final String id; + private double craftCost; + private double price; + private double effectivePrice; + + public ArmorPiece(String id) { + this.id = id; + } + + public double getEffectivePrice() { + return effectivePrice; + } + + public void setEffectivePrice(double effectivePrice) { + this.effectivePrice = effectivePrice; + } + + public String getId() { + return id; + } + + public double getCraftCost() { + return craftCost; + } + + public void setCraftCost(double craftCost) { + this.craftCost = craftCost; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java new file mode 100644 index 0000000000..a446dd8fc2 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java @@ -0,0 +1,105 @@ +package de.hysky.skyblocker.skyblock.museum; + +import de.hysky.skyblocker.skyblock.item.tooltip.adders.CraftPriceTooltip; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.NEURepoManager; +import io.github.moulberry.repo.data.NEUItem; + +import java.util.List; + +public class Donation { + private final String category; + private final String id; + private final List set; + private final int xp; + private final List upgrades; + private double price; + private double craftCost; + private double effectivePrice; + + public Donation(String category, String id, List set, int xp, List upgrades) { + this.category = category; + this.id = id; + this.set = set; + this.xp = xp; + this.upgrades = upgrades; + } + + public void initPriceData() { + this.price = 0; + this.craftCost = 0; + if (isArmorSet()) { + for (ArmorPiece piece : getSet()) { + double price = ItemUtils.getItemPrice(piece.getId()).leftDouble(); + double craftCost = getCraftCost(piece.getId()); + piece.setPrice(price); + piece.setCraftCost(craftCost); + this.price += price; + this.craftCost += craftCost; + } + } else { + this.price = ItemUtils.getItemPrice(id).leftDouble(); + this.craftCost = getCraftCost(id); + } + } + + public String getCategory() { + return category; + } + + public String getId() { + return id; + } + + public List getSet() { + return set; + } + + public boolean isArmorSet() { + return !set.isEmpty(); + } + + public int getXp() { + return xp; + } + + public List getUpgrades() { + return upgrades; + } + + public double getCraftCost() { + return craftCost; + } + + public double getEffectivePrice() { + return effectivePrice; + } + + public void setEffectivePrice(double effectivePrice) { + this.effectivePrice = effectivePrice; + } + + public double getPrice() { + return price; + } + + public boolean hasPrice() { + return effectivePrice > 0; + } + + public boolean hasLBinPrice() { + return price > 0; + } + + public boolean isCraftable() { + return craftCost > 0; + } + + private double getCraftCost(String id) { + NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(id); + if (neuItem != null && !neuItem.getRecipes().isEmpty()) { + return CraftPriceTooltip.getItemCost(neuItem.getRecipes().getFirst(), 0); + } + return 0; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java new file mode 100644 index 0000000000..4dd56e1b38 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java @@ -0,0 +1,159 @@ +package de.hysky.skyblocker.skyblock.museum; + +import de.hysky.skyblocker.skyblock.item.MuseumItemCache; +import de.hysky.skyblocker.skyblock.item.WikiLookup; +import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; +import de.hysky.skyblocker.utils.ItemUtils; +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.screen.recipebook.AnimatedResultButton; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class DonationButton extends ClickableWidget { + + private static final int SIZE = 33; + private static final int ITEM_OFFSET = 8; + private final Map setEffectivePricesText = new Object2ObjectArrayMap<>(); + List tooltip; + private Donation donation = null; + private ItemStack itemStack = null; + private String effectivePriceText = null; + + protected DonationButton(int x, int y) { + super(x, y, SIZE, SIZE + 2, ScreenTexts.EMPTY); + } + + protected ItemStack getDisplayStack() { + return this.itemStack; + } + + /** + * Initializes the button with a donation object. + * + * @param donation The donation to associate with this button. + */ + public void init(Donation donation) { + this.donation = donation; + this.effectivePriceText = FormatingUtils.formatPrice(donation.getEffectivePrice()); + + // Populate effective prices for armor pieces in the set + if (donation.isArmorSet()) { + donation.getSet().forEach(piece -> setEffectivePricesText.put(piece.getId(), FormatingUtils.formatPrice(piece.getEffectivePrice()))); + } + + // Determine the item stack to display + this.itemStack = !donation.isArmorSet() + ? ItemRepository.getItemStack(donation.getId()) + : ItemRepository.getItemStack( + donation.getSet().stream() + .filter(piece -> piece.getId().toLowerCase().contains("helmet") || piece.getId().toLowerCase().contains("hat")) + .findFirst() + .orElse(donation.getSet().get(1)) // Get chestplate if helmet not found + .getId() + ); + + if (itemStack != null) { + this.visible = true; + createTooltip(); + } + } + + + /** + * Clears the display stack and resets the button state. + */ + protected void clearDisplayStack() { + this.visible = false; // Hides the button + this.itemStack = null; + this.tooltip = null; + this.effectivePriceText = null; + this.setEffectivePricesText.clear(); + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + if (visible) { + MinecraftClient client = MinecraftClient.getInstance(); + + context.drawGuiTexture(RenderLayer::getGuiTextured, AnimatedResultButton.SLOT_CRAFTABLE_TEXTURE, this.getX(), this.getY(), this.width, this.height); + + int yOffset = 8; + + // Draw effective price if available + if (donation.hasPrice()) { + int textWidth = client.textRenderer.getWidth(effectivePriceText); + int centeredX = this.getX() + (this.width / 2) - (textWidth / 2); + int textY = this.getY() + ITEM_OFFSET + 13; + context.drawText(client.textRenderer, effectivePriceText, centeredX, textY, 0xFF00FF00, true); + yOffset -= 4; + } + + context.drawItemWithoutEntity(itemStack, this.getX() + ITEM_OFFSET, this.getY() + yOffset); + } + } + + /** + * Creates the tooltip for the button based on its associated donation data + */ + private void createTooltip() { + List tooltip = new ArrayList<>(); + Style textStyle = Style.EMPTY; + + if (donation.isArmorSet()) { + for (ArmorPiece piece : donation.getSet()) { + ItemStack stack = ItemRepository.getItemStack(piece.getId()); + if (stack != null) { + textStyle = stack.getName().getSiblings().getFirst().getStyle(); + Text itemName = stack.getName().copy(); + if (ItemUtils.getLore(stack).stream().anyMatch(lore -> lore.getString().toLowerCase().contains("soulbound"))) { + tooltip.add(Text.literal(" ").append(itemName)); + } else if (setEffectivePricesText.get(piece.getId()) != null) { + tooltip.add(Text.literal(" ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.literal(setEffectivePricesText.get(piece.getId())).formatted(Formatting.GOLD)).append(Text.literal(")").formatted(Formatting.DARK_GRAY))); + } else { + tooltip.add(Text.literal(" ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.literal("Unknown").formatted(Formatting.RED)).append(Text.literal(")").formatted(Formatting.DARK_GRAY))); + } + } + } + String armorName = MuseumItemCache.ARMOR_NAMES.get(donation.getId()); + tooltip.addFirst(Text.literal(Objects.requireNonNullElseGet(armorName, () -> donation.getId().toLowerCase())).setStyle(textStyle)); + } else { + ItemStack stack = ItemRepository.getItemStack(donation.getId()); + if (stack != null) tooltip.add(stack.getName()); + } + + Text lbinText = donation.hasLBinPrice() ? Text.literal(FormatingUtils.formatPrice(donation.getPrice())).append(" Coins").formatted(Formatting.GOLD) : Text.literal("Unknown").formatted(Formatting.RED); + Text craftCostText = donation.isCraftable() ? Text.literal(FormatingUtils.formatPrice(donation.getCraftCost())).append(" Coins").formatted(Formatting.GOLD) : Text.literal("Unknown").formatted(Formatting.RED); + + String wikiLookupKey = WikiLookup.wikiLookup.getBoundKeyTranslationKey(); + + tooltip.add(Text.literal("")); + tooltip.add(Text.literal("Reward: ").formatted(Formatting.GRAY).append(Text.literal(String.valueOf(donation.getXp())).append(" SkyBlock XP").formatted(Formatting.AQUA))); + tooltip.add(Text.literal("Lowest BIN: ").formatted(Formatting.GRAY).append(lbinText)); + tooltip.add(Text.literal("Craft Cost: ").formatted(Formatting.GRAY).append(craftCostText)); + tooltip.add(Text.literal("")); + tooltip.add(Text.literal("Click on " + wikiLookupKey.substring(wikiLookupKey.lastIndexOf('.') + 1).toUpperCase() + " to open the wiki page!").formatted(Formatting.YELLOW)); + + this.tooltip = tooltip; + } + + protected List getItemTooltip() { + return this.tooltip; + } + + + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) {} +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/FormatingUtils.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/FormatingUtils.java new file mode 100644 index 0000000000..f7183b4dbb --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/FormatingUtils.java @@ -0,0 +1,70 @@ +package de.hysky.skyblocker.skyblock.museum; + +public class FormatingUtils { + + /** + * Formats an armor item ID into a readable name + */ + public static String formatArmorName(String id, boolean isEquipment) { + String lowercaseKey = id.toLowerCase(); + + // Step 2: Replace "_" with space + String withSpaces = lowercaseKey.replace("_", " "); + + // Step 3: Capitalize the first letter of each word + String[] words = withSpaces.split(" "); + StringBuilder formattedName = new StringBuilder(); + for (String word : words) { + // Uppercase first letter, lowercase the rest + formattedName.append(Character.toUpperCase(word.charAt(0))) + .append(word.substring(1)) + .append(" "); + } + + if (isEquipment) { + formattedName.append("Equipment"); + } else if (!lowercaseKey.contains("armor") && + !lowercaseKey.contains("outfit") && + !lowercaseKey.contains("suit") && + !lowercaseKey.contains("tuxedo")) { + formattedName.append("Armor"); + } + + return formattedName.toString().trim(); + } + + /** + * Formats a double value into a shortened human-readable format. + * + * @param value The number to format. + * @return A formatted string (e.g., "10m", "5k", "1.2b"). + */ + public static String formatPrice(double value) { + String suffix = ""; + double divisor = 1; + + if (value >= 1_000_000_000) { + suffix = "b"; + divisor = 1_000_000_000; + } else if (value >= 1_000_000) { + suffix = "m"; + divisor = 1_000_000; + } else if (value >= 1_000) { + suffix = "k"; + divisor = 1_000; + } + + // Round the result first + double result = value / divisor; + // Prevent rounding up + if (result >= 100) { + result = (long) result; // Keep it as an integer + } else { + result = Math.floor(result * 10) / 10.0; // Round down to 1 decimal place + } + + return (result == (long) result) + ? String.format("%d%s", (long) result, suffix) + : String.format("%.1f%s", result, suffix); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java new file mode 100644 index 0000000000..c81786202e --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java @@ -0,0 +1,110 @@ +package de.hysky.skyblocker.skyblock.museum; + +import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.List; +import java.util.stream.Collectors; + +public class ItemFilter { + private FilterMode currentFilterMode = FilterMode.ALL; + + // Filtering logic methods + private static List filterAll(List donations) { + return donations; + } + + private static List filterWeapons(List donations) { + return donations.stream() + .filter(donation -> "weapons".equals(donation.getCategory())) + .collect(Collectors.toList()); + } + + private static List filterArmor(List donations) { + return donations.stream() + .filter(donation -> "armor".equals(donation.getCategory())) + .collect(Collectors.toList()); + } + + private static List filterRarities(List donations) { + return donations.stream() + .filter(donation -> "rarities".equals(donation.getCategory())) + .collect(Collectors.toList()); + } + + // Method to cycle through filtering modes and apply the corresponding logic + public void cycleFilterMode(List items, List filteredList) { + // Cycle to the next filter mode + currentFilterMode = FilterMode.values()[(currentFilterMode.ordinal() + 1) % FilterMode.values().length]; + // Apply the filtering logic for the current mode + currentFilterMode.applyFilter(items, filteredList); + } + + public void applyFilter(List items, List filteredList) { + currentFilterMode.applyFilter(items, filteredList); + } + + // Get the item associated with the current filter mode + public ItemStack getCurrentFilterItem() { + return currentFilterMode.getAssociatedItem(); + } + + public void resetFilter() { + this.currentFilterMode = FilterMode.ALL; + } + + public Tooltip getTooltip() { + Text tooltip = Text.literal("Item Filter\n\n").formatted(Formatting.GREEN) + .append(getFilterText(FilterMode.ALL)) + .append(getFilterText(FilterMode.WEAPONS)) + .append(getFilterText(FilterMode.ARMOR)) + .append(getFilterText(FilterMode.RARITIES)) + .append(Text.literal("\nClick to switch!").formatted(Formatting.YELLOW)); + return Tooltip.of(tooltip); + } + + private Text getFilterText(FilterMode mode) { + boolean isCurrent = mode == currentFilterMode; + return Text.literal((isCurrent ? "➤ " : " ") + mode.getDisplayName() + "\n") + .formatted(isCurrent ? Formatting.AQUA : Formatting.GRAY); + } + + public enum FilterMode { + ALL(new ItemStack(Items.NETHER_STAR), ItemFilter::filterAll, "All"), + WEAPONS(new ItemStack(Items.DIAMOND_SWORD), ItemFilter::filterWeapons, "Weapons"), + ARMOR(new ItemStack(Items.DIAMOND_CHESTPLATE), ItemFilter::filterArmor, "Armor"), + RARITIES(ItemRepository.getItemStack("JADERALD"), ItemFilter::filterRarities, "Rarities"); + + private final ItemStack associatedItem; + private final FilterFunction filterFunction; + private final String displayName; + + FilterMode(ItemStack item, FilterFunction function, String displayName) { + this.associatedItem = item; + this.filterFunction = function; + this.displayName = displayName; + } + + public ItemStack getAssociatedItem() { + return associatedItem; + } + + public String getDisplayName() { + return displayName; + } + + public void applyFilter(List items, List filteredList) { + filteredList.clear(); + filteredList.addAll(filterFunction.filter(items)); + } + } + + @FunctionalInterface + public interface FilterFunction { + List filter(List items); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java new file mode 100644 index 0000000000..8aa7e8fc2b --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java @@ -0,0 +1,147 @@ +package de.hysky.skyblocker.skyblock.museum; + +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.List; + +public class ItemSorter { + private SortMode currentSortMode = SortMode.LowestBIN; + + private static void sortByLowestBIN(List donations) { + donations.forEach(donation -> setEffectivePrices(donation, false)); + donations.sort(ItemSorter::compareEffectivePrices); + } + + private static void sortByCraftCost(List donations) { + donations.forEach(donation -> setEffectivePrices(donation, true)); + donations.sort(ItemSorter::compareEffectivePrices); + } + + private static void sortByXpPerCoin(List donations) { + donations.forEach(donation -> setEffectivePrices(donation, true)); + donations.sort(ItemSorter::compareXpPerCoin); + } + + // Set effective prices for the donation and its armor set pieces + public static void setEffectivePrices(Donation donation, boolean useCraftCost) { + if (donation.isArmorSet()) { + donation.getSet().forEach(piece -> + piece.setEffectivePrice(resolveEffectivePrice(piece.getPrice(), useCraftCost ? piece.getCraftCost() : 0)) + ); + } + donation.setEffectivePrice(resolveEffectivePrice(donation.getPrice(), useCraftCost ? donation.getCraftCost() : 0)); + } + + private static int compareEffectivePrices(Donation a, Donation b) { + double priceA = a.getEffectivePrice(); + double priceB = b.getEffectivePrice(); + + if (priceA <= 0 && priceB <= 0) return 0; // Both prices are invalid + if (priceA <= 0) return 1; // Move invalid price to the end + if (priceB <= 0) return -1; // Move invalid price to the end + return Double.compare(priceA, priceB); // Compare valid prices in ascending order + } + + // Resolve the effective price based on price and craft cost + private static double resolveEffectivePrice(double price, double craftCost) { + if (price > 0 && craftCost > 0) { + return Math.min(price, craftCost); + } + return price > 0 ? price : craftCost; // Choose whichever is valid + } + + // Comparison logic for XP per Coin + private static int compareXpPerCoin(Donation a, Donation b) { + double xpPerCoinA = calculateXpPerCoin(a); + double xpPerCoinB = calculateXpPerCoin(b); + + // If both ratios are 0, consider them equal + if (xpPerCoinA == 0 && xpPerCoinB == 0) return 0; + + // Move items with 0 XP per Coin to the end + if (xpPerCoinA == 0) return 1; + if (xpPerCoinB == 0) return -1; + + // Compare XP per Coin in descending order + return Double.compare(xpPerCoinB, xpPerCoinA); + } + + // Helper method to calculate XP per Coin + private static double calculateXpPerCoin(Donation donation) { + double effectivePrice = donation.getEffectivePrice(); + return effectivePrice > 0 ? donation.getXp() / effectivePrice : 0; + } + + // Method to cycle through sorting modes and apply the corresponding logic + public void cycleSortMode(List donations) { + // Cycle to the next sorting mode + currentSortMode = SortMode.values()[(currentSortMode.ordinal() + 1) % SortMode.values().length]; + // Apply the sorting logic for the current mode + currentSortMode.applySort(donations); + } + + public void applySort(List donations) { + currentSortMode.applySort(donations); + } + + // Get the item associated with the current filter mode + public ItemStack getCurrentSortingItem() { + return currentSortMode.getAssociatedItem(); + } + + public void resetSorting() { + this.currentSortMode = SortMode.LowestBIN; + } + + public Tooltip getTooltip() { + Text tooltip = Text.literal("Item Sort\n\n").formatted(Formatting.GREEN) + .append(getSortText(SortMode.LowestBIN)) + .append(getSortText(SortMode.CraftCost)) + .append(getSortText(SortMode.XpPerCoin)) + .append(Text.literal("\nClick to switch sort!").formatted(Formatting.YELLOW)); + return Tooltip.of(tooltip); + } + + private Text getSortText(SortMode mode) { + boolean isCurrent = mode == currentSortMode; + return Text.literal((isCurrent ? "➤ " : " ") + mode.getDisplayName() + "\n") + .formatted(isCurrent ? Formatting.AQUA : Formatting.GRAY); + } + + public enum SortMode { + LowestBIN(new ItemStack(Items.GOLD_INGOT), ItemSorter::sortByLowestBIN, "Lowest BIN"), + CraftCost(new ItemStack(Items.CRAFTING_TABLE), ItemSorter::sortByCraftCost, "Craft Cost"), + XpPerCoin(new ItemStack(Items.EXPERIENCE_BOTTLE), ItemSorter::sortByXpPerCoin, "Xp Per Coin"); + + private final ItemStack associatedItem; + private final SortFunction sortFunction; + private final String displayName; + + SortMode(ItemStack item, SortFunction function, String displayName) { + this.associatedItem = item; + this.sortFunction = function; + this.displayName = displayName; + } + + public ItemStack getAssociatedItem() { + return associatedItem; + } + + public String getDisplayName() { + return displayName; + } + + public void applySort(List donations) { + sortFunction.sort(donations); + } + } + + @FunctionalInterface + public interface SortFunction { + void sort(List donations); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java new file mode 100644 index 0000000000..b6ec744205 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java @@ -0,0 +1,301 @@ +package de.hysky.skyblocker.skyblock.museum; + +import com.google.common.collect.Lists; +import de.hysky.skyblocker.mixins.accessors.ScreenAccessor; +import de.hysky.skyblocker.skyblock.item.MuseumItemCache; +import de.hysky.skyblocker.skyblock.item.WikiLookup; +import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.screen.recipebook.RecipeBookResults; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.client.gui.widget.ToggleButtonWidget; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; +import org.lwjgl.glfw.GLFW; + +import java.util.ArrayList; +import java.util.List; + +public class MuseumManager extends ClickableWidget { + private static final Identifier BACKGROUND_TEXTURE = Identifier.ofVanilla("textures/gui/recipe_book.png"); + private static final int BACKGROUND_WIDTH = 147; + private static final int BACKGROUND_HEIGHT = 166; + + private static final int SEARCH_FIELD_WIDTH = 69; + private static final int SEARCH_FIELD_HEIGHT = 20; + private static final int BUTTON_SIZE = 20; + private static final int BUTTONS_PER_PAGE = 12; + private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); + private static final ItemSorter ITEM_SORTER = new ItemSorter(); + private static final ItemFilter ITEM_FILTER = new ItemFilter(); + private static final TextRenderer TEXT_RENDERER = CLIENT.textRenderer; + private static String SEARCH_QUERY = ""; + private static int CURRENT_PAGE = 0; + private final int layoutX; + private final int layoutY; + private final ToggleButtonWidget nextPageButton; + private final ToggleButtonWidget prevPageButton; + private final TextFieldWidget searchField; + private final List allDonations; + private final List filteredDonations = new ArrayList<>(); + private final List excludedDonationIds = new ArrayList<>(); + private final List donationButtons = Lists.newArrayListWithCapacity(BUTTONS_PER_PAGE); + private final ButtonWidget filterButton; + private final ButtonWidget sortButton; + private DonationButton hoveredDonationButton; + private int pageCount = 0; + + public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { + super(x, y, screen.width, screen.height, Text.of("")); + this.layoutX = getX() + backgroundWidth + 2; + this.layoutY = y; + + // Initialize search field + this.searchField = new TextFieldWidget(TEXT_RENDERER, layoutX + 25, layoutY + 11, SEARCH_FIELD_WIDTH, SEARCH_FIELD_HEIGHT, Text.of("Search")); + this.searchField.setMaxLength(60); + this.searchField.setVisible(true); + this.searchField.setEditableColor(0xFFFFFF); + this.searchField.setText(SEARCH_QUERY); + this.searchField.setPlaceholder(Text.literal("Search...").formatted(Formatting.ITALIC).formatted(Formatting.GRAY)); + + // Initialize page navigation buttons + this.nextPageButton = new ToggleButtonWidget(layoutX + 93, layoutY + 133, 12, 17, false); + this.nextPageButton.setTextures(RecipeBookResults.PAGE_FORWARD_TEXTURES); + this.prevPageButton = new ToggleButtonWidget(layoutX + 38, layoutY + 133, 12, 17, true); + this.prevPageButton.setTextures(RecipeBookResults.PAGE_BACKWARD_TEXTURES); + + this.allDonations = MuseumItemCache.getDonations(); + + // Create donation buttons for pagination + for (int i = 0; i < BUTTONS_PER_PAGE; i++) { + DonationButton button = new DonationButton(layoutX + 11 + 31 * (i % 4), layoutY + 34 + 31 * (i / 4)); + this.donationButtons.add(button); + } + + // Initialize sort button + this.sortButton = ButtonWidget.builder(Text.literal(""), button -> { + ITEM_SORTER.cycleSortMode(filteredDonations); + button.setTooltip(ITEM_SORTER.getTooltip()); + CURRENT_PAGE = 0; + updateButtons(); + }) + .tooltip(ITEM_SORTER.getTooltip()) + .position(layoutX + 95, layoutY + 11) + .size(BUTTON_SIZE, BUTTON_SIZE) + .build(); + + // Initialize filter button + this.filterButton = ButtonWidget.builder(Text.literal(""), button -> { + ITEM_FILTER.cycleFilterMode(allDonations, filteredDonations); + ITEM_SORTER.applySort(filteredDonations); + button.setTooltip(ITEM_FILTER.getTooltip()); + CURRENT_PAGE = 0; + updateButtons(); + }) + .tooltip(ITEM_FILTER.getTooltip()) + .position(layoutX + 116, layoutY + 11) + .size(BUTTON_SIZE, BUTTON_SIZE) + .build(); + + ITEM_FILTER.applyFilter(allDonations, filteredDonations); + ITEM_SORTER.applySort(filteredDonations); + updateSearchResults(false); + + ((ScreenAccessor) screen).callAddDrawableChild(this); + screen.setFocused(this); + } + + /** + * Resets the UI state including search text, current page, sorting, and filtering. + */ + public static void reset() { + SEARCH_QUERY = ""; + CURRENT_PAGE = 0; + ITEM_SORTER.resetSorting(); + ITEM_FILTER.resetFilter(); + } + + /** + * Updates visibility and content of page navigation buttons. + */ + private void updateNavigationButtons() { + this.prevPageButton.active = CURRENT_PAGE > 0; + this.nextPageButton.active = CURRENT_PAGE < pageCount - 1; + } + + /** + * Updates the donation buttons based on the current page and visible donations. + */ + private void updateButtons() { + List visibleDonations = filteredDonations.stream() + .filter(donation -> !excludedDonationIds.contains(donation.getId())) + .toList(); + + int buttonsSize = visibleDonations.size(); + this.pageCount = (int) Math.ceil((double) buttonsSize / BUTTONS_PER_PAGE); + + for (int i = 0; i < donationButtons.size(); ++i) { + int index = CURRENT_PAGE * donationButtons.size() + i; + + if (index < buttonsSize) { + donationButtons.get(i).init(visibleDonations.get(index)); + } else { + donationButtons.get(i).clearDisplayStack(); + } + } + updateNavigationButtons(); + } + + /** + * Updates search results based on the search text. + * + * @param resetPage Whether to reset to the first page. + */ + public void updateSearchResults(boolean resetPage) { + excludedDonationIds.clear(); + for (Donation item : allDonations) { + StringBuilder searchableContent = new StringBuilder(); + ItemStack itemStack = ItemRepository.getItemStack(item.getId()); + if (itemStack != null) { + searchableContent.append(itemStack.getName().getString()) + .append(ItemUtils.getConcatenatedLore(itemStack)); + } + if (item.getSet() != null && !item.getSet().isEmpty()) { + for (ArmorPiece piece : item.getSet()) { + ItemStack pieceStack = ItemRepository.getItemStack(piece.getId()); + if (pieceStack != null) searchableContent.append(pieceStack.getName().getString()) + .append(ItemUtils.getConcatenatedLore(pieceStack)); + } + } + if (!searchableContent.toString().toLowerCase().contains(SEARCH_QUERY.toLowerCase())) { + excludedDonationIds.add(item.getId()); + } + } + if (resetPage) CURRENT_PAGE = 0; + updateButtons(); + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + // Render the background texture for the widget + context.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_TEXTURE, layoutX, layoutY, 1.0f, 1.0f, BACKGROUND_WIDTH, BACKGROUND_HEIGHT, 256, 256 - 10); + + // Render page count if multiple pages exist + if (this.pageCount > 1) { + Text text = Text.translatable("gui.recipebook.page", CURRENT_PAGE + 1, this.pageCount); + int width = TEXT_RENDERER.getWidth(text); + + context.drawText(TEXT_RENDERER, text, layoutX - width / 2 + 73, layoutY + 137, -1, false); + } + + // Render donation buttons + this.hoveredDonationButton = null; + for (DonationButton resultButton : donationButtons) { + resultButton.render(context, mouseX, mouseY, delta); + + if (resultButton.visible && resultButton.isHovered()) this.hoveredDonationButton = resultButton; + } + + if (this.sortButton.active) { + int iconX = this.sortButton.getX() + (this.sortButton.getWidth() - 16) / 2; + int iconY = this.sortButton.getY() + (this.sortButton.getHeight() - 16) / 2; + ItemStack stack = ITEM_SORTER.getCurrentSortingItem(); + context.drawItemWithoutEntity(stack, iconX, iconY); + } + + if (this.filterButton.active) { + int iconX = this.filterButton.getX() + (this.filterButton.getWidth() - 16) / 2; + int iconY = this.filterButton.getY() + (this.filterButton.getHeight() - 16) / 2; + ItemStack stack = ITEM_FILTER.getCurrentFilterItem(); + context.drawItemWithoutEntity(stack, iconX, iconY); + } + + // Render the page flip buttons + if (this.prevPageButton.active) this.prevPageButton.render(context, mouseX, mouseY, delta); + if (this.nextPageButton.active) this.nextPageButton.render(context, mouseX, mouseY, delta); + + this.filterButton.render(context, mouseX, mouseY, delta); + this.sortButton.render(context, mouseX, mouseY, delta); + this.searchField.render(context, mouseX, mouseY, delta); + + this.drawTooltip(context, mouseX, mouseY); + } + + public void drawTooltip(DrawContext context, int x, int y) { + // Draw the tooltip of the hovered result button if one is hovered over + if (this.hoveredDonationButton != null && !this.hoveredDonationButton.getDisplayStack().isEmpty()) { + ItemStack stack = this.hoveredDonationButton.getDisplayStack(); + Identifier tooltipStyle = stack.get(DataComponentTypes.TOOLTIP_STYLE); + + context.drawTooltip(TEXT_RENDERER, hoveredDonationButton.getItemTooltip(), x, y, tooltipStyle); + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (this.searchField.mouseClicked(mouseX, mouseY, button)) { + this.searchField.setFocused(true); + return true; + } else if (this.nextPageButton.mouseClicked(mouseX, mouseY, button)) { + CURRENT_PAGE++; + updateButtons(); + return true; + } else if (this.prevPageButton.mouseClicked(mouseX, mouseY, button)) { + CURRENT_PAGE--; + updateButtons(); + return true; + } else if (this.filterButton.mouseClicked(mouseX, mouseY, button)) { + return true; + } else if (this.sortButton.mouseClicked(mouseX, mouseY, button)) { + return true; + } + + this.searchField.setFocused(false); + this.filterButton.setFocused(false); + this.sortButton.setFocused(false); + return false; + } + + @Override + public boolean keyReleased(int keyCode, int scanCode, int modifiers) { + return super.keyReleased(keyCode, scanCode, modifiers); + } + + @Override + public boolean charTyped(char chr, int modifiers) { + if (this.searchField.charTyped(chr, modifiers)) { + SEARCH_QUERY = this.searchField.getText(); + this.updateSearchResults(true); + return true; + } + return false; + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if ((this.searchField.isActive() && keyCode == GLFW.GLFW_KEY_E) + || this.searchField.keyPressed(keyCode, scanCode, modifiers)) { + SEARCH_QUERY = this.searchField.getText(); + this.updateSearchResults(true); + return true; + } else if (WikiLookup.wikiLookup.matchesKey(keyCode, scanCode) && CLIENT.player != null && hoveredDonationButton != null && hoveredDonationButton.getDisplayStack() != null) { + WikiLookup.openWiki(hoveredDonationButton.getDisplayStack(), CLIENT.player); + return true; + } + return false; + } + + @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 94dc75aa3e..9368bd5b12 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java @@ -453,4 +453,17 @@ public static Matcher getLoreLineIfContainsMatch(ItemStack stack, Pattern patter } return stringBuilder.toString(); } -} \ No newline at end of file + + /** + * Checks if the given item ID represents an equipment piece. + */ + public static boolean isEquipment(String itemId) { + return (itemId.contains("BELT") || + itemId.contains("GLOVES") || + itemId.contains("CLOAK") || + itemId.contains("GAUNTLET") || + itemId.contains("NECKLACE") || + itemId.contains("BRACELET") || + itemId.contains("HAT")); + } +} diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index eea3fd24b0..38494011f6 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -762,6 +762,9 @@ "skyblocker.config.uiAndVisuals.itemCooldown": "Item Cooldown", "skyblocker.config.uiAndVisuals.itemCooldown.enableItemCooldowns": "Enable Item Cooldown", + "skyblocker.config.uiAndVisuals.museumOverlay": "Museum Hud", + "skyblocker.config.uiAndVisuals.museumOverlay.@Tooltip": "Shows uncontributed items including their prices and more.\n\nMake sure to enable your Museum API to work", + "skyblocker.config.uiAndVisuals.searchOverlay": "Search Overlay", "skyblocker.config.uiAndVisuals.searchOverlay.enableAuctionHouse": "Enable For Auction House", "skyblocker.config.uiAndVisuals.searchOverlay.enableAuctionHouse.@Tooltip": "Show custom search overlay when searching in the auction house.", From 4c607afb21a56a4aa5c488c11ffa76d2289fa919 Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Fri, 6 Dec 2024 16:36:32 +0100 Subject: [PATCH 02/17] refactor --- .../skyblocker/mixins/HandledScreenMixin.java | 10 +- .../item/tooltip/adders/MuseumTooltip.java | 2 +- .../skyblock/museum/ArmorPiece.java | 40 ----- .../skyblocker/skyblock/museum/Donation.java | 131 +++++++++------- .../skyblock/museum/DonationButton.java | 129 +++++++++------- .../skyblock/museum/FormatingUtils.java | 70 --------- .../skyblock/museum/ItemFilter.java | 12 +- .../skyblock/museum/ItemSorter.java | 141 ++++++++++-------- .../{item => museum}/MuseumItemCache.java | 98 +++++++++--- .../skyblock/museum/MuseumManager.java | 56 ++++--- .../skyblock/museum/MuseumUtils.java | 137 +++++++++++++++++ .../skyblocker/skyblock/museum/PriceData.java | 48 ++++++ .../de/hysky/skyblocker/utils/ItemUtils.java | 14 ++ .../java/de/hysky/skyblocker/utils/Utils.java | 2 +- .../assets/skyblocker/lang/en_us.json | 4 +- 15 files changed, 546 insertions(+), 348 deletions(-) delete mode 100644 src/main/java/de/hysky/skyblocker/skyblock/museum/ArmorPiece.java delete mode 100644 src/main/java/de/hysky/skyblocker/skyblock/museum/FormatingUtils.java rename src/main/java/de/hysky/skyblocker/skyblock/{item => museum}/MuseumItemCache.java (79%) create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/museum/PriceData.java diff --git a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java index 271e748f72..ea39477bfc 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java @@ -15,6 +15,7 @@ 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.museum.MuseumItemCache; import de.hysky.skyblocker.skyblock.museum.MuseumManager; import de.hysky.skyblocker.skyblock.quicknav.QuickNav; import de.hysky.skyblocker.skyblock.quicknav.QuickNavButton; @@ -98,9 +99,6 @@ public abstract class HandledScreenMixin extends Screen @Shadow protected abstract List getTooltipFromItem(ItemStack stack); - @Shadow - public abstract T getScreenHandler(); - @Shadow protected int backgroundWidth; @@ -318,10 +316,8 @@ protected HandledScreenMixin(Text title) { } } - case GenericContainerScreenHandler genericContainerScreenHandler when title.equals(MuseumItemCache.DONATION_CONFIRMATION_SCREEN_TITLE) -> { - //Museum Item Cache donation tracking - MuseumItemCache.handleClick(slot, slotId, genericContainerScreenHandler.slots); - } + case GenericContainerScreenHandler genericContainerScreenHandler when title.equals(MuseumItemCache.DONATION_CONFIRMATION_SCREEN_TITLE) -> //Museum Item Cache donation tracking + MuseumItemCache.handleClick(slot, slotId, genericContainerScreenHandler.slots); case null, default -> {} } 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 index c4bf96c7d2..e52eefe25e 100644 --- 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 @@ -1,6 +1,6 @@ package de.hysky.skyblocker.skyblock.item.tooltip.adders; -import de.hysky.skyblocker.skyblock.item.MuseumItemCache; +import de.hysky.skyblocker.skyblock.museum.MuseumItemCache; import de.hysky.skyblocker.skyblock.item.tooltip.SimpleTooltipAdder; import de.hysky.skyblocker.skyblock.item.tooltip.info.TooltipInfoType; import de.hysky.skyblocker.utils.ItemUtils; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/ArmorPiece.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/ArmorPiece.java deleted file mode 100644 index 2783400eb5..0000000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/ArmorPiece.java +++ /dev/null @@ -1,40 +0,0 @@ -package de.hysky.skyblocker.skyblock.museum; - -public class ArmorPiece { - private final String id; - private double craftCost; - private double price; - private double effectivePrice; - - public ArmorPiece(String id) { - this.id = id; - } - - public double getEffectivePrice() { - return effectivePrice; - } - - public void setEffectivePrice(double effectivePrice) { - this.effectivePrice = effectivePrice; - } - - public String getId() { - return id; - } - - public double getCraftCost() { - return craftCost; - } - - public void setCraftCost(double craftCost) { - this.craftCost = craftCost; - } - - public double getPrice() { - return price; - } - - public void setPrice(double price) { - this.price = price; - } -} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java index a446dd8fc2..12c8f5641d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java @@ -1,23 +1,24 @@ package de.hysky.skyblocker.skyblock.museum; -import de.hysky.skyblocker.skyblock.item.tooltip.adders.CraftPriceTooltip; -import de.hysky.skyblocker.utils.ItemUtils; -import de.hysky.skyblocker.utils.NEURepoManager; -import io.github.moulberry.repo.data.NEUItem; +import net.minecraft.util.Pair; +import java.util.ArrayList; import java.util.List; public class Donation { private final String category; private final String id; - private final List set; - private final int xp; + private final List> set; private final List upgrades; - private double price; - private double craftCost; - private double effectivePrice; + private List downgrades; + private List> countsTowards;// downgrades not donated + private PriceData priceData; + private Pair discount; + private final int xp; + private int totalXp; + private double xpCoinsRatio; - public Donation(String category, String id, List set, int xp, List upgrades) { + public Donation(String category, String id, List> set, int xp, List upgrades) { this.category = category; this.id = id; this.set = set; @@ -25,81 +26,101 @@ public Donation(String category, String id, List set, int xp, List> getCountsTowards() { + return countsTowards; + } + + public void setCountsTowards(List> countsTowards) { + this.countsTowards = countsTowards; + } + + public PriceData getPriceData() { + return priceData; + } + + public void setPriceData() { + this.priceData = new PriceData(this); + } + + public void setDowngrades() { + List downgrades = new ArrayList<>(); + for (List list : MuseumItemCache.ORDERED_UPGRADES) { + int armorIndex = list.indexOf(id); + if (armorIndex > 0) { + for (int i = armorIndex - 1; i >= 0; i--) { + downgrades.add(list.get(i)); + } } - } else { - this.price = ItemUtils.getItemPrice(id).leftDouble(); - this.craftCost = getCraftCost(id); } + this.downgrades = downgrades; } - public String getCategory() { - return category; + public Pair getDiscount() { + return discount; } - public String getId() { - return id; + public void setDiscount(Pair discount) { + this.discount = discount; } - public List getSet() { - return set; + public boolean hasDiscount() { + return discount != null && discount.getRight() > 0d; } - public boolean isArmorSet() { - return !set.isEmpty(); + public List getDowngrades() { + return downgrades; } - public int getXp() { - return xp; + public double getXpCoinsRatio() { + return xpCoinsRatio; } - public List getUpgrades() { - return upgrades; + public void setXpCoinsRatio(double xpCoinsRatio) { + this.xpCoinsRatio = xpCoinsRatio; } - public double getCraftCost() { - return craftCost; + public String getCategory() { + return category; } - public double getEffectivePrice() { - return effectivePrice; + public String getId() { + return id; } - public void setEffectivePrice(double effectivePrice) { - this.effectivePrice = effectivePrice; + + public boolean isSet() { + return !set.isEmpty(); } - public double getPrice() { - return price; + public List> getSet() { + return set; } - public boolean hasPrice() { - return effectivePrice > 0; + public int getXp() { + return xp; } - public boolean hasLBinPrice() { - return price > 0; + public List getUpgrades() { + return upgrades; } public boolean isCraftable() { - return craftCost > 0; + return this.priceData.getCraftCost() > 0; } - private double getCraftCost(String id) { - NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(id); - if (neuItem != null && !neuItem.getRecipes().isEmpty()) { - return CraftPriceTooltip.getItemCost(neuItem.getRecipes().getFirst(), 0); - } - return 0; + public boolean hasLBinPrice() { + return this.priceData.getLBinPrice() > 0; + } + + public boolean hasPrice() { + return this.priceData.getEffectivePrice() > 0; } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java index 4dd56e1b38..6f21cf44ee 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java @@ -1,11 +1,10 @@ package de.hysky.skyblocker.skyblock.museum; -import de.hysky.skyblocker.skyblock.item.MuseumItemCache; import de.hysky.skyblocker.skyblock.item.WikiLookup; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.utils.ItemUtils; -import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; import net.minecraft.client.gui.screen.recipebook.AnimatedResultButton; @@ -13,24 +12,25 @@ import net.minecraft.client.render.RenderLayer; import net.minecraft.item.ItemStack; import net.minecraft.screen.ScreenTexts; -import net.minecraft.text.Style; +import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import net.minecraft.util.Pair; import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.Locale; public class DonationButton extends ClickableWidget { - private static final int SIZE = 33; private static final int ITEM_OFFSET = 8; - private final Map setEffectivePricesText = new Object2ObjectArrayMap<>(); - List tooltip; + private Donation donation = null; private ItemStack itemStack = null; - private String effectivePriceText = null; + private static final String WIKI_LOCKUP_KEY = WikiLookup.wikiLookup.getBoundKeyTranslationKey(); + private static final TextRenderer TEXT_RENDERER = MinecraftClient.getInstance().textRenderer; + private String textToRender; + private List tooltip; protected DonationButton(int x, int y) { super(x, y, SIZE, SIZE + 2, ScreenTexts.EMPTY); @@ -47,22 +47,17 @@ protected ItemStack getDisplayStack() { */ public void init(Donation donation) { this.donation = donation; - this.effectivePriceText = FormatingUtils.formatPrice(donation.getEffectivePrice()); - - // Populate effective prices for armor pieces in the set - if (donation.isArmorSet()) { - donation.getSet().forEach(piece -> setEffectivePricesText.put(piece.getId(), FormatingUtils.formatPrice(piece.getEffectivePrice()))); - } + this.textToRender = MuseumUtils.formatPrice(donation.getPriceData().getEffectivePrice()); // Determine the item stack to display - this.itemStack = !donation.isArmorSet() + this.itemStack = !donation.isSet() ? ItemRepository.getItemStack(donation.getId()) : ItemRepository.getItemStack( donation.getSet().stream() - .filter(piece -> piece.getId().toLowerCase().contains("helmet") || piece.getId().toLowerCase().contains("hat")) + .filter(piece -> piece.getLeft().toLowerCase(Locale.ENGLISH).contains("helmet") || piece.getLeft().toLowerCase(Locale.ENGLISH).contains("hat")) .findFirst() - .orElse(donation.getSet().get(1)) // Get chestplate if helmet not found - .getId() + .orElse(donation.getSet().get(1)) // gets chestplate + .getLeft() ); if (itemStack != null) { @@ -76,33 +71,29 @@ public void init(Donation donation) { * Clears the display stack and resets the button state. */ protected void clearDisplayStack() { - this.visible = false; // Hides the button + this.visible = false; this.itemStack = null; this.tooltip = null; - this.effectivePriceText = null; - this.setEffectivePricesText.clear(); + this.textToRender = null; } @Override protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { - if (visible) { - MinecraftClient client = MinecraftClient.getInstance(); + context.drawGuiTexture(RenderLayer::getGuiTextured, AnimatedResultButton.SLOT_CRAFTABLE_TEXTURE, this.getX(), this.getY(), this.width, this.height); - context.drawGuiTexture(RenderLayer::getGuiTextured, AnimatedResultButton.SLOT_CRAFTABLE_TEXTURE, this.getX(), this.getY(), this.width, this.height); + int yOffset = 8; - int yOffset = 8; + if (donation.hasPrice()) { + int textWidth = TEXT_RENDERER.getWidth(textToRender); + int centeredX = this.getX() + (this.width / 2) - (textWidth / 2); + int textY = this.getY() + ITEM_OFFSET + 13; + context.drawText(TEXT_RENDERER, textToRender, centeredX, textY, 0xFF00FF00, true); - // Draw effective price if available - if (donation.hasPrice()) { - int textWidth = client.textRenderer.getWidth(effectivePriceText); - int centeredX = this.getX() + (this.width / 2) - (textWidth / 2); - int textY = this.getY() + ITEM_OFFSET + 13; - context.drawText(client.textRenderer, effectivePriceText, centeredX, textY, 0xFF00FF00, true); - yOffset -= 4; - } - - context.drawItemWithoutEntity(itemStack, this.getX() + ITEM_OFFSET, this.getY() + yOffset); + yOffset -= 4; } + + context.drawItemWithoutEntity(itemStack, this.getX() + ITEM_OFFSET, this.getY() + yOffset); + } /** @@ -110,41 +101,65 @@ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float d */ private void createTooltip() { List tooltip = new ArrayList<>(); - Style textStyle = Style.EMPTY; - if (donation.isArmorSet()) { - for (ArmorPiece piece : donation.getSet()) { - ItemStack stack = ItemRepository.getItemStack(piece.getId()); + boolean soulbound = ItemUtils.isSoulbound(itemStack); + Pair discount = donation.getDiscount(); + List> countsTowards = donation.getCountsTowards(); + + // Display name + tooltip.add(MuseumUtils.getDisplayName(donation.getId(), donation.isSet())); + + // Set pieces display names + if (donation.isSet()) { + for (Pair piece : donation.getSet()) { + ItemStack stack = ItemRepository.getItemStack(piece.getLeft()); if (stack != null) { - textStyle = stack.getName().getSiblings().getFirst().getStyle(); Text itemName = stack.getName().copy(); - if (ItemUtils.getLore(stack).stream().anyMatch(lore -> lore.getString().toLowerCase().contains("soulbound"))) { + if (soulbound) { tooltip.add(Text.literal(" ").append(itemName)); - } else if (setEffectivePricesText.get(piece.getId()) != null) { - tooltip.add(Text.literal(" ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.literal(setEffectivePricesText.get(piece.getId())).formatted(Formatting.GOLD)).append(Text.literal(")").formatted(Formatting.DARK_GRAY))); + } else if (piece.getRight().getEffectivePrice() > 0) { + tooltip.add(Text.literal(" ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.literal(MuseumUtils.formatPrice(piece.getRight().getEffectivePrice())).formatted(Formatting.GOLD)).append(Text.literal(")").formatted(Formatting.DARK_GRAY))); } else { tooltip.add(Text.literal(" ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.literal("Unknown").formatted(Formatting.RED)).append(Text.literal(")").formatted(Formatting.DARK_GRAY))); } } } - String armorName = MuseumItemCache.ARMOR_NAMES.get(donation.getId()); - tooltip.addFirst(Text.literal(Objects.requireNonNullElseGet(armorName, () -> donation.getId().toLowerCase())).setStyle(textStyle)); + } + tooltip.add(Text.empty()); + + Text xpText = Text.literal(String.valueOf(donation.getTotalXp())).append(" SkyBlock XP").formatted(Formatting.AQUA); + tooltip.add(Text.literal("Reward: ").formatted(Formatting.GRAY).append(xpText)); + + if (soulbound) { + tooltip.add(Text.literal("Untradable").formatted(Formatting.GRAY).append(Text.literal(" (Soulbound)").formatted(Formatting.DARK_GRAY))); } else { - ItemStack stack = ItemRepository.getItemStack(donation.getId()); - if (stack != null) tooltip.add(stack.getName()); + PriceData priceData = donation.getPriceData(); + Text lBinText = donation.hasLBinPrice() ? Text.literal(MuseumUtils.formatPrice(priceData.getLBinPrice())).append(" Coins").formatted(Formatting.GOLD) : Text.literal("Unknown").formatted(Formatting.RED); + MutableText craftCostText = donation.isCraftable() ? Text.literal(MuseumUtils.formatPrice(donation.hasDiscount() ? priceData.getCraftCost() - discount.getRight() : priceData.getCraftCost())).append(" Coins").formatted(Formatting.GOLD) : Text.literal("Unknown").formatted(Formatting.RED); + Text discountText = donation.hasDiscount() && donation.isCraftable() ? Text.literal(" (").formatted(Formatting.DARK_GRAY).append(Text.literal(MuseumUtils.formatPrice(priceData.getCraftCost())).formatted(Formatting.GOLD)).append(" - ").append(Text.literal(MuseumUtils.formatPrice(discount.getRight())).formatted(Formatting.GOLD)).append(Text.literal(")").formatted(Formatting.DARK_GRAY)) : Text.empty(); + Text xpCoinsRatio = Text.literal(MuseumUtils.formatPrice(donation.getXpCoinsRatio())).append(" Coins per XP").formatted(Formatting.AQUA); + + tooltip.add(Text.literal("Lowest BIN: ").formatted(Formatting.GRAY).append(lBinText)); + tooltip.add(Text.literal("Craft Cost: ").formatted(Formatting.GRAY).append(craftCostText).append(discountText)); + tooltip.add(Text.literal("Coins/XP ratio: ").formatted(Formatting.GRAY).append(xpCoinsRatio)); } - Text lbinText = donation.hasLBinPrice() ? Text.literal(FormatingUtils.formatPrice(donation.getPrice())).append(" Coins").formatted(Formatting.GOLD) : Text.literal("Unknown").formatted(Formatting.RED); - Text craftCostText = donation.isCraftable() ? Text.literal(FormatingUtils.formatPrice(donation.getCraftCost())).append(" Coins").formatted(Formatting.GOLD) : Text.literal("Unknown").formatted(Formatting.RED); + if (countsTowards.size() > 1) { + tooltip.add(Text.empty()); + tooltip.add(Text.literal("Will count for:").formatted(Formatting.GRAY)); + for (Pair credit : countsTowards) { + tooltip.add(Text.literal(" ● ").formatted(Formatting.GRAY).append(MuseumUtils.getDisplayName(credit.getLeft(), donation.isSet())).append(Text.literal(" (" + credit.getRight() + " XP)").formatted(Formatting.AQUA))); + } + } - String wikiLookupKey = WikiLookup.wikiLookup.getBoundKeyTranslationKey(); + if (donation.isCraftable() && donation.hasDiscount()) { + tooltip.add(Text.empty()); + tooltip.add(Text.literal("Crafted with: ").formatted(Formatting.GRAY).append(Text.literal("(Donated Item)").formatted(Formatting.DARK_GRAY))); + tooltip.add(Text.literal(" - ").formatted(Formatting.GRAY).append(MuseumUtils.getDisplayName(discount.getLeft(), donation.isSet())).append(Text.literal(" ✔").formatted(Formatting.GREEN)).append(Text.literal(" (").formatted(Formatting.DARK_GRAY).append(Text.literal(MuseumUtils.formatPrice(discount.getRight())).formatted(Formatting.GOLD)).append(")").formatted(Formatting.DARK_GRAY))); + } - tooltip.add(Text.literal("")); - tooltip.add(Text.literal("Reward: ").formatted(Formatting.GRAY).append(Text.literal(String.valueOf(donation.getXp())).append(" SkyBlock XP").formatted(Formatting.AQUA))); - tooltip.add(Text.literal("Lowest BIN: ").formatted(Formatting.GRAY).append(lbinText)); - tooltip.add(Text.literal("Craft Cost: ").formatted(Formatting.GRAY).append(craftCostText)); - tooltip.add(Text.literal("")); - tooltip.add(Text.literal("Click on " + wikiLookupKey.substring(wikiLookupKey.lastIndexOf('.') + 1).toUpperCase() + " to open the wiki page!").formatted(Formatting.YELLOW)); + tooltip.add(Text.empty()); + tooltip.add(Text.literal("Click on " + WIKI_LOCKUP_KEY.substring(WIKI_LOCKUP_KEY.lastIndexOf('.') + 1).toUpperCase(Locale.ENGLISH) + " to open the wiki page!").formatted(Formatting.YELLOW)); this.tooltip = tooltip; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/FormatingUtils.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/FormatingUtils.java deleted file mode 100644 index f7183b4dbb..0000000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/FormatingUtils.java +++ /dev/null @@ -1,70 +0,0 @@ -package de.hysky.skyblocker.skyblock.museum; - -public class FormatingUtils { - - /** - * Formats an armor item ID into a readable name - */ - public static String formatArmorName(String id, boolean isEquipment) { - String lowercaseKey = id.toLowerCase(); - - // Step 2: Replace "_" with space - String withSpaces = lowercaseKey.replace("_", " "); - - // Step 3: Capitalize the first letter of each word - String[] words = withSpaces.split(" "); - StringBuilder formattedName = new StringBuilder(); - for (String word : words) { - // Uppercase first letter, lowercase the rest - formattedName.append(Character.toUpperCase(word.charAt(0))) - .append(word.substring(1)) - .append(" "); - } - - if (isEquipment) { - formattedName.append("Equipment"); - } else if (!lowercaseKey.contains("armor") && - !lowercaseKey.contains("outfit") && - !lowercaseKey.contains("suit") && - !lowercaseKey.contains("tuxedo")) { - formattedName.append("Armor"); - } - - return formattedName.toString().trim(); - } - - /** - * Formats a double value into a shortened human-readable format. - * - * @param value The number to format. - * @return A formatted string (e.g., "10m", "5k", "1.2b"). - */ - public static String formatPrice(double value) { - String suffix = ""; - double divisor = 1; - - if (value >= 1_000_000_000) { - suffix = "b"; - divisor = 1_000_000_000; - } else if (value >= 1_000_000) { - suffix = "m"; - divisor = 1_000_000; - } else if (value >= 1_000) { - suffix = "k"; - divisor = 1_000; - } - - // Round the result first - double result = value / divisor; - // Prevent rounding up - if (result >= 100) { - result = (long) result; // Keep it as an integer - } else { - result = Math.floor(result * 10) / 10.0; // Round down to 1 decimal place - } - - return (result == (long) result) - ? String.format("%d%s", (long) result, suffix) - : String.format("%.1f%s", result, suffix); - } -} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java index c81786202e..48e2e0f73a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java @@ -8,6 +8,7 @@ import net.minecraft.util.Formatting; import java.util.List; +import java.util.function.UnaryOperator; import java.util.stream.Collectors; public class ItemFilter { @@ -80,10 +81,10 @@ public enum FilterMode { RARITIES(ItemRepository.getItemStack("JADERALD"), ItemFilter::filterRarities, "Rarities"); private final ItemStack associatedItem; - private final FilterFunction filterFunction; + private final UnaryOperator> filterFunction; private final String displayName; - FilterMode(ItemStack item, FilterFunction function, String displayName) { + FilterMode(ItemStack item, UnaryOperator> function, String displayName) { this.associatedItem = item; this.filterFunction = function; this.displayName = displayName; @@ -99,12 +100,7 @@ public String getDisplayName() { public void applyFilter(List items, List filteredList) { filteredList.clear(); - filteredList.addAll(filterFunction.filter(items)); + filteredList.addAll(filterFunction.apply(items)); } } - - @FunctionalInterface - public interface FilterFunction { - List filter(List items); - } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java index 8aa7e8fc2b..17c0292b8e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java @@ -5,75 +5,91 @@ import net.minecraft.item.Items; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import net.minecraft.util.Pair; import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.stream.Collectors; public class ItemSorter { - private SortMode currentSortMode = SortMode.LowestBIN; - - private static void sortByLowestBIN(List donations) { - donations.forEach(donation -> setEffectivePrices(donation, false)); + // Sorting logic + private static final Consumer> sortByLowestBIN = donations -> { + donations.forEach(donation -> updateDonationData(donation, false)); donations.sort(ItemSorter::compareEffectivePrices); - } - - private static void sortByCraftCost(List donations) { - donations.forEach(donation -> setEffectivePrices(donation, true)); + }; + private static final Consumer> sortByCraftCost = donations -> { + donations.forEach(donation -> updateDonationData(donation, true)); donations.sort(ItemSorter::compareEffectivePrices); - } - - private static void sortByXpPerCoin(List donations) { - donations.forEach(donation -> setEffectivePrices(donation, true)); - donations.sort(ItemSorter::compareXpPerCoin); - } + }; + private static final Consumer> sortByXpPerCoin = donations -> { + donations.forEach(donation -> updateDonationData(donation, true)); + donations.sort(ItemSorter::compareCoinsPerXP); + }; + private SortMode currentSortMode = SortMode.LowestBIN; // Set effective prices for the donation and its armor set pieces - public static void setEffectivePrices(Donation donation, boolean useCraftCost) { - if (donation.isArmorSet()) { - donation.getSet().forEach(piece -> - piece.setEffectivePrice(resolveEffectivePrice(piece.getPrice(), useCraftCost ? piece.getCraftCost() : 0)) - ); - } - donation.setEffectivePrice(resolveEffectivePrice(donation.getPrice(), useCraftCost ? donation.getCraftCost() : 0)); + public static void updateDonationData(Donation donation, boolean useCraftCost) { + // Gather all donations that this one counts towards + List downgrades = donation.getDowngrades(); + Pair discount = donation.getDiscount(); + List willCountFor = downgrades.stream() + .map(MuseumManager::getDonation) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + willCountFor.addFirst(donation); // Ensure donation itself is part of the list + + // Calculate cumulative XP + int totalXP = willCountFor.stream().mapToInt(Donation::getXp).sum(); + + // Calculate effective prices + double lBinPrice = donation.getPriceData().getLBinPrice(); + double rawCraftCost = donation.isCraftable() ? donation.getPriceData().getCraftCost() : 0; + double craftCost = discount != null ? rawCraftCost - discount.getRight() : rawCraftCost; + double effectivePrice = useCraftCost + ? (craftCost > 0 ? (lBinPrice == 0 ? craftCost : Math.min(craftCost, lBinPrice)) : lBinPrice) + : (lBinPrice > 0 ? lBinPrice : craftCost); + double ratio = totalXP > 0 && effectivePrice > 0 ? effectivePrice / totalXP : 0; + + // Update donation with computed data + if (donation.isSet()) donation.getSet().forEach(pair -> pair.getRight().setEffectivePrice(effectivePrice == craftCost ? pair.getRight().getCraftCost() : pair.getRight().getLBinPrice())); + donation.getPriceData().setEffectivePrice(effectivePrice); + donation.setXpCoinsRatio(ratio); + donation.setTotalXp(totalXP); + donation.setCountsTowards(willCountFor.stream() + .map(d -> new Pair<>(d.getId(), d.getXp())) + .toList()); } private static int compareEffectivePrices(Donation a, Donation b) { - double priceA = a.getEffectivePrice(); - double priceB = b.getEffectivePrice(); + double priceA = a.getPriceData().getEffectivePrice(); + double priceB = b.getPriceData().getEffectivePrice(); - if (priceA <= 0 && priceB <= 0) return 0; // Both prices are invalid + if (priceA <= 0 && priceB <= 0) { + // Both prices are invalid, sort by XP descending + return Integer.compare(b.getTotalXp(), a.getTotalXp()); + } if (priceA <= 0) return 1; // Move invalid price to the end if (priceB <= 0) return -1; // Move invalid price to the end - return Double.compare(priceA, priceB); // Compare valid prices in ascending order - } - // Resolve the effective price based on price and craft cost - private static double resolveEffectivePrice(double price, double craftCost) { - if (price > 0 && craftCost > 0) { - return Math.min(price, craftCost); - } - return price > 0 ? price : craftCost; // Choose whichever is valid + // Compare valid prices in ascending order + return Double.compare(priceA, priceB); } - // Comparison logic for XP per Coin - private static int compareXpPerCoin(Donation a, Donation b) { - double xpPerCoinA = calculateXpPerCoin(a); - double xpPerCoinB = calculateXpPerCoin(b); - - // If both ratios are 0, consider them equal - if (xpPerCoinA == 0 && xpPerCoinB == 0) return 0; - - // Move items with 0 XP per Coin to the end - if (xpPerCoinA == 0) return 1; - if (xpPerCoinB == 0) return -1; + private static int compareCoinsPerXP(Donation a, Donation b) { + double xpPerCoinA = a.getXpCoinsRatio(); + double xpPerCoinB = b.getXpCoinsRatio(); - // Compare XP per Coin in descending order - return Double.compare(xpPerCoinB, xpPerCoinA); - } + if (xpPerCoinA == 0 && xpPerCoinB == 0) { + // Both ratios are 0, sort by XP descending + return Integer.compare(b.getTotalXp(), a.getTotalXp()); + } + if (xpPerCoinA == 0) return 1; // Move items with 0 XP/coin to the end + if (xpPerCoinB == 0) return -1; // Move items with 0 XP/coin to the end - // Helper method to calculate XP per Coin - private static double calculateXpPerCoin(Donation donation) { - double effectivePrice = donation.getEffectivePrice(); - return effectivePrice > 0 ? donation.getXp() / effectivePrice : 0; + // Compare XP/coin ratios in descending order + return Double.compare(xpPerCoinA, xpPerCoinB); } // Method to cycle through sorting modes and apply the corresponding logic @@ -81,11 +97,11 @@ public void cycleSortMode(List donations) { // Cycle to the next sorting mode currentSortMode = SortMode.values()[(currentSortMode.ordinal() + 1) % SortMode.values().length]; // Apply the sorting logic for the current mode - currentSortMode.applySort(donations); + applySort(donations); } public void applySort(List donations) { - currentSortMode.applySort(donations); + currentSortMode.getSortFunction().accept(donations); } // Get the item associated with the current filter mode @@ -101,7 +117,7 @@ public Tooltip getTooltip() { Text tooltip = Text.literal("Item Sort\n\n").formatted(Formatting.GREEN) .append(getSortText(SortMode.LowestBIN)) .append(getSortText(SortMode.CraftCost)) - .append(getSortText(SortMode.XpPerCoin)) + .append(getSortText(SortMode.CoinsPerXP)) .append(Text.literal("\nClick to switch sort!").formatted(Formatting.YELLOW)); return Tooltip.of(tooltip); } @@ -113,15 +129,15 @@ private Text getSortText(SortMode mode) { } public enum SortMode { - LowestBIN(new ItemStack(Items.GOLD_INGOT), ItemSorter::sortByLowestBIN, "Lowest BIN"), - CraftCost(new ItemStack(Items.CRAFTING_TABLE), ItemSorter::sortByCraftCost, "Craft Cost"), - XpPerCoin(new ItemStack(Items.EXPERIENCE_BOTTLE), ItemSorter::sortByXpPerCoin, "Xp Per Coin"); + LowestBIN(new ItemStack(Items.GOLD_INGOT), sortByLowestBIN, "Lowest BIN"), + CraftCost(new ItemStack(Items.CRAFTING_TABLE), sortByCraftCost, "Craft Cost"), + CoinsPerXP(new ItemStack(Items.EXPERIENCE_BOTTLE), sortByXpPerCoin, "Coins/XP Ratio"); private final ItemStack associatedItem; - private final SortFunction sortFunction; + private final Consumer> sortFunction; private final String displayName; - SortMode(ItemStack item, SortFunction function, String displayName) { + SortMode(ItemStack item, Consumer> function, String displayName) { this.associatedItem = item; this.sortFunction = function; this.displayName = displayName; @@ -135,13 +151,8 @@ public String getDisplayName() { return displayName; } - public void applySort(List donations) { - sortFunction.sort(donations); + public Consumer> getSortFunction() { + return sortFunction; } } - - @FunctionalInterface - public interface SortFunction { - void sort(List donations); - } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java similarity index 79% rename from src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java rename to src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java index 897cc7c906..8a4ce64932 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java @@ -1,4 +1,4 @@ -package de.hysky.skyblocker.skyblock.item; +package de.hysky.skyblocker.skyblock.museum; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -11,9 +11,6 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.annotations.Init; -import de.hysky.skyblocker.skyblock.museum.ArmorPiece; -import de.hysky.skyblocker.skyblock.museum.Donation; -import de.hysky.skyblocker.skyblock.museum.FormatingUtils; import de.hysky.skyblocker.utils.*; import de.hysky.skyblocker.utils.Http.ApiResponse; import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; @@ -26,22 +23,22 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.command.CommandRegistryAccess; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.*; import net.minecraft.screen.slot.Slot; import net.minecraft.text.Text; +import net.minecraft.util.Pair; import net.minecraft.util.collection.DefaultedList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; @@ -58,7 +55,8 @@ public class MuseumItemCache { public static final Map MAPPED_IDS = new Object2ObjectArrayMap<>(); private static CompletableFuture loaded; private static final Path MUSEUM_INFO = NEURepoManager.NEU_REPO.file("constants/museum.json").getFsPath(); - private static final List MUSEUM_DONATIONS = new ArrayList<>(); + public static final List MUSEUM_DONATIONS = new ArrayList<>(); + public static List> ORDERED_UPGRADES = new ArrayList<>(); @Init public static void init() { @@ -107,7 +105,7 @@ private static void save() { } /** - * Loads museum data from a JSON file, processes it, and updates internal data structures. + * Loads museum data from local repo. */ public static void loadMuseumItems() { MUSEUM_DONATIONS.clear(); @@ -136,12 +134,12 @@ public static void loadMuseumItems() { for (JsonElement element : array) { String itemID = element.getAsString(); - List set = new ArrayList<>(List.of()); + List> set = new ArrayList<>(); if (category.equals("armor")) { boolean isEquipment = true; for (JsonElement jsonElement : setsToItems.get(itemID).getAsJsonArray()) { if (isEquipment) isEquipment = ItemUtils.isEquipment(jsonElement.getAsString()); - set.add(new ArmorPiece(jsonElement.getAsString())); + set.add(new Pair<>(jsonElement.getAsString(), null)); } String realId = itemID; for (Map.Entry exception : setExceptions.entrySet()) { @@ -150,13 +148,40 @@ public static void loadMuseumItems() { break; } } - ARMOR_NAMES.put(itemID, FormatingUtils.formatArmorName(realId, isEquipment)); + ARMOR_NAMES.put(itemID, MuseumUtils.formatArmorName(realId, isEquipment)); } int itemXP = itemToXp.get(itemID).getAsInt(); List upgrades = getUpgrades(children, itemID); + + if (!upgrades.isEmpty()) { + // Try to find an existing upgrade list that either contains itemID or overlaps with upgrades + Optional> matchingUpgrade = ORDERED_UPGRADES.stream() + .filter(orderedUpgrade -> + orderedUpgrade.contains(itemID) || + !Collections.disjoint(orderedUpgrade, upgrades)) + .findFirst(); + + if (matchingUpgrade.isPresent()) { + List orderedUpgrade = matchingUpgrade.get(); + // If the matching list has fewer or equal items, replace it with the new upgrade list + if (orderedUpgrade.size() <= upgrades.size()) { + orderedUpgrade.clear(); + orderedUpgrade.add(itemID); + orderedUpgrade.addAll(upgrades); + } + } else { + // If no match, add a new upgrade list with itemID and upgrades + List newUpgrade = new ArrayList<>(); + newUpgrade.add(itemID); + newUpgrade.addAll(upgrades); + ORDERED_UPGRADES.add(newUpgrade); + } + } + MUSEUM_DONATIONS.add(new Donation(category, itemID, set, itemXP, upgrades)); } } + MUSEUM_DONATIONS.forEach(Donation::setDowngrades); LOGGER.info("[Skyblocker] Loaded museum data"); } catch (Exception e) { @@ -165,7 +190,7 @@ public static void loadMuseumItems() { } /** - * Gets a list of all upgrades (parent items) for a given item. + * Gets a list of all upgrades for a given item. */ public static List getUpgrades(Map children, String item) { List upgrades = new ArrayList<>(); @@ -203,15 +228,32 @@ public static List getDonations() { if (MUSEUM_ITEM_CACHE.containsKey(uuid) && MUSEUM_ITEM_CACHE.get(uuid).containsKey(profileId)) { ObjectOpenHashSet items = MUSEUM_ITEM_CACHE.get(uuid).get(profileId).collectedItemIds(); for (Donation donation : MUSEUM_DONATIONS) { - // Check if the donation id is not present in the collected items - if (!items.contains(donation.getId()) && items.stream().noneMatch(donation.getUpgrades()::contains)) { - donation.initPriceData(); - uncontributedItems.add(donation); + // Check if the donation id or his upgrades is not present in the collected items + //FIXME too many stream checks + //TODO: cache + if (!items.contains(MAPPED_IDS.entrySet().stream().filter(entry -> donation.getId().equals(entry.getValue())).map(Map.Entry::getKey).findFirst().orElse(null))) { + if (!items.contains(donation.getId()) && items.stream().noneMatch(donation.getUpgrades()::contains)) { + if (donation.isSet()) { + if (items.stream().anyMatch(i -> donation.getSet().stream().anyMatch(p -> p.getLeft().equals(i)))) continue; + if (items.stream().anyMatch(p -> donation.getUpgrades().stream().anyMatch(upgrade -> MuseumUtils.getPiecesBySetID(upgrade).contains(p)))) continue; + } + donation.setPriceData(); + uncontributedItems.add(donation); + } } } + + // Check if the item has a donated downgrade + uncontributedItems.forEach(donation -> donation.setDiscount(donation.getDowngrades().stream() + .filter(downgrade -> donation.isCraftable()) + .filter(downgrade -> uncontributedItems.stream().noneMatch(item -> item.getId().equals(downgrade))) + .map(downgrade -> new Pair<>( + downgrade, MuseumUtils.getSetCraftCost(downgrade))) + .findFirst() + .orElse(null))); } - uncontributedItems.sort(Comparator.comparing(Donation::getId)); + uncontributedItems.sort(Comparator.comparing(Donation::getId)); //Sorting alphabetically return uncontributedItems; } @@ -227,12 +269,14 @@ public static void handleClick(Slot slot, int slotId, DefaultedList slots) if (!itemId.isEmpty() && !profileId.isEmpty()) { if (MAPPED_IDS.containsKey(itemId)) itemId = MAPPED_IDS.get(itemId); + String setId = MuseumUtils.getSetID(itemId); String uuid = Utils.getUndashedUuid(); //Be safe about access to avoid NPEs Map playerData = MUSEUM_ITEM_CACHE.computeIfAbsent(uuid, _uuid -> new Object2ObjectOpenHashMap<>()); playerData.putIfAbsent(profileId, ProfileMuseumData.EMPTY.get()); playerData.get(profileId).collectedItemIds().add(itemId); + if (setId != null) playerData.get(profileId).collectedItemIds().add(setId); } } } @@ -259,7 +303,21 @@ private static void updateData4ProfileMember(String uuid, String profileId, Fabr //Set of all found item ids on profile ObjectOpenHashSet itemIds = new ObjectOpenHashSet<>(); - donatedSets.forEach((s, jsonElement) -> itemIds.add(s)); + for (Map.Entry donatedSet : donatedSets.entrySet()) { + //Item is plural here because the nbt is a list + String itemsData = donatedSet.getValue().getAsJsonObject().get("items").getAsJsonObject().get("data").getAsString(); + NbtList items = NbtIo.readCompressed(new ByteArrayInputStream(Base64.getDecoder().decode(itemsData)), NbtSizeTracker.ofUnlimitedBytes()).getList("i", NbtElement.COMPOUND_TYPE); + + for (int i = 0; i < items.size(); i++) { + NbtCompound tag = items.getCompound(i).getCompound("tag"); + + if (tag.contains("ExtraAttributes")) { + NbtCompound extraAttributes = tag.getCompound("ExtraAttributes"); + + if (extraAttributes.contains("id")) itemIds.add(extraAttributes.getString("id")); + } + } + } MUSEUM_ITEM_CACHE.get(uuid).put(profileId, new ProfileMuseumData(System.currentTimeMillis(), itemIds)); save(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java index b6ec744205..ffb3811a2b 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java @@ -1,11 +1,10 @@ package de.hysky.skyblocker.skyblock.museum; import com.google.common.collect.Lists; -import de.hysky.skyblocker.mixins.accessors.ScreenAccessor; -import de.hysky.skyblocker.skyblock.item.MuseumItemCache; import de.hysky.skyblocker.skyblock.item.WikiLookup; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.utils.ItemUtils; +import net.fabricmc.fabric.api.client.screen.v1.Screens; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; @@ -22,16 +21,17 @@ import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; +import net.minecraft.util.Pair; import org.lwjgl.glfw.GLFW; import java.util.ArrayList; import java.util.List; +import java.util.Locale; public class MuseumManager extends ClickableWidget { private static final Identifier BACKGROUND_TEXTURE = Identifier.ofVanilla("textures/gui/recipe_book.png"); private static final int BACKGROUND_WIDTH = 147; private static final int BACKGROUND_HEIGHT = 166; - private static final int SEARCH_FIELD_WIDTH = 69; private static final int SEARCH_FIELD_HEIGHT = 20; private static final int BUTTON_SIZE = 20; @@ -47,7 +47,7 @@ public class MuseumManager extends ClickableWidget { private final ToggleButtonWidget nextPageButton; private final ToggleButtonWidget prevPageButton; private final TextFieldWidget searchField; - private final List allDonations; + private static List donations = new ArrayList<>(); private final List filteredDonations = new ArrayList<>(); private final List excludedDonationIds = new ArrayList<>(); private final List donationButtons = Lists.newArrayListWithCapacity(BUTTONS_PER_PAGE); @@ -57,7 +57,7 @@ public class MuseumManager extends ClickableWidget { private int pageCount = 0; public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { - super(x, y, screen.width, screen.height, Text.of("")); + super(x, y, screen.width, screen.height, Text.empty()); this.layoutX = getX() + backgroundWidth + 2; this.layoutY = y; @@ -75,7 +75,7 @@ public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { this.prevPageButton = new ToggleButtonWidget(layoutX + 38, layoutY + 133, 12, 17, true); this.prevPageButton.setTextures(RecipeBookResults.PAGE_BACKWARD_TEXTURES); - this.allDonations = MuseumItemCache.getDonations(); + donations = MuseumItemCache.getDonations(); // Create donation buttons for pagination for (int i = 0; i < BUTTONS_PER_PAGE; i++) { @@ -84,7 +84,7 @@ public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { } // Initialize sort button - this.sortButton = ButtonWidget.builder(Text.literal(""), button -> { + this.sortButton = ButtonWidget.builder(Text.empty(), button -> { ITEM_SORTER.cycleSortMode(filteredDonations); button.setTooltip(ITEM_SORTER.getTooltip()); CURRENT_PAGE = 0; @@ -96,8 +96,8 @@ public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { .build(); // Initialize filter button - this.filterButton = ButtonWidget.builder(Text.literal(""), button -> { - ITEM_FILTER.cycleFilterMode(allDonations, filteredDonations); + this.filterButton = ButtonWidget.builder(Text.empty(), button -> { + ITEM_FILTER.cycleFilterMode(donations, filteredDonations); ITEM_SORTER.applySort(filteredDonations); button.setTooltip(ITEM_FILTER.getTooltip()); CURRENT_PAGE = 0; @@ -108,14 +108,27 @@ public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { .size(BUTTON_SIZE, BUTTON_SIZE) .build(); - ITEM_FILTER.applyFilter(allDonations, filteredDonations); + ITEM_FILTER.applyFilter(donations, filteredDonations); ITEM_SORTER.applySort(filteredDonations); updateSearchResults(false); - ((ScreenAccessor) screen).callAddDrawableChild(this); + Screens.getButtons(screen).add(this); screen.setFocused(this); } + /** + * Retrieves the Donation object corresponding to a given ID. + * + * @param id the ID of the donation to retrieve + * @return the Donation object associated with the given ID, or null if not found + */ + protected static Donation getDonation(String id) { + return donations.stream() + .filter(donation -> donation.getId().equals(id)) + .findFirst() + .orElse(null); + } + /** * Resets the UI state including search text, current page, sorting, and filtering. */ @@ -163,8 +176,9 @@ private void updateButtons() { * @param resetPage Whether to reset to the first page. */ public void updateSearchResults(boolean resetPage) { + SEARCH_QUERY = this.searchField.getText(); excludedDonationIds.clear(); - for (Donation item : allDonations) { + for (Donation item : donations) { StringBuilder searchableContent = new StringBuilder(); ItemStack itemStack = ItemRepository.getItemStack(item.getId()); if (itemStack != null) { @@ -172,13 +186,13 @@ public void updateSearchResults(boolean resetPage) { .append(ItemUtils.getConcatenatedLore(itemStack)); } if (item.getSet() != null && !item.getSet().isEmpty()) { - for (ArmorPiece piece : item.getSet()) { - ItemStack pieceStack = ItemRepository.getItemStack(piece.getId()); + for (Pair piece : item.getSet()) { + ItemStack pieceStack = ItemRepository.getItemStack(piece.getLeft()); if (pieceStack != null) searchableContent.append(pieceStack.getName().getString()) .append(ItemUtils.getConcatenatedLore(pieceStack)); } } - if (!searchableContent.toString().toLowerCase().contains(SEARCH_QUERY.toLowerCase())) { + if (!searchableContent.toString().toLowerCase(Locale.ENGLISH).contains(SEARCH_QUERY.toLowerCase(Locale.ENGLISH))) { excludedDonationIds.add(item.getId()); } } @@ -212,6 +226,7 @@ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float d int iconY = this.sortButton.getY() + (this.sortButton.getHeight() - 16) / 2; ItemStack stack = ITEM_SORTER.getCurrentSortingItem(); context.drawItemWithoutEntity(stack, iconX, iconY); + this.sortButton.render(context, mouseX, mouseY, delta); } if (this.filterButton.active) { @@ -219,14 +234,13 @@ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float d int iconY = this.filterButton.getY() + (this.filterButton.getHeight() - 16) / 2; ItemStack stack = ITEM_FILTER.getCurrentFilterItem(); context.drawItemWithoutEntity(stack, iconX, iconY); + this.filterButton.render(context, mouseX, mouseY, delta); } // Render the page flip buttons if (this.prevPageButton.active) this.prevPageButton.render(context, mouseX, mouseY, delta); if (this.nextPageButton.active) this.nextPageButton.render(context, mouseX, mouseY, delta); - this.filterButton.render(context, mouseX, mouseY, delta); - this.sortButton.render(context, mouseX, mouseY, delta); this.searchField.render(context, mouseX, mouseY, delta); this.drawTooltip(context, mouseX, mouseY); @@ -275,8 +289,7 @@ public boolean keyReleased(int keyCode, int scanCode, int modifiers) { @Override public boolean charTyped(char chr, int modifiers) { if (this.searchField.charTyped(chr, modifiers)) { - SEARCH_QUERY = this.searchField.getText(); - this.updateSearchResults(true); + updateSearchResults(true); return true; } return false; @@ -286,10 +299,9 @@ public boolean charTyped(char chr, int modifiers) { public boolean keyPressed(int keyCode, int scanCode, int modifiers) { if ((this.searchField.isActive() && keyCode == GLFW.GLFW_KEY_E) || this.searchField.keyPressed(keyCode, scanCode, modifiers)) { - SEARCH_QUERY = this.searchField.getText(); - this.updateSearchResults(true); + updateSearchResults(true); return true; - } else if (WikiLookup.wikiLookup.matchesKey(keyCode, scanCode) && CLIENT.player != null && hoveredDonationButton != null && hoveredDonationButton.getDisplayStack() != null) { + } else if (WikiLookup.wikiLookup.matchesKey(keyCode, scanCode) && hoveredDonationButton != null && hoveredDonationButton.getDisplayStack() != null) { WikiLookup.openWiki(hoveredDonationButton.getDisplayStack(), CLIENT.player); return true; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java new file mode 100644 index 0000000000..8f4f4dbd1e --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java @@ -0,0 +1,137 @@ +package de.hysky.skyblocker.skyblock.museum; + +import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Pair; + +import java.text.NumberFormat; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.stream.Collectors; + +public class MuseumUtils { + + /** + * Calculates the total crafting cost for a set associated with a given ID. + * + * @param id the ID of the set for which the crafting cost is calculated + * @return the total crafting cost of the set + */ + protected static double getSetCraftCost(String id) { + double cost = 0; + for (Donation donation : MuseumItemCache.MUSEUM_DONATIONS) { + if (donation.getId().equals(id)) { + for (Pair piece : donation.getSet()){ + cost += ItemUtils.getCraftCost(piece.getLeft()); + } + } + } + return cost; + } + + /** + * Retrieves the display name for an item or a set. + * If the item is part of a set, it returns the set's name like "Divan's armor". + * + * @param id the ID of the item or set + * @param isSet true if the ID refers to a set, false if it refers to an individual item + * @return the display name of the item or set + */ + protected static Text getDisplayName(String id, boolean isSet) { + if (isSet) { + Style nameStyle = Style.EMPTY; + String setName = MuseumItemCache.ARMOR_NAMES.get(id); + if (setName != null) { + Optional donation = MuseumItemCache.MUSEUM_DONATIONS.stream().filter(d -> d.getId().equals(id)).findFirst(); + if (donation.isPresent()) { + if (!donation.get().getSet().isEmpty()) { + Text pieceName = getDisplayName(donation.get().getSet().getFirst().getLeft(), false); + if (pieceName != null) { + nameStyle = pieceName.getSiblings().getFirst().getStyle(); + } + } + } + return Text.literal(setName).setStyle(nameStyle); + } + } else { + ItemStack stack = ItemRepository.getItemStack(id); + if (stack != null) { + return stack.getName(); + } + } + return Text.literal(id); + } + + /** + * Retrieves the set ID for a given piece ID. + * + * @param id the piece ID to search for + * @return the ID of the set that the piece belongs to, or null if not found + */ + protected static String getSetID(String id) { + for (Donation donation : MuseumItemCache.MUSEUM_DONATIONS) { + for (Pair set : donation.getSet()) { + if (set.getLeft().equals(id)) { + return donation.getId(); + } + } + } + return null; + } + + protected static List getPiecesBySetID(String donationId) { + return MuseumItemCache.MUSEUM_DONATIONS.stream() + .filter(d -> d.getId().equals(donationId)) + .map(Donation::getSet) + .flatMap(List::stream) + .map(Pair::getLeft) + .collect(Collectors.toList()); + } + + /** + * Formats an armor item ID into a readable name + */ + public static String formatArmorName(String id, boolean isEquipment) { + String lowercaseKey = id.toLowerCase(Locale.ENGLISH); + + // Step 2: Replace "_" with space + String withSpaces = lowercaseKey.replace("_", " "); + + // Step 3: Capitalize the first letter of each word + String[] words = withSpaces.split(" "); + StringBuilder formattedName = new StringBuilder(); + for (String word : words) { + // Uppercase first letter, lowercase the rest + formattedName.append(Character.toUpperCase(word.charAt(0))) + .append(word.substring(1)) + .append(" "); + } + + if (isEquipment) { + formattedName.append("Equipment"); + } else if (!lowercaseKey.contains("armor") && + !lowercaseKey.contains("outfit") && + !lowercaseKey.contains("suit") && + !lowercaseKey.contains("tuxedo")) { + formattedName.append("Armor"); + } + + return formattedName.toString().trim(); + } + + /** + * Formats a double value into a shortened human-readable format. + * + * @param value The number to format. + * @return A formatted string (e.g., "10M", "5K", "1.2B"). + */ + public static String formatPrice(double value) { + NumberFormat NUMBER_FORMATTER_S = NumberFormat.getCompactNumberInstance(Locale.CANADA, NumberFormat.Style.SHORT); + NUMBER_FORMATTER_S.setMaximumFractionDigits(1); + return NUMBER_FORMATTER_S.format(value); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/PriceData.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/PriceData.java new file mode 100644 index 0000000000..2b9489f696 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/PriceData.java @@ -0,0 +1,48 @@ +package de.hysky.skyblocker.skyblock.museum; + +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.util.Pair; + +public class PriceData { + private double lBinPrice = 0; + private double craftCost = 0; + private double effectivePrice; + + public PriceData(double lBinPrice, double craftCost) { + this.lBinPrice = lBinPrice; + this.craftCost = craftCost; + } + + public PriceData(Donation donation) { + if (donation.isSet()) { + for (Pair piece : donation.getSet()) { + double lBinPrice = ItemUtils.getItemPrice(piece.getLeft()).leftDouble(); + double craftCost = ItemUtils.getCraftCost(piece.getLeft()); + + this.lBinPrice += lBinPrice; + this.craftCost += craftCost; + + piece.setRight(new PriceData(lBinPrice, craftCost)); + } + } else { + this.lBinPrice = ItemUtils.getItemPrice(donation.getId()).leftDouble(); + this.craftCost = ItemUtils.getCraftCost(donation.getId()); + } + } + + public double getLBinPrice() { + return lBinPrice; + } + + public double getCraftCost() { + return craftCost; + } + + public double getEffectivePrice() { + return effectivePrice; + } + + public void setEffectivePrice(double effectivePrice) { + this.effectivePrice = effectivePrice; + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java index 9368bd5b12..34874f4aa7 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java @@ -10,10 +10,12 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.skyblock.PetCache; +import de.hysky.skyblocker.skyblock.item.tooltip.adders.CraftPriceTooltip; import de.hysky.skyblocker.skyblock.item.tooltip.adders.ObtainedDateTooltip; import de.hysky.skyblocker.skyblock.item.tooltip.info.TooltipInfoType; import de.hysky.skyblocker.utils.datafixer.ItemStackComponentizationFixer; import de.hysky.skyblocker.utils.networth.NetworthCalculator; +import io.github.moulberry.repo.data.NEUItem; import it.unimi.dsi.fastutil.doubles.DoubleBooleanPair; import it.unimi.dsi.fastutil.ints.IntIntPair; import it.unimi.dsi.fastutil.longs.LongBooleanPair; @@ -300,6 +302,14 @@ public static LiteralArgumentBuilder dumpHeldItemNetw return DoubleBooleanPair.of(0, false); } + public static double getCraftCost(String id) { + NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(id); + if (neuItem != null && !neuItem.getRecipes().isEmpty()) { + return CraftPriceTooltip.getItemCost(neuItem.getRecipes().getFirst(), 0); + } + return 0; + } + /** * 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 @@ -466,4 +476,8 @@ public static boolean isEquipment(String itemId) { itemId.contains("BRACELET") || itemId.contains("HAT")); } + + public static boolean isSoulbound(ItemStack stack) { + return getLore(stack).stream().anyMatch(lore -> lore.getString().toLowerCase(Locale.ENGLISH).contains("soulbound")); + } } diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java index 5c5d7d3842..b14e71211d 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Utils.java +++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java @@ -6,7 +6,7 @@ import de.hysky.skyblocker.annotations.Init; 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.museum.MuseumItemCache; import de.hysky.skyblocker.utils.scheduler.MessageScheduler; import de.hysky.skyblocker.utils.scheduler.Scheduler; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 38494011f6..c097418394 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -762,8 +762,8 @@ "skyblocker.config.uiAndVisuals.itemCooldown": "Item Cooldown", "skyblocker.config.uiAndVisuals.itemCooldown.enableItemCooldowns": "Enable Item Cooldown", - "skyblocker.config.uiAndVisuals.museumOverlay": "Museum Hud", - "skyblocker.config.uiAndVisuals.museumOverlay.@Tooltip": "Shows uncontributed items including their prices and more.\n\nMake sure to enable your Museum API to work", + "skyblocker.config.uiAndVisuals.museumOverlay": "Museum Overlay", + "skyblocker.config.uiAndVisuals.museumOverlay.@Tooltip": "Shows undonated items including their prices and more.\n\nMake sure to enable your Museum API to work!", "skyblocker.config.uiAndVisuals.searchOverlay": "Search Overlay", "skyblocker.config.uiAndVisuals.searchOverlay.enableAuctionHouse": "Enable For Auction House", From b775afb3a57f90184093b19e3ecbc988eb4dc188 Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Fri, 6 Dec 2024 17:52:22 +0100 Subject: [PATCH 03/17] added translatable texts --- .../skyblock/museum/DonationButton.java | 26 +++++++++---------- .../skyblock/museum/ItemFilter.java | 20 +++++++------- .../skyblock/museum/ItemSorter.java | 18 ++++++------- .../skyblock/museum/MuseumManager.java | 4 +-- .../assets/skyblocker/lang/en_us.json | 23 ++++++++++++++++ 5 files changed, 57 insertions(+), 34 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java index 6f21cf44ee..dcda39f74e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java @@ -120,33 +120,33 @@ private void createTooltip() { } else if (piece.getRight().getEffectivePrice() > 0) { tooltip.add(Text.literal(" ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.literal(MuseumUtils.formatPrice(piece.getRight().getEffectivePrice())).formatted(Formatting.GOLD)).append(Text.literal(")").formatted(Formatting.DARK_GRAY))); } else { - tooltip.add(Text.literal(" ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.literal("Unknown").formatted(Formatting.RED)).append(Text.literal(")").formatted(Formatting.DARK_GRAY))); + tooltip.add(Text.literal(" ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.translatable("skyblocker.museum.hud.unknownPrice").formatted(Formatting.RED)).append(Text.literal(")").formatted(Formatting.DARK_GRAY))); } } } } tooltip.add(Text.empty()); - Text xpText = Text.literal(String.valueOf(donation.getTotalXp())).append(" SkyBlock XP").formatted(Formatting.AQUA); - tooltip.add(Text.literal("Reward: ").formatted(Formatting.GRAY).append(xpText)); + Text xpText = Text.literal(String.valueOf(donation.getTotalXp())).append(" ").append(Text.translatable("skyblocker.museum.hud.skyblockXp")).formatted(Formatting.AQUA); + tooltip.add(Text.translatable("skyblocker.museum.hud.xpReward").append(": ").formatted(Formatting.GRAY).append(xpText)); if (soulbound) { - tooltip.add(Text.literal("Untradable").formatted(Formatting.GRAY).append(Text.literal(" (Soulbound)").formatted(Formatting.DARK_GRAY))); + tooltip.add(Text.translatable("skyblocker.museum.hud.untradeableItem").formatted(Formatting.GRAY).append(Text.literal(" (").append(Text.translatable("skyblocker.museum.hud.soulboundItem")).append(")").formatted(Formatting.DARK_GRAY))); } else { PriceData priceData = donation.getPriceData(); - Text lBinText = donation.hasLBinPrice() ? Text.literal(MuseumUtils.formatPrice(priceData.getLBinPrice())).append(" Coins").formatted(Formatting.GOLD) : Text.literal("Unknown").formatted(Formatting.RED); - MutableText craftCostText = donation.isCraftable() ? Text.literal(MuseumUtils.formatPrice(donation.hasDiscount() ? priceData.getCraftCost() - discount.getRight() : priceData.getCraftCost())).append(" Coins").formatted(Formatting.GOLD) : Text.literal("Unknown").formatted(Formatting.RED); + Text lBinText = donation.hasLBinPrice() ? Text.literal(MuseumUtils.formatPrice(priceData.getLBinPrice())).append(" Coins").formatted(Formatting.GOLD) : Text.translatable("skyblocker.museum.hud.unknownPrice").formatted(Formatting.RED); + MutableText craftCostText = donation.isCraftable() ? Text.literal(MuseumUtils.formatPrice(donation.hasDiscount() ? priceData.getCraftCost() - discount.getRight() : priceData.getCraftCost())).append(" ").append(Text.translatable("skyblocker.museum.hud.coin")).formatted(Formatting.GOLD) : Text.translatable("skyblocker.museum.hud.unknownPrice").formatted(Formatting.RED); Text discountText = donation.hasDiscount() && donation.isCraftable() ? Text.literal(" (").formatted(Formatting.DARK_GRAY).append(Text.literal(MuseumUtils.formatPrice(priceData.getCraftCost())).formatted(Formatting.GOLD)).append(" - ").append(Text.literal(MuseumUtils.formatPrice(discount.getRight())).formatted(Formatting.GOLD)).append(Text.literal(")").formatted(Formatting.DARK_GRAY)) : Text.empty(); - Text xpCoinsRatio = Text.literal(MuseumUtils.formatPrice(donation.getXpCoinsRatio())).append(" Coins per XP").formatted(Formatting.AQUA); + Text xpCoinsRatio = Text.literal(MuseumUtils.formatPrice(donation.getXpCoinsRatio())).append(" ").append(Text.translatable("skyblocker.museum.hud.ratioText")).formatted(Formatting.AQUA); - tooltip.add(Text.literal("Lowest BIN: ").formatted(Formatting.GRAY).append(lBinText)); - tooltip.add(Text.literal("Craft Cost: ").formatted(Formatting.GRAY).append(craftCostText).append(discountText)); - tooltip.add(Text.literal("Coins/XP ratio: ").formatted(Formatting.GRAY).append(xpCoinsRatio)); + tooltip.add(Text.translatable("skyblocker.museum.hud.sorter.lBin").append(": ").formatted(Formatting.GRAY).append(lBinText)); + tooltip.add(Text.translatable("skyblocker.museum.hud.sorter.craftCost").append(": ").formatted(Formatting.GRAY).append(craftCostText).append(discountText)); + tooltip.add(Text.translatable("skyblocker.museum.hud.sorter.ratio").append(": ").formatted(Formatting.GRAY).append(xpCoinsRatio)); } if (countsTowards.size() > 1) { tooltip.add(Text.empty()); - tooltip.add(Text.literal("Will count for:").formatted(Formatting.GRAY)); + tooltip.add(Text.translatable("skyblocker.museum.hud.countsFor").append(":").formatted(Formatting.GRAY)); for (Pair credit : countsTowards) { tooltip.add(Text.literal(" ● ").formatted(Formatting.GRAY).append(MuseumUtils.getDisplayName(credit.getLeft(), donation.isSet())).append(Text.literal(" (" + credit.getRight() + " XP)").formatted(Formatting.AQUA))); } @@ -154,12 +154,12 @@ private void createTooltip() { if (donation.isCraftable() && donation.hasDiscount()) { tooltip.add(Text.empty()); - tooltip.add(Text.literal("Crafted with: ").formatted(Formatting.GRAY).append(Text.literal("(Donated Item)").formatted(Formatting.DARK_GRAY))); + tooltip.add(Text.translatable("skyblocker.museum.hud.craftIngredient").append(": ").formatted(Formatting.GRAY).append(Text.literal("(").append(Text.translatable("skyblocker.museum.hud.alreadyDonatedItem").append(")")).formatted(Formatting.DARK_GRAY))); tooltip.add(Text.literal(" - ").formatted(Formatting.GRAY).append(MuseumUtils.getDisplayName(discount.getLeft(), donation.isSet())).append(Text.literal(" ✔").formatted(Formatting.GREEN)).append(Text.literal(" (").formatted(Formatting.DARK_GRAY).append(Text.literal(MuseumUtils.formatPrice(discount.getRight())).formatted(Formatting.GOLD)).append(")").formatted(Formatting.DARK_GRAY))); } tooltip.add(Text.empty()); - tooltip.add(Text.literal("Click on " + WIKI_LOCKUP_KEY.substring(WIKI_LOCKUP_KEY.lastIndexOf('.') + 1).toUpperCase(Locale.ENGLISH) + " to open the wiki page!").formatted(Formatting.YELLOW)); + tooltip.add(Text.translatable("skyblocker.museum.hud.wikiLookup", WIKI_LOCKUP_KEY.substring(WIKI_LOCKUP_KEY.lastIndexOf('.') + 1).toUpperCase(Locale.ENGLISH)).formatted(Formatting.YELLOW)); this.tooltip = tooltip; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java index 48e2e0f73a..a89b5eda05 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java @@ -59,32 +59,32 @@ public void resetFilter() { } public Tooltip getTooltip() { - Text tooltip = Text.literal("Item Filter\n\n").formatted(Formatting.GREEN) + Text tooltip = Text.translatable("skyblocker.museum.hud.filter").append("\n\n").formatted(Formatting.GREEN) .append(getFilterText(FilterMode.ALL)) .append(getFilterText(FilterMode.WEAPONS)) .append(getFilterText(FilterMode.ARMOR)) .append(getFilterText(FilterMode.RARITIES)) - .append(Text.literal("\nClick to switch!").formatted(Formatting.YELLOW)); + .append("\n").append(Text.translatable("skyblocker.museum.hud.filter.switch").formatted(Formatting.YELLOW)); return Tooltip.of(tooltip); } private Text getFilterText(FilterMode mode) { boolean isCurrent = mode == currentFilterMode; - return Text.literal((isCurrent ? "➤ " : " ") + mode.getDisplayName() + "\n") + return Text.literal((isCurrent ? "➤ " : " ")).append(mode.getDisplayName()).append("\n") .formatted(isCurrent ? Formatting.AQUA : Formatting.GRAY); } public enum FilterMode { - ALL(new ItemStack(Items.NETHER_STAR), ItemFilter::filterAll, "All"), - WEAPONS(new ItemStack(Items.DIAMOND_SWORD), ItemFilter::filterWeapons, "Weapons"), - ARMOR(new ItemStack(Items.DIAMOND_CHESTPLATE), ItemFilter::filterArmor, "Armor"), - RARITIES(ItemRepository.getItemStack("JADERALD"), ItemFilter::filterRarities, "Rarities"); + ALL(new ItemStack(Items.NETHER_STAR), ItemFilter::filterAll, Text.translatable("skyblocker.museum.hud.filter.all")), + WEAPONS(new ItemStack(Items.DIAMOND_SWORD), ItemFilter::filterWeapons, Text.translatable("skyblocker.museum.hud.filter.weapons")), + ARMOR(new ItemStack(Items.DIAMOND_CHESTPLATE), ItemFilter::filterArmor, Text.translatable("skyblocker.museum.hud.filter.armor")), + RARITIES(ItemRepository.getItemStack("JADERALD"), ItemFilter::filterRarities, Text.translatable("skyblocker.museum.hud.filter.rarities")); private final ItemStack associatedItem; private final UnaryOperator> filterFunction; - private final String displayName; + private final Text displayName; - FilterMode(ItemStack item, UnaryOperator> function, String displayName) { + FilterMode(ItemStack item, UnaryOperator> function, Text displayName) { this.associatedItem = item; this.filterFunction = function; this.displayName = displayName; @@ -94,7 +94,7 @@ public ItemStack getAssociatedItem() { return associatedItem; } - public String getDisplayName() { + public Text getDisplayName() { return displayName; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java index 17c0292b8e..dcce6a19e4 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java @@ -114,30 +114,30 @@ public void resetSorting() { } public Tooltip getTooltip() { - Text tooltip = Text.literal("Item Sort\n\n").formatted(Formatting.GREEN) + Text tooltip = Text.translatable("skyblocker.museum.hud.sorter").append("\n\n").formatted(Formatting.GREEN) .append(getSortText(SortMode.LowestBIN)) .append(getSortText(SortMode.CraftCost)) .append(getSortText(SortMode.CoinsPerXP)) - .append(Text.literal("\nClick to switch sort!").formatted(Formatting.YELLOW)); + .append("\n").append(Text.translatable("skyblocker.museum.hud.sorter.switch").formatted(Formatting.YELLOW)); return Tooltip.of(tooltip); } private Text getSortText(SortMode mode) { boolean isCurrent = mode == currentSortMode; - return Text.literal((isCurrent ? "➤ " : " ") + mode.getDisplayName() + "\n") + return Text.literal((isCurrent ? "➤ " : " ")).append(mode.getDisplayName()).append("\n") .formatted(isCurrent ? Formatting.AQUA : Formatting.GRAY); } public enum SortMode { - LowestBIN(new ItemStack(Items.GOLD_INGOT), sortByLowestBIN, "Lowest BIN"), - CraftCost(new ItemStack(Items.CRAFTING_TABLE), sortByCraftCost, "Craft Cost"), - CoinsPerXP(new ItemStack(Items.EXPERIENCE_BOTTLE), sortByXpPerCoin, "Coins/XP Ratio"); + LowestBIN(new ItemStack(Items.GOLD_INGOT), sortByLowestBIN, Text.translatable("skyblocker.museum.hud.sorter.lBin")), + CraftCost(new ItemStack(Items.CRAFTING_TABLE), sortByCraftCost, Text.translatable("skyblocker.museum.hud.sorter.craftCost")), + CoinsPerXP(new ItemStack(Items.EXPERIENCE_BOTTLE), sortByXpPerCoin, Text.translatable("skyblocker.museum.hud.sorter.ratio")); private final ItemStack associatedItem; private final Consumer> sortFunction; - private final String displayName; + private final Text displayName; - SortMode(ItemStack item, Consumer> function, String displayName) { + SortMode(ItemStack item, Consumer> function, Text displayName) { this.associatedItem = item; this.sortFunction = function; this.displayName = displayName; @@ -147,7 +147,7 @@ public ItemStack getAssociatedItem() { return associatedItem; } - public String getDisplayName() { + public Text getDisplayName() { return displayName; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java index ffb3811a2b..cd4630c45a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java @@ -62,12 +62,12 @@ public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { this.layoutY = y; // Initialize search field - this.searchField = new TextFieldWidget(TEXT_RENDERER, layoutX + 25, layoutY + 11, SEARCH_FIELD_WIDTH, SEARCH_FIELD_HEIGHT, Text.of("Search")); + this.searchField = new TextFieldWidget(TEXT_RENDERER, layoutX + 25, layoutY + 11, SEARCH_FIELD_WIDTH, SEARCH_FIELD_HEIGHT, Text.empty()); this.searchField.setMaxLength(60); this.searchField.setVisible(true); this.searchField.setEditableColor(0xFFFFFF); this.searchField.setText(SEARCH_QUERY); - this.searchField.setPlaceholder(Text.literal("Search...").formatted(Formatting.ITALIC).formatted(Formatting.GRAY)); + this.searchField.setPlaceholder(Text.translatable("gui.recipebook.search_hint").formatted(Formatting.ITALIC).formatted(Formatting.GRAY)); // Initialize page navigation buttons this.nextPageButton = new ToggleButtonWidget(layoutX + 93, layoutY + 133, 12, 17, false); diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 1e679ffb62..fd8015acfb 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -882,6 +882,29 @@ "skyblocker.updateRepository.failed": "§cUpdating the local repository failed. See logs for details.", "skyblocker.updateRepository.error": "§cEncountered an error while deleting and updating the local repository. Try again in a moment or remove the files manually and restart the game. See logs for details.", + "skyblocker.museum.hud.alreadyDonatedItem": "Donated Item", + "skyblocker.museum.hud.coin": "Coins", + "skyblocker.museum.hud.craftIngredient": "Crafted with", + "skyblocker.museum.hud.countsFor": "Will count for", + "skyblocker.museum.hud.filter": "Item Filter", + "skyblocker.museum.hud.filter.all": "All", + "skyblocker.museum.hud.filter.armor": "Armor", + "skyblocker.museum.hud.filter.rarities": "Rarities", + "skyblocker.museum.hud.filter.switch": "Click to switch filter!", + "skyblocker.museum.hud.filter.weapons": "Weapons", + "skyblocker.museum.hud.ratioText": "Coins per XP", + "skyblocker.museum.hud.skyblockXp": "SkyBlock XP", + "skyblocker.museum.hud.sorter": "Item Sort", + "skyblocker.museum.hud.sorter.craftCost": "Craft Cost", + "skyblocker.museum.hud.sorter.lBin": "Lowest BIN", + "skyblocker.museum.hud.sorter.ratio": "Coins/XP Ratio", + "skyblocker.museum.hud.sorter.switch": "Click to switch sort!", + "skyblocker.museum.hud.soulboundItem": "Soulbound", + "skyblocker.museum.hud.untradeableItem": "Untradable", + "skyblocker.museum.hud.unknownPrice": "Unknown", + "skyblocker.museum.hud.wikiLookup": "Click on %s to open the wiki page!", + "skyblocker.museum.hud.xpReward": "Reward", + "skyblocker.museum.attemptingResync": "Attempting to resync your museum item donations...", "skyblocker.museum.cannotResync": "Cannot resync your museum item donations! Note that you can only do this once every two days.", "skyblocker.museum.resyncSuccess": "Successfully resynced your museum item donations!", From 96834edb637d02b415e21abd2fe7e12172dcd747 Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Sat, 7 Dec 2024 18:17:52 +0100 Subject: [PATCH 04/17] caching improvements --- .../skyblock/museum/MuseumItemCache.java | 61 +++++++------------ 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java index 8a4ce64932..bc4007dcd5 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java @@ -23,7 +23,6 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.command.CommandRegistryAccess; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.*; import net.minecraft.screen.slot.Slot; import net.minecraft.text.Text; import net.minecraft.util.Pair; @@ -33,7 +32,6 @@ import java.io.BufferedReader; import java.io.BufferedWriter; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; @@ -81,12 +79,12 @@ private static void registerCommands(CommandDispatcher { + loadMuseumItems(); try (BufferedReader reader = Files.newBufferedReader(CACHE_FILE)) { Map> cachedData = ProfileMuseumData.SERIALIZATION_CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).getOrThrow(); MUSEUM_ITEM_CACHE.putAll(cachedData); LOGGER.info("[Skyblocker] Loaded museum items cache"); - loadMuseumItems(); } catch (NoSuchFileException ignored) { } catch (IOException e) { LOGGER.error("[Skyblocker] Failed to load cached museum items", e); @@ -108,8 +106,6 @@ private static void save() { * Loads museum data from local repo. */ public static void loadMuseumItems() { - MUSEUM_DONATIONS.clear(); - try (BufferedReader reader = Files.newBufferedReader(MUSEUM_INFO)) { // Parse the JSON file JsonObject json = JsonParser.parseReader(reader).getAsJsonObject(); @@ -184,7 +180,8 @@ public static void loadMuseumItems() { MUSEUM_DONATIONS.forEach(Donation::setDowngrades); LOGGER.info("[Skyblocker] Loaded museum data"); - } catch (Exception e) { + } catch (NoSuchFileException ignored) { + } catch (IOException e) { LOGGER.error("[Skyblocker] Failed to load donations data", e); } } @@ -229,28 +226,16 @@ public static List getDonations() { ObjectOpenHashSet items = MUSEUM_ITEM_CACHE.get(uuid).get(profileId).collectedItemIds(); for (Donation donation : MUSEUM_DONATIONS) { // Check if the donation id or his upgrades is not present in the collected items - //FIXME too many stream checks - //TODO: cache - if (!items.contains(MAPPED_IDS.entrySet().stream().filter(entry -> donation.getId().equals(entry.getValue())).map(Map.Entry::getKey).findFirst().orElse(null))) { - if (!items.contains(donation.getId()) && items.stream().noneMatch(donation.getUpgrades()::contains)) { - if (donation.isSet()) { - if (items.stream().anyMatch(i -> donation.getSet().stream().anyMatch(p -> p.getLeft().equals(i)))) continue; - if (items.stream().anyMatch(p -> donation.getUpgrades().stream().anyMatch(upgrade -> MuseumUtils.getPiecesBySetID(upgrade).contains(p)))) continue; - } - donation.setPriceData(); - uncontributedItems.add(donation); + if (!items.contains(donation.getId())) { + if (donation.isSet()) { + if (items.stream().anyMatch(i -> donation.getSet().stream().anyMatch(p -> p.getLeft().equals(i)))) continue; + //if (items.stream().anyMatch(p -> donation.getUpgrades().stream().anyMatch(upgrade -> MuseumUtils.getPiecesBySetID(upgrade).contains(p)))) continue; } + donation.setPriceData(); + uncontributedItems.add(donation); } - } - // Check if the item has a donated downgrade - uncontributedItems.forEach(donation -> donation.setDiscount(donation.getDowngrades().stream() - .filter(downgrade -> donation.isCraftable()) - .filter(downgrade -> uncontributedItems.stream().noneMatch(item -> item.getId().equals(downgrade))) - .map(downgrade -> new Pair<>( - downgrade, MuseumUtils.getSetCraftCost(downgrade))) - .findFirst() - .orElse(null))); + } } uncontributedItems.sort(Comparator.comparing(Donation::getId)); //Sorting alphabetically @@ -303,21 +288,19 @@ private static void updateData4ProfileMember(String uuid, String profileId, Fabr //Set of all found item ids on profile ObjectOpenHashSet itemIds = new ObjectOpenHashSet<>(); - for (Map.Entry donatedSet : donatedSets.entrySet()) { - //Item is plural here because the nbt is a list - String itemsData = donatedSet.getValue().getAsJsonObject().get("items").getAsJsonObject().get("data").getAsString(); - NbtList items = NbtIo.readCompressed(new ByteArrayInputStream(Base64.getDecoder().decode(itemsData)), NbtSizeTracker.ofUnlimitedBytes()).getList("i", NbtElement.COMPOUND_TYPE); - - for (int i = 0; i < items.size(); i++) { - NbtCompound tag = items.getCompound(i).getCompound("tag"); - - if (tag.contains("ExtraAttributes")) { - NbtCompound extraAttributes = tag.getCompound("ExtraAttributes"); - - if (extraAttributes.contains("id")) itemIds.add(extraAttributes.getString("id")); + donatedSets.forEach((s, __) -> { + Optional donation = MUSEUM_DONATIONS.stream().filter(d -> d.getId().equals(s)).findFirst(); + donation.ifPresent(value -> itemIds.addAll(value.getDowngrades())); + if (donation.isPresent()) { + if (donation.get().isSet()) { + itemIds.addAll(donation.get().getSet().stream().map(Pair::getLeft).toList()); + donation.get().getDowngrades().forEach(downgrade -> itemIds.addAll(MuseumUtils.getPiecesBySetID(downgrade))); + } else { + itemIds.add(donation.get().getId().replace("STARRED_", "")); + itemIds.addAll(donation.get().getDowngrades()); } } - } + }); MUSEUM_ITEM_CACHE.get(uuid).put(profileId, new ProfileMuseumData(System.currentTimeMillis(), itemIds)); save(); @@ -389,7 +372,9 @@ public static void tick(String profileId) { } } + //FIXME Called every frame while holding on undonated items only why? public static boolean hasItemInMuseum(String id) { + id = id.replace("STARRED_", ""); String uuid = Utils.getUndashedUuid(); ObjectOpenHashSet collectedItemIds = (!MUSEUM_ITEM_CACHE.containsKey(uuid) || Utils.getProfileId().isBlank() || !MUSEUM_ITEM_CACHE.get(uuid).containsKey(Utils.getProfileId())) ? null : MUSEUM_ITEM_CACHE.get(uuid).get(Utils.getProfileId()).collectedItemIds(); From 09ee61bdca5bcd8e8c44bfbad651c84f94a6505e Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Sun, 8 Dec 2024 11:06:13 +0100 Subject: [PATCH 05/17] unused method --- .../skyblock/museum/MuseumUtils.java | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java index 8f4f4dbd1e..d056d127ee 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java @@ -15,24 +15,6 @@ public class MuseumUtils { - /** - * Calculates the total crafting cost for a set associated with a given ID. - * - * @param id the ID of the set for which the crafting cost is calculated - * @return the total crafting cost of the set - */ - protected static double getSetCraftCost(String id) { - double cost = 0; - for (Donation donation : MuseumItemCache.MUSEUM_DONATIONS) { - if (donation.getId().equals(id)) { - for (Pair piece : donation.getSet()){ - cost += ItemUtils.getCraftCost(piece.getLeft()); - } - } - } - return cost; - } - /** * Retrieves the display name for an item or a set. * If the item is part of a set, it returns the set's name like "Divan's armor". From 942b79b0aa6180abeca205abbe27f845cc667802 Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Fri, 13 Dec 2024 09:39:37 +0100 Subject: [PATCH 06/17] Changes --- .../mixins/accessors/ScreenAccessor.java | 7 --- .../skyblocker/skyblock/museum/Donation.java | 49 +++++++------------ .../skyblock/museum/DonationButton.java | 33 +++++++------ .../skyblock/museum/ItemSorter.java | 11 +++-- .../skyblock/museum/MuseumItemCache.java | 45 ++++++++++------- .../skyblock/museum/MuseumManager.java | 11 +++-- .../skyblock/museum/MuseumUtils.java | 31 +++++++++--- .../skyblocker/skyblock/museum/PriceData.java | 21 ++++---- .../de/hysky/skyblocker/utils/ItemUtils.java | 4 +- .../assets/skyblocker/lang/en_us.json | 2 +- 10 files changed, 115 insertions(+), 99 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/mixins/accessors/ScreenAccessor.java b/src/main/java/de/hysky/skyblocker/mixins/accessors/ScreenAccessor.java index e73cef1019..c354430276 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/accessors/ScreenAccessor.java +++ b/src/main/java/de/hysky/skyblocker/mixins/accessors/ScreenAccessor.java @@ -1,21 +1,14 @@ package de.hysky.skyblocker.mixins.accessors; -import net.minecraft.client.gui.Drawable; -import net.minecraft.client.gui.Element; -import net.minecraft.client.gui.Selectable; import net.minecraft.client.gui.screen.Screen; 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; @Mixin(Screen.class) public interface ScreenAccessor { @Accessor @Mutable void setTitle(Text title); - - @Invoker("addDrawableChild") - T callAddDrawableChild(T drawableElement); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java index 12c8f5641d..6eef1fb24a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java @@ -1,6 +1,8 @@ package de.hysky.skyblocker.skyblock.museum; -import net.minecraft.util.Pair; +import it.unimi.dsi.fastutil.objects.ObjectDoublePair; +import it.unimi.dsi.fastutil.objects.ObjectIntPair; +import it.unimi.dsi.fastutil.objects.ObjectObjectMutablePair; import java.util.ArrayList; import java.util.List; @@ -8,22 +10,20 @@ public class Donation { private final String category; private final String id; - private final List> set; - private final List upgrades; - private List downgrades; - private List> countsTowards;// downgrades not donated + private final List> set; + private final List downgrades = new ArrayList<>(); + private List> countsTowards;// downgrades not donated private PriceData priceData; - private Pair discount; + private ObjectDoublePair discount; private final int xp; private int totalXp; private double xpCoinsRatio; - public Donation(String category, String id, List> set, int xp, List upgrades) { + public Donation(String category, String id, List> set, int xp) { this.category = category; this.id = id; this.set = set; this.xp = xp; - this.upgrades = upgrades; } public int getTotalXp() { @@ -34,11 +34,11 @@ public void setTotalXp(int totalXp) { this.totalXp = totalXp; } - public List> getCountsTowards() { + public List> getCountsTowards() { return countsTowards; } - public void setCountsTowards(List> countsTowards) { + public void setCountsTowards(List> countsTowards) { this.countsTowards = countsTowards; } @@ -50,29 +50,20 @@ public void setPriceData() { this.priceData = new PriceData(this); } - public void setDowngrades() { - List downgrades = new ArrayList<>(); - for (List list : MuseumItemCache.ORDERED_UPGRADES) { - int armorIndex = list.indexOf(id); - if (armorIndex > 0) { - for (int i = armorIndex - 1; i >= 0; i--) { - downgrades.add(list.get(i)); - } - } - } - this.downgrades = downgrades; - } - - public Pair getDiscount() { + public ObjectDoublePair getDiscount() { return discount; } - public void setDiscount(Pair discount) { + public void setDiscount(ObjectDoublePair discount) { this.discount = discount; } public boolean hasDiscount() { - return discount != null && discount.getRight() > 0d; + return discount != null && discount.rightDouble() > 0d; + } + + public void addDowngrade(String downgrade) { + this.downgrades.add(downgrade); } public List getDowngrades() { @@ -100,7 +91,7 @@ public boolean isSet() { return !set.isEmpty(); } - public List> getSet() { + public List> getSet() { return set; } @@ -108,10 +99,6 @@ public int getXp() { return xp; } - public List getUpgrades() { - return upgrades; - } - public boolean isCraftable() { return this.priceData.getCraftCost() > 0; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java index dcda39f74e..ca1b7a0ea5 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java @@ -3,6 +3,9 @@ import de.hysky.skyblocker.skyblock.item.WikiLookup; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.utils.ItemUtils; +import it.unimi.dsi.fastutil.objects.ObjectDoublePair; +import it.unimi.dsi.fastutil.objects.ObjectIntPair; +import it.unimi.dsi.fastutil.objects.ObjectObjectMutablePair; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; @@ -15,7 +18,6 @@ import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; -import net.minecraft.util.Pair; import java.util.ArrayList; import java.util.List; @@ -27,7 +29,6 @@ public class DonationButton extends ClickableWidget { private Donation donation = null; private ItemStack itemStack = null; - private static final String WIKI_LOCKUP_KEY = WikiLookup.wikiLookup.getBoundKeyTranslationKey(); private static final TextRenderer TEXT_RENDERER = MinecraftClient.getInstance().textRenderer; private String textToRender; private List tooltip; @@ -54,10 +55,10 @@ public void init(Donation donation) { ? ItemRepository.getItemStack(donation.getId()) : ItemRepository.getItemStack( donation.getSet().stream() - .filter(piece -> piece.getLeft().toLowerCase(Locale.ENGLISH).contains("helmet") || piece.getLeft().toLowerCase(Locale.ENGLISH).contains("hat")) + .filter(piece -> piece.left().toLowerCase(Locale.ENGLISH).contains("helmet") || piece.left().toLowerCase(Locale.ENGLISH).contains("hat")) .findFirst() .orElse(donation.getSet().get(1)) // gets chestplate - .getLeft() + .left() ); if (itemStack != null) { @@ -72,6 +73,7 @@ public void init(Donation donation) { */ protected void clearDisplayStack() { this.visible = false; + this.donation = null; this.itemStack = null; this.tooltip = null; this.textToRender = null; @@ -100,25 +102,26 @@ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float d * Creates the tooltip for the button based on its associated donation data */ private void createTooltip() { + final String WIKI_LOCKUP_KEY = WikiLookup.wikiLookup.getBoundKeyTranslationKey(); List tooltip = new ArrayList<>(); boolean soulbound = ItemUtils.isSoulbound(itemStack); - Pair discount = donation.getDiscount(); - List> countsTowards = donation.getCountsTowards(); + ObjectDoublePair discount = donation.getDiscount(); + List> countsTowards = donation.getCountsTowards(); // Display name tooltip.add(MuseumUtils.getDisplayName(donation.getId(), donation.isSet())); // Set pieces display names if (donation.isSet()) { - for (Pair piece : donation.getSet()) { - ItemStack stack = ItemRepository.getItemStack(piece.getLeft()); + for (ObjectObjectMutablePair piece : donation.getSet()) { + ItemStack stack = ItemRepository.getItemStack(piece.left()); if (stack != null) { Text itemName = stack.getName().copy(); if (soulbound) { tooltip.add(Text.literal(" ").append(itemName)); - } else if (piece.getRight().getEffectivePrice() > 0) { - tooltip.add(Text.literal(" ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.literal(MuseumUtils.formatPrice(piece.getRight().getEffectivePrice())).formatted(Formatting.GOLD)).append(Text.literal(")").formatted(Formatting.DARK_GRAY))); + } else if (piece.right().getEffectivePrice() > 0) { + tooltip.add(Text.literal(" ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.literal(MuseumUtils.formatPrice(piece.right().getEffectivePrice())).formatted(Formatting.GOLD)).append(Text.literal(")").formatted(Formatting.DARK_GRAY))); } else { tooltip.add(Text.literal(" ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.translatable("skyblocker.museum.hud.unknownPrice").formatted(Formatting.RED)).append(Text.literal(")").formatted(Formatting.DARK_GRAY))); } @@ -135,8 +138,8 @@ private void createTooltip() { } else { PriceData priceData = donation.getPriceData(); Text lBinText = donation.hasLBinPrice() ? Text.literal(MuseumUtils.formatPrice(priceData.getLBinPrice())).append(" Coins").formatted(Formatting.GOLD) : Text.translatable("skyblocker.museum.hud.unknownPrice").formatted(Formatting.RED); - MutableText craftCostText = donation.isCraftable() ? Text.literal(MuseumUtils.formatPrice(donation.hasDiscount() ? priceData.getCraftCost() - discount.getRight() : priceData.getCraftCost())).append(" ").append(Text.translatable("skyblocker.museum.hud.coin")).formatted(Formatting.GOLD) : Text.translatable("skyblocker.museum.hud.unknownPrice").formatted(Formatting.RED); - Text discountText = donation.hasDiscount() && donation.isCraftable() ? Text.literal(" (").formatted(Formatting.DARK_GRAY).append(Text.literal(MuseumUtils.formatPrice(priceData.getCraftCost())).formatted(Formatting.GOLD)).append(" - ").append(Text.literal(MuseumUtils.formatPrice(discount.getRight())).formatted(Formatting.GOLD)).append(Text.literal(")").formatted(Formatting.DARK_GRAY)) : Text.empty(); + MutableText craftCostText = donation.isCraftable() ? Text.literal(MuseumUtils.formatPrice(donation.hasDiscount() ? priceData.getCraftCost() - discount.rightDouble() : priceData.getCraftCost())).append(" ").append(Text.translatable("skyblocker.museum.hud.coin")).formatted(Formatting.GOLD) : Text.translatable("skyblocker.museum.hud.unknownPrice").formatted(Formatting.RED); + Text discountText = donation.hasDiscount() && donation.isCraftable() ? Text.literal(" (").formatted(Formatting.DARK_GRAY).append(Text.literal(MuseumUtils.formatPrice(priceData.getCraftCost())).formatted(Formatting.GOLD)).append(" - ").append(Text.literal(MuseumUtils.formatPrice(discount.rightDouble())).formatted(Formatting.GOLD)).append(Text.literal(")").formatted(Formatting.DARK_GRAY)) : Text.empty(); Text xpCoinsRatio = Text.literal(MuseumUtils.formatPrice(donation.getXpCoinsRatio())).append(" ").append(Text.translatable("skyblocker.museum.hud.ratioText")).formatted(Formatting.AQUA); tooltip.add(Text.translatable("skyblocker.museum.hud.sorter.lBin").append(": ").formatted(Formatting.GRAY).append(lBinText)); @@ -147,15 +150,15 @@ private void createTooltip() { if (countsTowards.size() > 1) { tooltip.add(Text.empty()); tooltip.add(Text.translatable("skyblocker.museum.hud.countsFor").append(":").formatted(Formatting.GRAY)); - for (Pair credit : countsTowards) { - tooltip.add(Text.literal(" ● ").formatted(Formatting.GRAY).append(MuseumUtils.getDisplayName(credit.getLeft(), donation.isSet())).append(Text.literal(" (" + credit.getRight() + " XP)").formatted(Formatting.AQUA))); + for (ObjectIntPair credit : countsTowards) { + tooltip.add(Text.literal(" ● ").formatted(Formatting.GRAY).append(MuseumUtils.getDisplayName(credit.left(), donation.isSet())).append(Text.literal(" (" + credit.rightInt() + " XP)").formatted(Formatting.AQUA))); } } if (donation.isCraftable() && donation.hasDiscount()) { tooltip.add(Text.empty()); tooltip.add(Text.translatable("skyblocker.museum.hud.craftIngredient").append(": ").formatted(Formatting.GRAY).append(Text.literal("(").append(Text.translatable("skyblocker.museum.hud.alreadyDonatedItem").append(")")).formatted(Formatting.DARK_GRAY))); - tooltip.add(Text.literal(" - ").formatted(Formatting.GRAY).append(MuseumUtils.getDisplayName(discount.getLeft(), donation.isSet())).append(Text.literal(" ✔").formatted(Formatting.GREEN)).append(Text.literal(" (").formatted(Formatting.DARK_GRAY).append(Text.literal(MuseumUtils.formatPrice(discount.getRight())).formatted(Formatting.GOLD)).append(")").formatted(Formatting.DARK_GRAY))); + tooltip.add(Text.literal(" - ").formatted(Formatting.GRAY).append(MuseumUtils.getDisplayName(discount.left(), donation.isSet())).append(Text.literal(" ✔").formatted(Formatting.GREEN)).append(Text.literal(" (").formatted(Formatting.DARK_GRAY).append(Text.literal(MuseumUtils.formatPrice(discount.rightDouble())).formatted(Formatting.GOLD)).append(")").formatted(Formatting.DARK_GRAY))); } tooltip.add(Text.empty()); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java index dcce6a19e4..7df5ce3241 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java @@ -1,11 +1,12 @@ package de.hysky.skyblocker.skyblock.museum; +import it.unimi.dsi.fastutil.objects.ObjectDoublePair; +import it.unimi.dsi.fastutil.objects.ObjectIntPair; import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.text.Text; import net.minecraft.util.Formatting; -import net.minecraft.util.Pair; import java.util.List; import java.util.Objects; @@ -32,7 +33,7 @@ public class ItemSorter { public static void updateDonationData(Donation donation, boolean useCraftCost) { // Gather all donations that this one counts towards List downgrades = donation.getDowngrades(); - Pair discount = donation.getDiscount(); + ObjectDoublePair discount = donation.getDiscount(); List willCountFor = downgrades.stream() .map(MuseumManager::getDonation) .filter(Objects::nonNull) @@ -46,19 +47,19 @@ public static void updateDonationData(Donation donation, boolean useCraftCost) { // Calculate effective prices double lBinPrice = donation.getPriceData().getLBinPrice(); double rawCraftCost = donation.isCraftable() ? donation.getPriceData().getCraftCost() : 0; - double craftCost = discount != null ? rawCraftCost - discount.getRight() : rawCraftCost; + double craftCost = discount != null ? rawCraftCost - discount.rightDouble() : rawCraftCost; double effectivePrice = useCraftCost ? (craftCost > 0 ? (lBinPrice == 0 ? craftCost : Math.min(craftCost, lBinPrice)) : lBinPrice) : (lBinPrice > 0 ? lBinPrice : craftCost); double ratio = totalXP > 0 && effectivePrice > 0 ? effectivePrice / totalXP : 0; // Update donation with computed data - if (donation.isSet()) donation.getSet().forEach(pair -> pair.getRight().setEffectivePrice(effectivePrice == craftCost ? pair.getRight().getCraftCost() : pair.getRight().getLBinPrice())); + if (donation.isSet()) donation.getSet().forEach(pair -> pair.right().setEffectivePrice(effectivePrice == craftCost ? pair.right().getCraftCost() : pair.right().getLBinPrice())); donation.getPriceData().setEffectivePrice(effectivePrice); donation.setXpCoinsRatio(ratio); donation.setTotalXp(totalXP); donation.setCountsTowards(willCountFor.stream() - .map(d -> new Pair<>(d.getId(), d.getXp())) + .map(d -> ObjectIntPair.of(d.getId(), d.getXp())) .toList()); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java index bc4007dcd5..622ca93f7a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java @@ -13,10 +13,7 @@ import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.utils.*; import de.hysky.skyblocker.utils.Http.ApiResponse; -import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.*; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; @@ -25,7 +22,6 @@ import net.minecraft.item.ItemStack; import net.minecraft.screen.slot.Slot; import net.minecraft.text.Text; -import net.minecraft.util.Pair; import net.minecraft.util.collection.DefaultedList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -130,12 +126,12 @@ public static void loadMuseumItems() { for (JsonElement element : array) { String itemID = element.getAsString(); - List> set = new ArrayList<>(); + List> set = new ArrayList<>(); if (category.equals("armor")) { boolean isEquipment = true; for (JsonElement jsonElement : setsToItems.get(itemID).getAsJsonArray()) { if (isEquipment) isEquipment = ItemUtils.isEquipment(jsonElement.getAsString()); - set.add(new Pair<>(jsonElement.getAsString(), null)); + set.add(new ObjectObjectMutablePair<>(jsonElement.getAsString(), null)); } String realId = itemID; for (Map.Entry exception : setExceptions.entrySet()) { @@ -174,10 +170,20 @@ public static void loadMuseumItems() { } } - MUSEUM_DONATIONS.add(new Donation(category, itemID, set, itemXP, upgrades)); + MUSEUM_DONATIONS.add(new Donation(category, itemID, set, itemXP)); } } - MUSEUM_DONATIONS.forEach(Donation::setDowngrades); + + MUSEUM_DONATIONS.forEach(donation -> { + for (List list : ORDERED_UPGRADES) { + int armorIndex = list.indexOf(donation.getId()); + if (armorIndex > 0) { + for (int i = armorIndex - 1; i >= 0; i--) { + donation.addDowngrade(list.get(i)); + } + } + } + }); LOGGER.info("[Skyblocker] Loaded museum data"); } catch (NoSuchFileException ignored) { @@ -227,18 +233,22 @@ public static List getDonations() { for (Donation donation : MUSEUM_DONATIONS) { // Check if the donation id or his upgrades is not present in the collected items if (!items.contains(donation.getId())) { - if (donation.isSet()) { - if (items.stream().anyMatch(i -> donation.getSet().stream().anyMatch(p -> p.getLeft().equals(i)))) continue; - //if (items.stream().anyMatch(p -> donation.getUpgrades().stream().anyMatch(upgrade -> MuseumUtils.getPiecesBySetID(upgrade).contains(p)))) continue; - } + if (donation.isSet() && items.stream().anyMatch(i -> donation.getSet().stream().anyMatch(p -> p.left().equals(i)))) continue; donation.setPriceData(); uncontributedItems.add(donation); } - } - } - uncontributedItems.sort(Comparator.comparing(Donation::getId)); //Sorting alphabetically + // Check if the item has a donated downgrade + uncontributedItems.forEach(donation -> donation.setDiscount(donation.getDowngrades().stream() + .filter(downgrade -> donation.isCraftable()) + .filter(downgrade -> uncontributedItems.stream().noneMatch(item -> item.getId().equals(downgrade))) + .map(downgrade -> ObjectDoublePair.of(downgrade, MuseumUtils.getSetCraftCost(downgrade))) + .findFirst() + .orElse(null))); + + uncontributedItems.sort(Comparator.comparing(Donation::getId)); //Sorting alphabetically + } return uncontributedItems; } @@ -293,7 +303,7 @@ private static void updateData4ProfileMember(String uuid, String profileId, Fabr donation.ifPresent(value -> itemIds.addAll(value.getDowngrades())); if (donation.isPresent()) { if (donation.get().isSet()) { - itemIds.addAll(donation.get().getSet().stream().map(Pair::getLeft).toList()); + itemIds.addAll(donation.get().getSet().stream().map(ObjectObjectMutablePair::left).toList()); donation.get().getDowngrades().forEach(downgrade -> itemIds.addAll(MuseumUtils.getPiecesBySetID(downgrade))); } else { itemIds.add(donation.get().getId().replace("STARRED_", "")); @@ -372,7 +382,6 @@ public static void tick(String profileId) { } } - //FIXME Called every frame while holding on undonated items only why? public static boolean hasItemInMuseum(String id) { id = id.replace("STARRED_", ""); String uuid = Utils.getUndashedUuid(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java index cd4630c45a..d2a5024dc7 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java @@ -4,6 +4,7 @@ import de.hysky.skyblocker.skyblock.item.WikiLookup; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.utils.ItemUtils; +import it.unimi.dsi.fastutil.objects.ObjectObjectMutablePair; import net.fabricmc.fabric.api.client.screen.v1.Screens; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; @@ -15,14 +16,13 @@ import net.minecraft.client.gui.widget.ClickableWidget; import net.minecraft.client.gui.widget.TextFieldWidget; import net.minecraft.client.gui.widget.ToggleButtonWidget; +import net.minecraft.client.option.KeyBinding; import net.minecraft.client.render.RenderLayer; import net.minecraft.component.DataComponentTypes; import net.minecraft.item.ItemStack; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; -import net.minecraft.util.Pair; -import org.lwjgl.glfw.GLFW; import java.util.ArrayList; import java.util.List; @@ -40,6 +40,7 @@ public class MuseumManager extends ClickableWidget { private static final ItemSorter ITEM_SORTER = new ItemSorter(); private static final ItemFilter ITEM_FILTER = new ItemFilter(); private static final TextRenderer TEXT_RENDERER = CLIENT.textRenderer; + private static final KeyBinding INVENTORY_OPEN_KEY = CLIENT.options.inventoryKey; private static String SEARCH_QUERY = ""; private static int CURRENT_PAGE = 0; private final int layoutX; @@ -186,8 +187,8 @@ public void updateSearchResults(boolean resetPage) { .append(ItemUtils.getConcatenatedLore(itemStack)); } if (item.getSet() != null && !item.getSet().isEmpty()) { - for (Pair piece : item.getSet()) { - ItemStack pieceStack = ItemRepository.getItemStack(piece.getLeft()); + for (ObjectObjectMutablePair piece : item.getSet()) { + ItemStack pieceStack = ItemRepository.getItemStack(piece.left()); if (pieceStack != null) searchableContent.append(pieceStack.getName().getString()) .append(ItemUtils.getConcatenatedLore(pieceStack)); } @@ -297,7 +298,7 @@ public boolean charTyped(char chr, int modifiers) { @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if ((this.searchField.isActive() && keyCode == GLFW.GLFW_KEY_E) + if ((this.searchField.isActive() && INVENTORY_OPEN_KEY.matchesKey(keyCode, scanCode)) || this.searchField.keyPressed(keyCode, scanCode, modifiers)) { updateSearchResults(true); return true; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java index d056d127ee..d5b8ed5a88 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java @@ -2,10 +2,10 @@ import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.utils.ItemUtils; +import it.unimi.dsi.fastutil.objects.ObjectObjectMutablePair; import net.minecraft.item.ItemStack; import net.minecraft.text.Style; import net.minecraft.text.Text; -import net.minecraft.util.Pair; import java.text.NumberFormat; import java.util.List; @@ -15,6 +15,26 @@ public class MuseumUtils { + private static final NumberFormat NUMBER_FORMATTER_S = NumberFormat.getCompactNumberInstance(Locale.CANADA, NumberFormat.Style.SHORT); + + /** + * Calculates the total crafting cost for a set associated with a given ID. + * + * @param id the ID of the set for which the crafting cost is calculated + * @return the total crafting cost of the set + */ + protected static double getSetCraftCost(String id) { + double cost = 0; + for (Donation donation : MuseumItemCache.MUSEUM_DONATIONS) { + if (donation.getId().equals(id)) { + for (ObjectObjectMutablePair piece : donation.getSet()){ + cost += ItemUtils.getCraftCost(piece.left()); + } + } + } + return cost; + } + /** * Retrieves the display name for an item or a set. * If the item is part of a set, it returns the set's name like "Divan's armor". @@ -31,7 +51,7 @@ protected static Text getDisplayName(String id, boolean isSet) { Optional donation = MuseumItemCache.MUSEUM_DONATIONS.stream().filter(d -> d.getId().equals(id)).findFirst(); if (donation.isPresent()) { if (!donation.get().getSet().isEmpty()) { - Text pieceName = getDisplayName(donation.get().getSet().getFirst().getLeft(), false); + Text pieceName = getDisplayName(donation.get().getSet().getFirst().left(), false); if (pieceName != null) { nameStyle = pieceName.getSiblings().getFirst().getStyle(); } @@ -56,8 +76,8 @@ protected static Text getDisplayName(String id, boolean isSet) { */ protected static String getSetID(String id) { for (Donation donation : MuseumItemCache.MUSEUM_DONATIONS) { - for (Pair set : donation.getSet()) { - if (set.getLeft().equals(id)) { + for (ObjectObjectMutablePair set : donation.getSet()) { + if (set.left().equals(id)) { return donation.getId(); } } @@ -70,7 +90,7 @@ protected static List getPiecesBySetID(String donationId) { .filter(d -> d.getId().equals(donationId)) .map(Donation::getSet) .flatMap(List::stream) - .map(Pair::getLeft) + .map(ObjectObjectMutablePair::left) .collect(Collectors.toList()); } @@ -112,7 +132,6 @@ public static String formatArmorName(String id, boolean isEquipment) { * @return A formatted string (e.g., "10M", "5K", "1.2B"). */ public static String formatPrice(double value) { - NumberFormat NUMBER_FORMATTER_S = NumberFormat.getCompactNumberInstance(Locale.CANADA, NumberFormat.Style.SHORT); NUMBER_FORMATTER_S.setMaximumFractionDigits(1); return NUMBER_FORMATTER_S.format(value); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/PriceData.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/PriceData.java index 2b9489f696..9aa8b29cfb 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/PriceData.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/PriceData.java @@ -1,11 +1,11 @@ package de.hysky.skyblocker.skyblock.museum; import de.hysky.skyblocker.utils.ItemUtils; -import net.minecraft.util.Pair; +import it.unimi.dsi.fastutil.objects.ObjectObjectMutablePair; public class PriceData { - private double lBinPrice = 0; - private double craftCost = 0; + private final double lBinPrice; + private final double craftCost; private double effectivePrice; public PriceData(double lBinPrice, double craftCost) { @@ -15,15 +15,18 @@ public PriceData(double lBinPrice, double craftCost) { public PriceData(Donation donation) { if (donation.isSet()) { - for (Pair piece : donation.getSet()) { - double lBinPrice = ItemUtils.getItemPrice(piece.getLeft()).leftDouble(); - double craftCost = ItemUtils.getCraftCost(piece.getLeft()); + double totalLBinPrice = 0, totalCraftCost = 0; + for (ObjectObjectMutablePair piece : donation.getSet()) { + double lBinPrice = ItemUtils.getItemPrice(piece.left()).leftDouble(); + double craftCost = ItemUtils.getCraftCost(piece.left()); - this.lBinPrice += lBinPrice; - this.craftCost += craftCost; + totalLBinPrice += lBinPrice; + totalCraftCost += craftCost; - piece.setRight(new PriceData(lBinPrice, craftCost)); + piece.right(new PriceData(lBinPrice, craftCost)); } + this.lBinPrice = totalLBinPrice; + this.craftCost = totalCraftCost; } else { this.lBinPrice = ItemUtils.getItemPrice(donation.getId()).leftDouble(); this.craftCost = ItemUtils.getCraftCost(donation.getId()); diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java index 1185ae7ebb..985e755233 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java @@ -324,8 +324,8 @@ public static PetInfo getPetInfo(ComponentHolder stack) { return DoubleBooleanPair.of(0, false); } - public static double getCraftCost(String id) { - NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(id); + public static double getCraftCost(String neuId) { + NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(neuId); if (neuItem != null && !neuItem.getRecipes().isEmpty()) { return CraftPriceTooltip.getItemCost(neuItem.getRecipes().getFirst(), 0); } diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index fd8015acfb..42c6a36548 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -785,7 +785,7 @@ "skyblocker.config.uiAndVisuals.itemCooldown.enableItemCooldowns": "Enable Item Cooldown", "skyblocker.config.uiAndVisuals.museumOverlay": "Museum Overlay", - "skyblocker.config.uiAndVisuals.museumOverlay.@Tooltip": "Shows undonated items including their prices and more.\n\nMake sure to enable your Museum API to work!", + "skyblocker.config.uiAndVisuals.museumOverlay.@Tooltip": "Shows undonated items including their prices and more.\n\nMake sure to enable your Museum API for this to work!", "skyblocker.config.uiAndVisuals.searchOverlay": "Search Overlay", "skyblocker.config.uiAndVisuals.searchOverlay.enableAuctionHouse": "Enable For Auction House", From 451022bb730f9e70b7b2c616b37952417b2f9615 Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Fri, 13 Dec 2024 14:17:54 +0100 Subject: [PATCH 07/17] reformated --- .../skyblocker/skyblock/museum/Donation.java | 204 +++---- .../skyblock/museum/DonationButton.java | 300 +++++----- .../skyblock/museum/ItemFilter.java | 182 +++--- .../skyblock/museum/ItemSorter.java | 285 ++++----- .../skyblock/museum/MuseumItemCache.java | 10 +- .../skyblock/museum/MuseumManager.java | 565 +++++++++--------- .../skyblock/museum/MuseumUtils.java | 4 +- .../skyblocker/skyblock/museum/PriceData.java | 88 +-- 8 files changed, 820 insertions(+), 818 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java index 6eef1fb24a..5a90d95e12 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java @@ -8,106 +8,106 @@ import java.util.List; public class Donation { - private final String category; - private final String id; - private final List> set; - private final List downgrades = new ArrayList<>(); - private List> countsTowards;// downgrades not donated - private PriceData priceData; - private ObjectDoublePair discount; - private final int xp; - private int totalXp; - private double xpCoinsRatio; - - public Donation(String category, String id, List> set, int xp) { - this.category = category; - this.id = id; - this.set = set; - this.xp = xp; - } - - public int getTotalXp() { - return totalXp; - } - - public void setTotalXp(int totalXp) { - this.totalXp = totalXp; - } - - public List> getCountsTowards() { - return countsTowards; - } - - public void setCountsTowards(List> countsTowards) { - this.countsTowards = countsTowards; - } - - public PriceData getPriceData() { - return priceData; - } - - public void setPriceData() { - this.priceData = new PriceData(this); - } - - public ObjectDoublePair getDiscount() { - return discount; - } - - public void setDiscount(ObjectDoublePair discount) { - this.discount = discount; - } - - public boolean hasDiscount() { - return discount != null && discount.rightDouble() > 0d; - } - - public void addDowngrade(String downgrade) { - this.downgrades.add(downgrade); - } - - public List getDowngrades() { - return downgrades; - } - - public double getXpCoinsRatio() { - return xpCoinsRatio; - } - - public void setXpCoinsRatio(double xpCoinsRatio) { - this.xpCoinsRatio = xpCoinsRatio; - } - - public String getCategory() { - return category; - } - - public String getId() { - return id; - } - - - public boolean isSet() { - return !set.isEmpty(); - } - - public List> getSet() { - return set; - } - - public int getXp() { - return xp; - } - - public boolean isCraftable() { - return this.priceData.getCraftCost() > 0; - } - - public boolean hasLBinPrice() { - return this.priceData.getLBinPrice() > 0; - } - - public boolean hasPrice() { - return this.priceData.getEffectivePrice() > 0; - } + private final String category; + private final String id; + private final List> set; + private final List downgrades = new ArrayList<>(); + private final int xp; + private List> countsTowards;// downgrades not donated + private PriceData priceData; + private ObjectDoublePair discount; + private int totalXp; + private double xpCoinsRatio; + + public Donation(String category, String id, List> set, int xp) { + this.category = category; + this.id = id; + this.set = set; + this.xp = xp; + } + + public int getTotalXp() { + return totalXp; + } + + public void setTotalXp(int totalXp) { + this.totalXp = totalXp; + } + + public List> getCountsTowards() { + return countsTowards; + } + + public void setCountsTowards(List> countsTowards) { + this.countsTowards = countsTowards; + } + + public PriceData getPriceData() { + return priceData; + } + + public void setPriceData() { + this.priceData = new PriceData(this); + } + + public ObjectDoublePair getDiscount() { + return discount; + } + + public void setDiscount(ObjectDoublePair discount) { + this.discount = discount; + } + + public boolean hasDiscount() { + return discount != null && discount.rightDouble() > 0d; + } + + public void addDowngrade(String downgrade) { + this.downgrades.add(downgrade); + } + + public List getDowngrades() { + return downgrades; + } + + public double getXpCoinsRatio() { + return xpCoinsRatio; + } + + public void setXpCoinsRatio(double xpCoinsRatio) { + this.xpCoinsRatio = xpCoinsRatio; + } + + public String getCategory() { + return category; + } + + public String getId() { + return id; + } + + + public boolean isSet() { + return !set.isEmpty(); + } + + public List> getSet() { + return set; + } + + public int getXp() { + return xp; + } + + public boolean isCraftable() { + return this.priceData.getCraftCost() > 0; + } + + public boolean hasLBinPrice() { + return this.priceData.getLBinPrice() > 0; + } + + public boolean hasPrice() { + return this.priceData.getEffectivePrice() > 0; + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java index ca1b7a0ea5..fd02a30dfe 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java @@ -24,154 +24,154 @@ import java.util.Locale; public class DonationButton extends ClickableWidget { - private static final int SIZE = 33; - private static final int ITEM_OFFSET = 8; - - private Donation donation = null; - private ItemStack itemStack = null; - private static final TextRenderer TEXT_RENDERER = MinecraftClient.getInstance().textRenderer; - private String textToRender; - private List tooltip; - - protected DonationButton(int x, int y) { - super(x, y, SIZE, SIZE + 2, ScreenTexts.EMPTY); - } - - protected ItemStack getDisplayStack() { - return this.itemStack; - } - - /** - * Initializes the button with a donation object. - * - * @param donation The donation to associate with this button. - */ - public void init(Donation donation) { - this.donation = donation; - this.textToRender = MuseumUtils.formatPrice(donation.getPriceData().getEffectivePrice()); - - // Determine the item stack to display - this.itemStack = !donation.isSet() - ? ItemRepository.getItemStack(donation.getId()) - : ItemRepository.getItemStack( - donation.getSet().stream() - .filter(piece -> piece.left().toLowerCase(Locale.ENGLISH).contains("helmet") || piece.left().toLowerCase(Locale.ENGLISH).contains("hat")) - .findFirst() - .orElse(donation.getSet().get(1)) // gets chestplate - .left() - ); - - if (itemStack != null) { - this.visible = true; - createTooltip(); - } - } - - - /** - * Clears the display stack and resets the button state. - */ - protected void clearDisplayStack() { - this.visible = false; - this.donation = null; - this.itemStack = null; - this.tooltip = null; - this.textToRender = null; - } - - @Override - protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { - context.drawGuiTexture(RenderLayer::getGuiTextured, AnimatedResultButton.SLOT_CRAFTABLE_TEXTURE, this.getX(), this.getY(), this.width, this.height); - - int yOffset = 8; - - if (donation.hasPrice()) { - int textWidth = TEXT_RENDERER.getWidth(textToRender); - int centeredX = this.getX() + (this.width / 2) - (textWidth / 2); - int textY = this.getY() + ITEM_OFFSET + 13; - context.drawText(TEXT_RENDERER, textToRender, centeredX, textY, 0xFF00FF00, true); - - yOffset -= 4; - } - - context.drawItemWithoutEntity(itemStack, this.getX() + ITEM_OFFSET, this.getY() + yOffset); - - } - - /** - * Creates the tooltip for the button based on its associated donation data - */ - private void createTooltip() { - final String WIKI_LOCKUP_KEY = WikiLookup.wikiLookup.getBoundKeyTranslationKey(); - List tooltip = new ArrayList<>(); - - boolean soulbound = ItemUtils.isSoulbound(itemStack); - ObjectDoublePair discount = donation.getDiscount(); - List> countsTowards = donation.getCountsTowards(); - - // Display name - tooltip.add(MuseumUtils.getDisplayName(donation.getId(), donation.isSet())); - - // Set pieces display names - if (donation.isSet()) { - for (ObjectObjectMutablePair piece : donation.getSet()) { - ItemStack stack = ItemRepository.getItemStack(piece.left()); - if (stack != null) { - Text itemName = stack.getName().copy(); - if (soulbound) { - tooltip.add(Text.literal(" ").append(itemName)); - } else if (piece.right().getEffectivePrice() > 0) { - tooltip.add(Text.literal(" ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.literal(MuseumUtils.formatPrice(piece.right().getEffectivePrice())).formatted(Formatting.GOLD)).append(Text.literal(")").formatted(Formatting.DARK_GRAY))); - } else { - tooltip.add(Text.literal(" ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.translatable("skyblocker.museum.hud.unknownPrice").formatted(Formatting.RED)).append(Text.literal(")").formatted(Formatting.DARK_GRAY))); - } - } - } - } - tooltip.add(Text.empty()); - - Text xpText = Text.literal(String.valueOf(donation.getTotalXp())).append(" ").append(Text.translatable("skyblocker.museum.hud.skyblockXp")).formatted(Formatting.AQUA); - tooltip.add(Text.translatable("skyblocker.museum.hud.xpReward").append(": ").formatted(Formatting.GRAY).append(xpText)); - - if (soulbound) { - tooltip.add(Text.translatable("skyblocker.museum.hud.untradeableItem").formatted(Formatting.GRAY).append(Text.literal(" (").append(Text.translatable("skyblocker.museum.hud.soulboundItem")).append(")").formatted(Formatting.DARK_GRAY))); - } else { - PriceData priceData = donation.getPriceData(); - Text lBinText = donation.hasLBinPrice() ? Text.literal(MuseumUtils.formatPrice(priceData.getLBinPrice())).append(" Coins").formatted(Formatting.GOLD) : Text.translatable("skyblocker.museum.hud.unknownPrice").formatted(Formatting.RED); - MutableText craftCostText = donation.isCraftable() ? Text.literal(MuseumUtils.formatPrice(donation.hasDiscount() ? priceData.getCraftCost() - discount.rightDouble() : priceData.getCraftCost())).append(" ").append(Text.translatable("skyblocker.museum.hud.coin")).formatted(Formatting.GOLD) : Text.translatable("skyblocker.museum.hud.unknownPrice").formatted(Formatting.RED); - Text discountText = donation.hasDiscount() && donation.isCraftable() ? Text.literal(" (").formatted(Formatting.DARK_GRAY).append(Text.literal(MuseumUtils.formatPrice(priceData.getCraftCost())).formatted(Formatting.GOLD)).append(" - ").append(Text.literal(MuseumUtils.formatPrice(discount.rightDouble())).formatted(Formatting.GOLD)).append(Text.literal(")").formatted(Formatting.DARK_GRAY)) : Text.empty(); - Text xpCoinsRatio = Text.literal(MuseumUtils.formatPrice(donation.getXpCoinsRatio())).append(" ").append(Text.translatable("skyblocker.museum.hud.ratioText")).formatted(Formatting.AQUA); - - tooltip.add(Text.translatable("skyblocker.museum.hud.sorter.lBin").append(": ").formatted(Formatting.GRAY).append(lBinText)); - tooltip.add(Text.translatable("skyblocker.museum.hud.sorter.craftCost").append(": ").formatted(Formatting.GRAY).append(craftCostText).append(discountText)); - tooltip.add(Text.translatable("skyblocker.museum.hud.sorter.ratio").append(": ").formatted(Formatting.GRAY).append(xpCoinsRatio)); - } - - if (countsTowards.size() > 1) { - tooltip.add(Text.empty()); - tooltip.add(Text.translatable("skyblocker.museum.hud.countsFor").append(":").formatted(Formatting.GRAY)); - for (ObjectIntPair credit : countsTowards) { - tooltip.add(Text.literal(" ● ").formatted(Formatting.GRAY).append(MuseumUtils.getDisplayName(credit.left(), donation.isSet())).append(Text.literal(" (" + credit.rightInt() + " XP)").formatted(Formatting.AQUA))); - } - } - - if (donation.isCraftable() && donation.hasDiscount()) { - tooltip.add(Text.empty()); - tooltip.add(Text.translatable("skyblocker.museum.hud.craftIngredient").append(": ").formatted(Formatting.GRAY).append(Text.literal("(").append(Text.translatable("skyblocker.museum.hud.alreadyDonatedItem").append(")")).formatted(Formatting.DARK_GRAY))); - tooltip.add(Text.literal(" - ").formatted(Formatting.GRAY).append(MuseumUtils.getDisplayName(discount.left(), donation.isSet())).append(Text.literal(" ✔").formatted(Formatting.GREEN)).append(Text.literal(" (").formatted(Formatting.DARK_GRAY).append(Text.literal(MuseumUtils.formatPrice(discount.rightDouble())).formatted(Formatting.GOLD)).append(")").formatted(Formatting.DARK_GRAY))); - } - - tooltip.add(Text.empty()); - tooltip.add(Text.translatable("skyblocker.museum.hud.wikiLookup", WIKI_LOCKUP_KEY.substring(WIKI_LOCKUP_KEY.lastIndexOf('.') + 1).toUpperCase(Locale.ENGLISH)).formatted(Formatting.YELLOW)); - - this.tooltip = tooltip; - } - - protected List getItemTooltip() { - return this.tooltip; - } - - - @Override - protected void appendClickableNarrations(NarrationMessageBuilder builder) {} + private static final int SIZE = 33; + private static final int ITEM_OFFSET = 8; + private static final TextRenderer TEXT_RENDERER = MinecraftClient.getInstance().textRenderer; + private Donation donation = null; + private ItemStack itemStack = null; + private String textToRender; + private List tooltip; + + protected DonationButton(int x, int y) { + super(x, y, SIZE, SIZE + 2, ScreenTexts.EMPTY); + } + + protected ItemStack getDisplayStack() { + return this.itemStack; + } + + /** + * Initializes the button with a donation object. + * + * @param donation The donation to associate with this button. + */ + public void init(Donation donation) { + this.donation = donation; + this.textToRender = MuseumUtils.formatPrice(donation.getPriceData().getEffectivePrice()); + + // Determine the item stack to display + this.itemStack = !donation.isSet() + ? ItemRepository.getItemStack(donation.getId()) + : ItemRepository.getItemStack( + donation.getSet().stream() + .filter(piece -> piece.left().toLowerCase(Locale.ENGLISH).contains("helmet") || piece.left().toLowerCase(Locale.ENGLISH).contains("hat")) + .findFirst() + .orElse(donation.getSet().get(1)) // gets chestplate + .left() + ); + + if (itemStack != null) { + this.visible = true; + createTooltip(); + } + } + + + /** + * Clears the display stack and resets the button state. + */ + protected void clearDisplayStack() { + this.visible = false; + this.donation = null; + this.itemStack = null; + this.tooltip = null; + this.textToRender = null; + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + context.drawGuiTexture(RenderLayer::getGuiTextured, AnimatedResultButton.SLOT_CRAFTABLE_TEXTURE, this.getX(), this.getY(), this.width, this.height); + + int yOffset = 8; + + if (donation.hasPrice()) { + int textWidth = TEXT_RENDERER.getWidth(textToRender); + int centeredX = this.getX() + (this.width / 2) - (textWidth / 2); + int textY = this.getY() + ITEM_OFFSET + 13; + context.drawText(TEXT_RENDERER, textToRender, centeredX, textY, 0xFF00FF00, true); + + yOffset -= 4; + } + + context.drawItemWithoutEntity(itemStack, this.getX() + ITEM_OFFSET, this.getY() + yOffset); + + } + + /** + * Creates the tooltip for the button based on its associated donation data + */ + private void createTooltip() { + final String WIKI_LOCKUP_KEY = WikiLookup.wikiLookup.getBoundKeyTranslationKey(); + List tooltip = new ArrayList<>(); + + boolean soulbound = ItemUtils.isSoulbound(itemStack); + ObjectDoublePair discount = donation.getDiscount(); + List> countsTowards = donation.getCountsTowards(); + + // Display name + tooltip.add(MuseumUtils.getDisplayName(donation.getId(), donation.isSet())); + + // Set pieces display names + if (donation.isSet()) { + for (ObjectObjectMutablePair piece : donation.getSet()) { + ItemStack stack = ItemRepository.getItemStack(piece.left()); + if (stack != null) { + Text itemName = stack.getName().copy(); + if (soulbound) { + tooltip.add(Text.literal(" ").append(itemName)); + } else if (piece.right().getEffectivePrice() > 0) { + tooltip.add(Text.literal(" ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.literal(MuseumUtils.formatPrice(piece.right().getEffectivePrice())).formatted(Formatting.GOLD)).append(Text.literal(")").formatted(Formatting.DARK_GRAY))); + } else { + tooltip.add(Text.literal(" ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.translatable("skyblocker.museum.hud.unknownPrice").formatted(Formatting.RED)).append(Text.literal(")").formatted(Formatting.DARK_GRAY))); + } + } + } + } + tooltip.add(Text.empty()); + + Text xpText = Text.literal(String.valueOf(donation.getTotalXp())).append(" ").append(Text.translatable("skyblocker.museum.hud.skyblockXp")).formatted(Formatting.AQUA); + tooltip.add(Text.translatable("skyblocker.museum.hud.xpReward").append(": ").formatted(Formatting.GRAY).append(xpText)); + + if (soulbound) { + tooltip.add(Text.translatable("skyblocker.museum.hud.untradeableItem").formatted(Formatting.GRAY).append(Text.literal(" (").append(Text.translatable("skyblocker.museum.hud.soulboundItem")).append(")").formatted(Formatting.DARK_GRAY))); + } else { + PriceData priceData = donation.getPriceData(); + Text lBinText = donation.hasLBinPrice() ? Text.literal(MuseumUtils.formatPrice(priceData.getLBinPrice())).append(" Coins").formatted(Formatting.GOLD) : Text.translatable("skyblocker.museum.hud.unknownPrice").formatted(Formatting.RED); + MutableText craftCostText = donation.isCraftable() ? Text.literal(MuseumUtils.formatPrice(donation.hasDiscount() ? priceData.getCraftCost() - discount.rightDouble() : priceData.getCraftCost())).append(" ").append(Text.translatable("skyblocker.museum.hud.coin")).formatted(Formatting.GOLD) : Text.translatable("skyblocker.museum.hud.unknownPrice").formatted(Formatting.RED); + Text discountText = donation.hasDiscount() && donation.isCraftable() ? Text.literal(" (").formatted(Formatting.DARK_GRAY).append(Text.literal(MuseumUtils.formatPrice(priceData.getCraftCost())).formatted(Formatting.GOLD)).append(" - ").append(Text.literal(MuseumUtils.formatPrice(discount.rightDouble())).formatted(Formatting.GOLD)).append(Text.literal(")").formatted(Formatting.DARK_GRAY)) : Text.empty(); + Text xpCoinsRatio = Text.literal(MuseumUtils.formatPrice(donation.getXpCoinsRatio())).append(" ").append(Text.translatable("skyblocker.museum.hud.ratioText")).formatted(Formatting.AQUA); + + tooltip.add(Text.translatable("skyblocker.museum.hud.sorter.lBin").append(": ").formatted(Formatting.GRAY).append(lBinText)); + tooltip.add(Text.translatable("skyblocker.museum.hud.sorter.craftCost").append(": ").formatted(Formatting.GRAY).append(craftCostText).append(discountText)); + tooltip.add(Text.translatable("skyblocker.museum.hud.sorter.ratio").append(": ").formatted(Formatting.GRAY).append(xpCoinsRatio)); + } + + if (countsTowards.size() > 1) { + tooltip.add(Text.empty()); + tooltip.add(Text.translatable("skyblocker.museum.hud.countsFor").append(":").formatted(Formatting.GRAY)); + for (ObjectIntPair credit : countsTowards) { + tooltip.add(Text.literal(" ● ").formatted(Formatting.GRAY).append(MuseumUtils.getDisplayName(credit.left(), donation.isSet())).append(Text.literal(" (" + credit.rightInt() + " XP)").formatted(Formatting.AQUA))); + } + } + + if (donation.isCraftable() && donation.hasDiscount()) { + tooltip.add(Text.empty()); + tooltip.add(Text.translatable("skyblocker.museum.hud.craftIngredient").append(": ").formatted(Formatting.GRAY).append(Text.literal("(").append(Text.translatable("skyblocker.museum.hud.alreadyDonatedItem").append(")")).formatted(Formatting.DARK_GRAY))); + tooltip.add(Text.literal(" - ").formatted(Formatting.GRAY).append(MuseumUtils.getDisplayName(discount.left(), donation.isSet())).append(Text.literal(" ✔").formatted(Formatting.GREEN)).append(Text.literal(" (").formatted(Formatting.DARK_GRAY).append(Text.literal(MuseumUtils.formatPrice(discount.rightDouble())).formatted(Formatting.GOLD)).append(")").formatted(Formatting.DARK_GRAY))); + } + + tooltip.add(Text.empty()); + tooltip.add(Text.translatable("skyblocker.museum.hud.wikiLookup", WIKI_LOCKUP_KEY.substring(WIKI_LOCKUP_KEY.lastIndexOf('.') + 1).toUpperCase(Locale.ENGLISH)).formatted(Formatting.YELLOW)); + + this.tooltip = tooltip; + } + + protected List getItemTooltip() { + return this.tooltip; + } + + + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) { + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java index a89b5eda05..eaf0baf909 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java @@ -12,95 +12,95 @@ import java.util.stream.Collectors; public class ItemFilter { - private FilterMode currentFilterMode = FilterMode.ALL; - - // Filtering logic methods - private static List filterAll(List donations) { - return donations; - } - - private static List filterWeapons(List donations) { - return donations.stream() - .filter(donation -> "weapons".equals(donation.getCategory())) - .collect(Collectors.toList()); - } - - private static List filterArmor(List donations) { - return donations.stream() - .filter(donation -> "armor".equals(donation.getCategory())) - .collect(Collectors.toList()); - } - - private static List filterRarities(List donations) { - return donations.stream() - .filter(donation -> "rarities".equals(donation.getCategory())) - .collect(Collectors.toList()); - } - - // Method to cycle through filtering modes and apply the corresponding logic - public void cycleFilterMode(List items, List filteredList) { - // Cycle to the next filter mode - currentFilterMode = FilterMode.values()[(currentFilterMode.ordinal() + 1) % FilterMode.values().length]; - // Apply the filtering logic for the current mode - currentFilterMode.applyFilter(items, filteredList); - } - - public void applyFilter(List items, List filteredList) { - currentFilterMode.applyFilter(items, filteredList); - } - - // Get the item associated with the current filter mode - public ItemStack getCurrentFilterItem() { - return currentFilterMode.getAssociatedItem(); - } - - public void resetFilter() { - this.currentFilterMode = FilterMode.ALL; - } - - public Tooltip getTooltip() { - Text tooltip = Text.translatable("skyblocker.museum.hud.filter").append("\n\n").formatted(Formatting.GREEN) - .append(getFilterText(FilterMode.ALL)) - .append(getFilterText(FilterMode.WEAPONS)) - .append(getFilterText(FilterMode.ARMOR)) - .append(getFilterText(FilterMode.RARITIES)) - .append("\n").append(Text.translatable("skyblocker.museum.hud.filter.switch").formatted(Formatting.YELLOW)); - return Tooltip.of(tooltip); - } - - private Text getFilterText(FilterMode mode) { - boolean isCurrent = mode == currentFilterMode; - return Text.literal((isCurrent ? "➤ " : " ")).append(mode.getDisplayName()).append("\n") - .formatted(isCurrent ? Formatting.AQUA : Formatting.GRAY); - } - - public enum FilterMode { - ALL(new ItemStack(Items.NETHER_STAR), ItemFilter::filterAll, Text.translatable("skyblocker.museum.hud.filter.all")), - WEAPONS(new ItemStack(Items.DIAMOND_SWORD), ItemFilter::filterWeapons, Text.translatable("skyblocker.museum.hud.filter.weapons")), - ARMOR(new ItemStack(Items.DIAMOND_CHESTPLATE), ItemFilter::filterArmor, Text.translatable("skyblocker.museum.hud.filter.armor")), - RARITIES(ItemRepository.getItemStack("JADERALD"), ItemFilter::filterRarities, Text.translatable("skyblocker.museum.hud.filter.rarities")); - - private final ItemStack associatedItem; - private final UnaryOperator> filterFunction; - private final Text displayName; - - FilterMode(ItemStack item, UnaryOperator> function, Text displayName) { - this.associatedItem = item; - this.filterFunction = function; - this.displayName = displayName; - } - - public ItemStack getAssociatedItem() { - return associatedItem; - } - - public Text getDisplayName() { - return displayName; - } - - public void applyFilter(List items, List filteredList) { - filteredList.clear(); - filteredList.addAll(filterFunction.apply(items)); - } - } + private FilterMode currentFilterMode = FilterMode.ALL; + + // Filtering logic methods + private static List filterAll(List donations) { + return donations; + } + + private static List filterWeapons(List donations) { + return donations.stream() + .filter(donation -> "weapons".equals(donation.getCategory())) + .collect(Collectors.toList()); + } + + private static List filterArmor(List donations) { + return donations.stream() + .filter(donation -> "armor".equals(donation.getCategory())) + .collect(Collectors.toList()); + } + + private static List filterRarities(List donations) { + return donations.stream() + .filter(donation -> "rarities".equals(donation.getCategory())) + .collect(Collectors.toList()); + } + + // Method to cycle through filtering modes and apply the corresponding logic + public void cycleFilterMode(List items, List filteredList) { + // Cycle to the next filter mode + currentFilterMode = FilterMode.values()[(currentFilterMode.ordinal() + 1) % FilterMode.values().length]; + // Apply the filtering logic for the current mode + currentFilterMode.applyFilter(items, filteredList); + } + + public void applyFilter(List items, List filteredList) { + currentFilterMode.applyFilter(items, filteredList); + } + + // Get the item associated with the current filter mode + public ItemStack getCurrentFilterItem() { + return currentFilterMode.getAssociatedItem(); + } + + public void resetFilter() { + this.currentFilterMode = FilterMode.ALL; + } + + public Tooltip getTooltip() { + Text tooltip = Text.translatable("skyblocker.museum.hud.filter").append("\n\n").formatted(Formatting.GREEN) + .append(getFilterText(FilterMode.ALL)) + .append(getFilterText(FilterMode.WEAPONS)) + .append(getFilterText(FilterMode.ARMOR)) + .append(getFilterText(FilterMode.RARITIES)) + .append("\n").append(Text.translatable("skyblocker.museum.hud.filter.switch").formatted(Formatting.YELLOW)); + return Tooltip.of(tooltip); + } + + private Text getFilterText(FilterMode mode) { + boolean isCurrent = mode == currentFilterMode; + return Text.literal((isCurrent ? "➤ " : " ")).append(mode.getDisplayName()).append("\n") + .formatted(isCurrent ? Formatting.AQUA : Formatting.GRAY); + } + + public enum FilterMode { + ALL(new ItemStack(Items.NETHER_STAR), ItemFilter::filterAll, Text.translatable("skyblocker.museum.hud.filter.all")), + WEAPONS(new ItemStack(Items.DIAMOND_SWORD), ItemFilter::filterWeapons, Text.translatable("skyblocker.museum.hud.filter.weapons")), + ARMOR(new ItemStack(Items.DIAMOND_CHESTPLATE), ItemFilter::filterArmor, Text.translatable("skyblocker.museum.hud.filter.armor")), + RARITIES(ItemRepository.getItemStack("JADERALD"), ItemFilter::filterRarities, Text.translatable("skyblocker.museum.hud.filter.rarities")); + + private final ItemStack associatedItem; + private final UnaryOperator> filterFunction; + private final Text displayName; + + FilterMode(ItemStack item, UnaryOperator> function, Text displayName) { + this.associatedItem = item; + this.filterFunction = function; + this.displayName = displayName; + } + + public ItemStack getAssociatedItem() { + return associatedItem; + } + + public Text getDisplayName() { + return displayName; + } + + public void applyFilter(List items, List filteredList) { + filteredList.clear(); + filteredList.addAll(filterFunction.apply(items)); + } + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java index 7df5ce3241..b9729a1ef5 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java @@ -14,146 +14,147 @@ import java.util.stream.Collectors; public class ItemSorter { - // Sorting logic - private static final Consumer> sortByLowestBIN = donations -> { - donations.forEach(donation -> updateDonationData(donation, false)); - donations.sort(ItemSorter::compareEffectivePrices); - }; - private static final Consumer> sortByCraftCost = donations -> { - donations.forEach(donation -> updateDonationData(donation, true)); - donations.sort(ItemSorter::compareEffectivePrices); - }; - private static final Consumer> sortByXpPerCoin = donations -> { - donations.forEach(donation -> updateDonationData(donation, true)); - donations.sort(ItemSorter::compareCoinsPerXP); - }; - private SortMode currentSortMode = SortMode.LowestBIN; - - // Set effective prices for the donation and its armor set pieces - public static void updateDonationData(Donation donation, boolean useCraftCost) { - // Gather all donations that this one counts towards - List downgrades = donation.getDowngrades(); - ObjectDoublePair discount = donation.getDiscount(); - List willCountFor = downgrades.stream() - .map(MuseumManager::getDonation) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - willCountFor.addFirst(donation); // Ensure donation itself is part of the list - - // Calculate cumulative XP - int totalXP = willCountFor.stream().mapToInt(Donation::getXp).sum(); - - // Calculate effective prices - double lBinPrice = donation.getPriceData().getLBinPrice(); - double rawCraftCost = donation.isCraftable() ? donation.getPriceData().getCraftCost() : 0; - double craftCost = discount != null ? rawCraftCost - discount.rightDouble() : rawCraftCost; - double effectivePrice = useCraftCost - ? (craftCost > 0 ? (lBinPrice == 0 ? craftCost : Math.min(craftCost, lBinPrice)) : lBinPrice) - : (lBinPrice > 0 ? lBinPrice : craftCost); - double ratio = totalXP > 0 && effectivePrice > 0 ? effectivePrice / totalXP : 0; - - // Update donation with computed data - if (donation.isSet()) donation.getSet().forEach(pair -> pair.right().setEffectivePrice(effectivePrice == craftCost ? pair.right().getCraftCost() : pair.right().getLBinPrice())); - donation.getPriceData().setEffectivePrice(effectivePrice); - donation.setXpCoinsRatio(ratio); - donation.setTotalXp(totalXP); - donation.setCountsTowards(willCountFor.stream() - .map(d -> ObjectIntPair.of(d.getId(), d.getXp())) - .toList()); - } - - private static int compareEffectivePrices(Donation a, Donation b) { - double priceA = a.getPriceData().getEffectivePrice(); - double priceB = b.getPriceData().getEffectivePrice(); - - if (priceA <= 0 && priceB <= 0) { - // Both prices are invalid, sort by XP descending - return Integer.compare(b.getTotalXp(), a.getTotalXp()); - } - if (priceA <= 0) return 1; // Move invalid price to the end - if (priceB <= 0) return -1; // Move invalid price to the end - - // Compare valid prices in ascending order - return Double.compare(priceA, priceB); - } - - private static int compareCoinsPerXP(Donation a, Donation b) { - double xpPerCoinA = a.getXpCoinsRatio(); - double xpPerCoinB = b.getXpCoinsRatio(); - - if (xpPerCoinA == 0 && xpPerCoinB == 0) { - // Both ratios are 0, sort by XP descending - return Integer.compare(b.getTotalXp(), a.getTotalXp()); - } - if (xpPerCoinA == 0) return 1; // Move items with 0 XP/coin to the end - if (xpPerCoinB == 0) return -1; // Move items with 0 XP/coin to the end - - // Compare XP/coin ratios in descending order - return Double.compare(xpPerCoinA, xpPerCoinB); - } - - // Method to cycle through sorting modes and apply the corresponding logic - public void cycleSortMode(List donations) { - // Cycle to the next sorting mode - currentSortMode = SortMode.values()[(currentSortMode.ordinal() + 1) % SortMode.values().length]; - // Apply the sorting logic for the current mode - applySort(donations); - } - - public void applySort(List donations) { - currentSortMode.getSortFunction().accept(donations); - } - - // Get the item associated with the current filter mode - public ItemStack getCurrentSortingItem() { - return currentSortMode.getAssociatedItem(); - } - - public void resetSorting() { - this.currentSortMode = SortMode.LowestBIN; - } - - public Tooltip getTooltip() { - Text tooltip = Text.translatable("skyblocker.museum.hud.sorter").append("\n\n").formatted(Formatting.GREEN) - .append(getSortText(SortMode.LowestBIN)) - .append(getSortText(SortMode.CraftCost)) - .append(getSortText(SortMode.CoinsPerXP)) - .append("\n").append(Text.translatable("skyblocker.museum.hud.sorter.switch").formatted(Formatting.YELLOW)); - return Tooltip.of(tooltip); - } - - private Text getSortText(SortMode mode) { - boolean isCurrent = mode == currentSortMode; - return Text.literal((isCurrent ? "➤ " : " ")).append(mode.getDisplayName()).append("\n") - .formatted(isCurrent ? Formatting.AQUA : Formatting.GRAY); - } - - public enum SortMode { - LowestBIN(new ItemStack(Items.GOLD_INGOT), sortByLowestBIN, Text.translatable("skyblocker.museum.hud.sorter.lBin")), - CraftCost(new ItemStack(Items.CRAFTING_TABLE), sortByCraftCost, Text.translatable("skyblocker.museum.hud.sorter.craftCost")), - CoinsPerXP(new ItemStack(Items.EXPERIENCE_BOTTLE), sortByXpPerCoin, Text.translatable("skyblocker.museum.hud.sorter.ratio")); - - private final ItemStack associatedItem; - private final Consumer> sortFunction; - private final Text displayName; - - SortMode(ItemStack item, Consumer> function, Text displayName) { - this.associatedItem = item; - this.sortFunction = function; - this.displayName = displayName; - } - - public ItemStack getAssociatedItem() { - return associatedItem; - } - - public Text getDisplayName() { - return displayName; - } - - public Consumer> getSortFunction() { - return sortFunction; - } - } + // Sorting logic + private static final Consumer> sortByLowestBIN = donations -> { + donations.forEach(donation -> updateDonationData(donation, false)); + donations.sort(ItemSorter::compareEffectivePrices); + }; + private static final Consumer> sortByCraftCost = donations -> { + donations.forEach(donation -> updateDonationData(donation, true)); + donations.sort(ItemSorter::compareEffectivePrices); + }; + private static final Consumer> sortByXpPerCoin = donations -> { + donations.forEach(donation -> updateDonationData(donation, true)); + donations.sort(ItemSorter::compareCoinsPerXP); + }; + private SortMode currentSortMode = SortMode.LowestBIN; + + // Set effective prices for the donation and its armor set pieces + public static void updateDonationData(Donation donation, boolean useCraftCost) { + // Gather all donations that this one counts towards + List downgrades = donation.getDowngrades(); + ObjectDoublePair discount = donation.getDiscount(); + List willCountFor = downgrades.stream() + .map(MuseumManager::getDonation) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + willCountFor.addFirst(donation); // Ensure donation itself is part of the list + + // Calculate cumulative XP + int totalXP = willCountFor.stream().mapToInt(Donation::getXp).sum(); + + // Calculate effective prices + double lBinPrice = donation.getPriceData().getLBinPrice(); + double rawCraftCost = donation.isCraftable() ? donation.getPriceData().getCraftCost() : 0; + double craftCost = discount != null ? rawCraftCost - discount.rightDouble() : rawCraftCost; + double effectivePrice = useCraftCost + ? (craftCost > 0 ? (lBinPrice == 0 ? craftCost : Math.min(craftCost, lBinPrice)) : lBinPrice) + : (lBinPrice > 0 ? lBinPrice : craftCost); + double ratio = totalXP > 0 && effectivePrice > 0 ? effectivePrice / totalXP : 0; + + // Update donation with computed data + if (donation.isSet()) + donation.getSet().forEach(pair -> pair.right().setEffectivePrice(effectivePrice == craftCost ? pair.right().getCraftCost() : pair.right().getLBinPrice())); + donation.getPriceData().setEffectivePrice(effectivePrice); + donation.setXpCoinsRatio(ratio); + donation.setTotalXp(totalXP); + donation.setCountsTowards(willCountFor.stream() + .map(d -> ObjectIntPair.of(d.getId(), d.getXp())) + .toList()); + } + + private static int compareEffectivePrices(Donation a, Donation b) { + double priceA = a.getPriceData().getEffectivePrice(); + double priceB = b.getPriceData().getEffectivePrice(); + + if (priceA <= 0 && priceB <= 0) { + // Both prices are invalid, sort by XP descending + return Integer.compare(b.getTotalXp(), a.getTotalXp()); + } + if (priceA <= 0) return 1; // Move invalid price to the end + if (priceB <= 0) return -1; // Move invalid price to the end + + // Compare valid prices in ascending order + return Double.compare(priceA, priceB); + } + + private static int compareCoinsPerXP(Donation a, Donation b) { + double xpPerCoinA = a.getXpCoinsRatio(); + double xpPerCoinB = b.getXpCoinsRatio(); + + if (xpPerCoinA == 0 && xpPerCoinB == 0) { + // Both ratios are 0, sort by XP descending + return Integer.compare(b.getTotalXp(), a.getTotalXp()); + } + if (xpPerCoinA == 0) return 1; // Move items with 0 XP/coin to the end + if (xpPerCoinB == 0) return -1; // Move items with 0 XP/coin to the end + + // Compare XP/coin ratios in descending order + return Double.compare(xpPerCoinA, xpPerCoinB); + } + + // Method to cycle through sorting modes and apply the corresponding logic + public void cycleSortMode(List donations) { + // Cycle to the next sorting mode + currentSortMode = SortMode.values()[(currentSortMode.ordinal() + 1) % SortMode.values().length]; + // Apply the sorting logic for the current mode + applySort(donations); + } + + public void applySort(List donations) { + currentSortMode.getSortFunction().accept(donations); + } + + // Get the item associated with the current filter mode + public ItemStack getCurrentSortingItem() { + return currentSortMode.getAssociatedItem(); + } + + public void resetSorting() { + this.currentSortMode = SortMode.LowestBIN; + } + + public Tooltip getTooltip() { + Text tooltip = Text.translatable("skyblocker.museum.hud.sorter").append("\n\n").formatted(Formatting.GREEN) + .append(getSortText(SortMode.LowestBIN)) + .append(getSortText(SortMode.CraftCost)) + .append(getSortText(SortMode.CoinsPerXP)) + .append("\n").append(Text.translatable("skyblocker.museum.hud.sorter.switch").formatted(Formatting.YELLOW)); + return Tooltip.of(tooltip); + } + + private Text getSortText(SortMode mode) { + boolean isCurrent = mode == currentSortMode; + return Text.literal((isCurrent ? "➤ " : " ")).append(mode.getDisplayName()).append("\n") + .formatted(isCurrent ? Formatting.AQUA : Formatting.GRAY); + } + + public enum SortMode { + LowestBIN(new ItemStack(Items.GOLD_INGOT), sortByLowestBIN, Text.translatable("skyblocker.museum.hud.sorter.lBin")), + CraftCost(new ItemStack(Items.CRAFTING_TABLE), sortByCraftCost, Text.translatable("skyblocker.museum.hud.sorter.craftCost")), + CoinsPerXP(new ItemStack(Items.EXPERIENCE_BOTTLE), sortByXpPerCoin, Text.translatable("skyblocker.museum.hud.sorter.ratio")); + + private final ItemStack associatedItem; + private final Consumer> sortFunction; + private final Text displayName; + + SortMode(ItemStack item, Consumer> function, Text displayName) { + this.associatedItem = item; + this.sortFunction = function; + this.displayName = displayName; + } + + public ItemStack getAssociatedItem() { + return associatedItem; + } + + public Text getDisplayName() { + return displayName; + } + + public Consumer> getSortFunction() { + return sortFunction; + } + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java index 622ca93f7a..04ebb7d447 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java @@ -39,18 +39,18 @@ import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; public class MuseumItemCache { + public static final Map ARMOR_NAMES = new Object2ObjectArrayMap<>(); + public static final String DONATION_CONFIRMATION_SCREEN_TITLE = "Confirm Donation"; + public static final Map MAPPED_IDS = new Object2ObjectArrayMap<>(); + public static final List MUSEUM_DONATIONS = new ArrayList<>(); private static final Logger LOGGER = LoggerFactory.getLogger(MuseumItemCache.class); private static final Path CACHE_FILE = SkyblockerMod.CONFIG_DIR.resolve("museum_item_cache.json"); - public static final Map ARMOR_NAMES = new Object2ObjectArrayMap<>(); private static final Map> MUSEUM_ITEM_CACHE = new Object2ObjectOpenHashMap<>(); private static final String ERROR_LOG_TEMPLATE = "[Skyblocker] Failed to refresh museum item data for profile {}"; - public static final String DONATION_CONFIRMATION_SCREEN_TITLE = "Confirm Donation"; private static final int CONFIRM_DONATION_BUTTON_SLOT = 20; - public static final Map MAPPED_IDS = new Object2ObjectArrayMap<>(); - private static CompletableFuture loaded; private static final Path MUSEUM_INFO = NEURepoManager.NEU_REPO.file("constants/museum.json").getFsPath(); - public static final List MUSEUM_DONATIONS = new ArrayList<>(); public static List> ORDERED_UPGRADES = new ArrayList<>(); + private static CompletableFuture loaded; @Init public static void init() { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java index d2a5024dc7..a13761cf28 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java @@ -29,286 +29,287 @@ import java.util.Locale; public class MuseumManager extends ClickableWidget { - private static final Identifier BACKGROUND_TEXTURE = Identifier.ofVanilla("textures/gui/recipe_book.png"); - private static final int BACKGROUND_WIDTH = 147; - private static final int BACKGROUND_HEIGHT = 166; - private static final int SEARCH_FIELD_WIDTH = 69; - private static final int SEARCH_FIELD_HEIGHT = 20; - private static final int BUTTON_SIZE = 20; - private static final int BUTTONS_PER_PAGE = 12; - private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); - private static final ItemSorter ITEM_SORTER = new ItemSorter(); - private static final ItemFilter ITEM_FILTER = new ItemFilter(); - private static final TextRenderer TEXT_RENDERER = CLIENT.textRenderer; - private static final KeyBinding INVENTORY_OPEN_KEY = CLIENT.options.inventoryKey; - private static String SEARCH_QUERY = ""; - private static int CURRENT_PAGE = 0; - private final int layoutX; - private final int layoutY; - private final ToggleButtonWidget nextPageButton; - private final ToggleButtonWidget prevPageButton; - private final TextFieldWidget searchField; - private static List donations = new ArrayList<>(); - private final List filteredDonations = new ArrayList<>(); - private final List excludedDonationIds = new ArrayList<>(); - private final List donationButtons = Lists.newArrayListWithCapacity(BUTTONS_PER_PAGE); - private final ButtonWidget filterButton; - private final ButtonWidget sortButton; - private DonationButton hoveredDonationButton; - private int pageCount = 0; - - public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { - super(x, y, screen.width, screen.height, Text.empty()); - this.layoutX = getX() + backgroundWidth + 2; - this.layoutY = y; - - // Initialize search field - this.searchField = new TextFieldWidget(TEXT_RENDERER, layoutX + 25, layoutY + 11, SEARCH_FIELD_WIDTH, SEARCH_FIELD_HEIGHT, Text.empty()); - this.searchField.setMaxLength(60); - this.searchField.setVisible(true); - this.searchField.setEditableColor(0xFFFFFF); - this.searchField.setText(SEARCH_QUERY); - this.searchField.setPlaceholder(Text.translatable("gui.recipebook.search_hint").formatted(Formatting.ITALIC).formatted(Formatting.GRAY)); - - // Initialize page navigation buttons - this.nextPageButton = new ToggleButtonWidget(layoutX + 93, layoutY + 133, 12, 17, false); - this.nextPageButton.setTextures(RecipeBookResults.PAGE_FORWARD_TEXTURES); - this.prevPageButton = new ToggleButtonWidget(layoutX + 38, layoutY + 133, 12, 17, true); - this.prevPageButton.setTextures(RecipeBookResults.PAGE_BACKWARD_TEXTURES); - - donations = MuseumItemCache.getDonations(); - - // Create donation buttons for pagination - for (int i = 0; i < BUTTONS_PER_PAGE; i++) { - DonationButton button = new DonationButton(layoutX + 11 + 31 * (i % 4), layoutY + 34 + 31 * (i / 4)); - this.donationButtons.add(button); - } - - // Initialize sort button - this.sortButton = ButtonWidget.builder(Text.empty(), button -> { - ITEM_SORTER.cycleSortMode(filteredDonations); - button.setTooltip(ITEM_SORTER.getTooltip()); - CURRENT_PAGE = 0; - updateButtons(); - }) - .tooltip(ITEM_SORTER.getTooltip()) - .position(layoutX + 95, layoutY + 11) - .size(BUTTON_SIZE, BUTTON_SIZE) - .build(); - - // Initialize filter button - this.filterButton = ButtonWidget.builder(Text.empty(), button -> { - ITEM_FILTER.cycleFilterMode(donations, filteredDonations); - ITEM_SORTER.applySort(filteredDonations); - button.setTooltip(ITEM_FILTER.getTooltip()); - CURRENT_PAGE = 0; - updateButtons(); - }) - .tooltip(ITEM_FILTER.getTooltip()) - .position(layoutX + 116, layoutY + 11) - .size(BUTTON_SIZE, BUTTON_SIZE) - .build(); - - ITEM_FILTER.applyFilter(donations, filteredDonations); - ITEM_SORTER.applySort(filteredDonations); - updateSearchResults(false); - - Screens.getButtons(screen).add(this); - screen.setFocused(this); - } - - /** - * Retrieves the Donation object corresponding to a given ID. - * - * @param id the ID of the donation to retrieve - * @return the Donation object associated with the given ID, or null if not found - */ - protected static Donation getDonation(String id) { - return donations.stream() - .filter(donation -> donation.getId().equals(id)) - .findFirst() - .orElse(null); - } - - /** - * Resets the UI state including search text, current page, sorting, and filtering. - */ - public static void reset() { - SEARCH_QUERY = ""; - CURRENT_PAGE = 0; - ITEM_SORTER.resetSorting(); - ITEM_FILTER.resetFilter(); - } - - /** - * Updates visibility and content of page navigation buttons. - */ - private void updateNavigationButtons() { - this.prevPageButton.active = CURRENT_PAGE > 0; - this.nextPageButton.active = CURRENT_PAGE < pageCount - 1; - } - - /** - * Updates the donation buttons based on the current page and visible donations. - */ - private void updateButtons() { - List visibleDonations = filteredDonations.stream() - .filter(donation -> !excludedDonationIds.contains(donation.getId())) - .toList(); - - int buttonsSize = visibleDonations.size(); - this.pageCount = (int) Math.ceil((double) buttonsSize / BUTTONS_PER_PAGE); - - for (int i = 0; i < donationButtons.size(); ++i) { - int index = CURRENT_PAGE * donationButtons.size() + i; - - if (index < buttonsSize) { - donationButtons.get(i).init(visibleDonations.get(index)); - } else { - donationButtons.get(i).clearDisplayStack(); - } - } - updateNavigationButtons(); - } - - /** - * Updates search results based on the search text. - * - * @param resetPage Whether to reset to the first page. - */ - public void updateSearchResults(boolean resetPage) { - SEARCH_QUERY = this.searchField.getText(); - excludedDonationIds.clear(); - for (Donation item : donations) { - StringBuilder searchableContent = new StringBuilder(); - ItemStack itemStack = ItemRepository.getItemStack(item.getId()); - if (itemStack != null) { - searchableContent.append(itemStack.getName().getString()) - .append(ItemUtils.getConcatenatedLore(itemStack)); - } - if (item.getSet() != null && !item.getSet().isEmpty()) { - for (ObjectObjectMutablePair piece : item.getSet()) { - ItemStack pieceStack = ItemRepository.getItemStack(piece.left()); - if (pieceStack != null) searchableContent.append(pieceStack.getName().getString()) - .append(ItemUtils.getConcatenatedLore(pieceStack)); - } - } - if (!searchableContent.toString().toLowerCase(Locale.ENGLISH).contains(SEARCH_QUERY.toLowerCase(Locale.ENGLISH))) { - excludedDonationIds.add(item.getId()); - } - } - if (resetPage) CURRENT_PAGE = 0; - updateButtons(); - } - - @Override - protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { - // Render the background texture for the widget - context.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_TEXTURE, layoutX, layoutY, 1.0f, 1.0f, BACKGROUND_WIDTH, BACKGROUND_HEIGHT, 256, 256 - 10); - - // Render page count if multiple pages exist - if (this.pageCount > 1) { - Text text = Text.translatable("gui.recipebook.page", CURRENT_PAGE + 1, this.pageCount); - int width = TEXT_RENDERER.getWidth(text); - - context.drawText(TEXT_RENDERER, text, layoutX - width / 2 + 73, layoutY + 137, -1, false); - } - - // Render donation buttons - this.hoveredDonationButton = null; - for (DonationButton resultButton : donationButtons) { - resultButton.render(context, mouseX, mouseY, delta); - - if (resultButton.visible && resultButton.isHovered()) this.hoveredDonationButton = resultButton; - } - - if (this.sortButton.active) { - int iconX = this.sortButton.getX() + (this.sortButton.getWidth() - 16) / 2; - int iconY = this.sortButton.getY() + (this.sortButton.getHeight() - 16) / 2; - ItemStack stack = ITEM_SORTER.getCurrentSortingItem(); - context.drawItemWithoutEntity(stack, iconX, iconY); - this.sortButton.render(context, mouseX, mouseY, delta); - } - - if (this.filterButton.active) { - int iconX = this.filterButton.getX() + (this.filterButton.getWidth() - 16) / 2; - int iconY = this.filterButton.getY() + (this.filterButton.getHeight() - 16) / 2; - ItemStack stack = ITEM_FILTER.getCurrentFilterItem(); - context.drawItemWithoutEntity(stack, iconX, iconY); - this.filterButton.render(context, mouseX, mouseY, delta); - } - - // Render the page flip buttons - if (this.prevPageButton.active) this.prevPageButton.render(context, mouseX, mouseY, delta); - if (this.nextPageButton.active) this.nextPageButton.render(context, mouseX, mouseY, delta); - - this.searchField.render(context, mouseX, mouseY, delta); - - this.drawTooltip(context, mouseX, mouseY); - } - - public void drawTooltip(DrawContext context, int x, int y) { - // Draw the tooltip of the hovered result button if one is hovered over - if (this.hoveredDonationButton != null && !this.hoveredDonationButton.getDisplayStack().isEmpty()) { - ItemStack stack = this.hoveredDonationButton.getDisplayStack(); - Identifier tooltipStyle = stack.get(DataComponentTypes.TOOLTIP_STYLE); - - context.drawTooltip(TEXT_RENDERER, hoveredDonationButton.getItemTooltip(), x, y, tooltipStyle); - } - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (this.searchField.mouseClicked(mouseX, mouseY, button)) { - this.searchField.setFocused(true); - return true; - } else if (this.nextPageButton.mouseClicked(mouseX, mouseY, button)) { - CURRENT_PAGE++; - updateButtons(); - return true; - } else if (this.prevPageButton.mouseClicked(mouseX, mouseY, button)) { - CURRENT_PAGE--; - updateButtons(); - return true; - } else if (this.filterButton.mouseClicked(mouseX, mouseY, button)) { - return true; - } else if (this.sortButton.mouseClicked(mouseX, mouseY, button)) { - return true; - } - - this.searchField.setFocused(false); - this.filterButton.setFocused(false); - this.sortButton.setFocused(false); - return false; - } - - @Override - public boolean keyReleased(int keyCode, int scanCode, int modifiers) { - return super.keyReleased(keyCode, scanCode, modifiers); - } - - @Override - public boolean charTyped(char chr, int modifiers) { - if (this.searchField.charTyped(chr, modifiers)) { - updateSearchResults(true); - return true; - } - return false; - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if ((this.searchField.isActive() && INVENTORY_OPEN_KEY.matchesKey(keyCode, scanCode)) - || this.searchField.keyPressed(keyCode, scanCode, modifiers)) { - updateSearchResults(true); - return true; - } else if (WikiLookup.wikiLookup.matchesKey(keyCode, scanCode) && hoveredDonationButton != null && hoveredDonationButton.getDisplayStack() != null) { - WikiLookup.openWiki(hoveredDonationButton.getDisplayStack(), CLIENT.player); - return true; - } - return false; - } - - @Override - protected void appendClickableNarrations(NarrationMessageBuilder builder) {} + private static final Identifier BACKGROUND_TEXTURE = Identifier.ofVanilla("textures/gui/recipe_book.png"); + private static final int BACKGROUND_WIDTH = 147; + private static final int BACKGROUND_HEIGHT = 166; + private static final int SEARCH_FIELD_WIDTH = 69; + private static final int SEARCH_FIELD_HEIGHT = 20; + private static final int BUTTON_SIZE = 20; + private static final int BUTTONS_PER_PAGE = 12; + private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); + private static final ItemSorter ITEM_SORTER = new ItemSorter(); + private static final ItemFilter ITEM_FILTER = new ItemFilter(); + private static final TextRenderer TEXT_RENDERER = CLIENT.textRenderer; + private static final KeyBinding INVENTORY_OPEN_KEY = CLIENT.options.inventoryKey; + private static String SEARCH_QUERY = ""; + private static int CURRENT_PAGE = 0; + private static List donations = new ArrayList<>(); + private final int layoutX; + private final int layoutY; + private final ToggleButtonWidget nextPageButton; + private final ToggleButtonWidget prevPageButton; + private final TextFieldWidget searchField; + private final List filteredDonations = new ArrayList<>(); + private final List excludedDonationIds = new ArrayList<>(); + private final List donationButtons = Lists.newArrayListWithCapacity(BUTTONS_PER_PAGE); + private final ButtonWidget filterButton; + private final ButtonWidget sortButton; + private DonationButton hoveredDonationButton; + private int pageCount = 0; + + public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { + super(x, y, screen.width, screen.height, Text.empty()); + this.layoutX = getX() + backgroundWidth + 2; + this.layoutY = y; + + // Initialize search field + this.searchField = new TextFieldWidget(TEXT_RENDERER, layoutX + 25, layoutY + 11, SEARCH_FIELD_WIDTH, SEARCH_FIELD_HEIGHT, Text.empty()); + this.searchField.setMaxLength(60); + this.searchField.setVisible(true); + this.searchField.setEditableColor(0xFFFFFF); + this.searchField.setText(SEARCH_QUERY); + this.searchField.setPlaceholder(Text.translatable("gui.recipebook.search_hint").formatted(Formatting.ITALIC).formatted(Formatting.GRAY)); + + // Initialize page navigation buttons + this.nextPageButton = new ToggleButtonWidget(layoutX + 93, layoutY + 133, 12, 17, false); + this.nextPageButton.setTextures(RecipeBookResults.PAGE_FORWARD_TEXTURES); + this.prevPageButton = new ToggleButtonWidget(layoutX + 38, layoutY + 133, 12, 17, true); + this.prevPageButton.setTextures(RecipeBookResults.PAGE_BACKWARD_TEXTURES); + + donations = MuseumItemCache.getDonations(); + + // Create donation buttons for pagination + for (int i = 0; i < BUTTONS_PER_PAGE; i++) { + DonationButton button = new DonationButton(layoutX + 11 + 31 * (i % 4), layoutY + 34 + 31 * (i / 4)); + this.donationButtons.add(button); + } + + // Initialize sort button + this.sortButton = ButtonWidget.builder(Text.empty(), button -> { + ITEM_SORTER.cycleSortMode(filteredDonations); + button.setTooltip(ITEM_SORTER.getTooltip()); + CURRENT_PAGE = 0; + updateButtons(); + }) + .tooltip(ITEM_SORTER.getTooltip()) + .position(layoutX + 95, layoutY + 11) + .size(BUTTON_SIZE, BUTTON_SIZE) + .build(); + + // Initialize filter button + this.filterButton = ButtonWidget.builder(Text.empty(), button -> { + ITEM_FILTER.cycleFilterMode(donations, filteredDonations); + ITEM_SORTER.applySort(filteredDonations); + button.setTooltip(ITEM_FILTER.getTooltip()); + CURRENT_PAGE = 0; + updateButtons(); + }) + .tooltip(ITEM_FILTER.getTooltip()) + .position(layoutX + 116, layoutY + 11) + .size(BUTTON_SIZE, BUTTON_SIZE) + .build(); + + ITEM_FILTER.applyFilter(donations, filteredDonations); + ITEM_SORTER.applySort(filteredDonations); + updateSearchResults(false); + + Screens.getButtons(screen).add(this); + screen.setFocused(this); + } + + /** + * Retrieves the Donation object corresponding to a given ID. + * + * @param id the ID of the donation to retrieve + * @return the Donation object associated with the given ID, or null if not found + */ + protected static Donation getDonation(String id) { + return donations.stream() + .filter(donation -> donation.getId().equals(id)) + .findFirst() + .orElse(null); + } + + /** + * Resets the UI state including search text, current page, sorting, and filtering. + */ + public static void reset() { + SEARCH_QUERY = ""; + CURRENT_PAGE = 0; + ITEM_SORTER.resetSorting(); + ITEM_FILTER.resetFilter(); + } + + /** + * Updates visibility and content of page navigation buttons. + */ + private void updateNavigationButtons() { + this.prevPageButton.active = CURRENT_PAGE > 0; + this.nextPageButton.active = CURRENT_PAGE < pageCount - 1; + } + + /** + * Updates the donation buttons based on the current page and visible donations. + */ + private void updateButtons() { + List visibleDonations = filteredDonations.stream() + .filter(donation -> !excludedDonationIds.contains(donation.getId())) + .toList(); + + int buttonsSize = visibleDonations.size(); + this.pageCount = (int) Math.ceil((double) buttonsSize / BUTTONS_PER_PAGE); + + for (int i = 0; i < donationButtons.size(); ++i) { + int index = CURRENT_PAGE * donationButtons.size() + i; + + if (index < buttonsSize) { + donationButtons.get(i).init(visibleDonations.get(index)); + } else { + donationButtons.get(i).clearDisplayStack(); + } + } + updateNavigationButtons(); + } + + /** + * Updates search results based on the search text. + * + * @param resetPage Whether to reset to the first page. + */ + public void updateSearchResults(boolean resetPage) { + SEARCH_QUERY = this.searchField.getText(); + excludedDonationIds.clear(); + for (Donation item : donations) { + StringBuilder searchableContent = new StringBuilder(); + ItemStack itemStack = ItemRepository.getItemStack(item.getId()); + if (itemStack != null) { + searchableContent.append(itemStack.getName().getString()) + .append(ItemUtils.getConcatenatedLore(itemStack)); + } + if (item.getSet() != null && !item.getSet().isEmpty()) { + for (ObjectObjectMutablePair piece : item.getSet()) { + ItemStack pieceStack = ItemRepository.getItemStack(piece.left()); + if (pieceStack != null) searchableContent.append(pieceStack.getName().getString()) + .append(ItemUtils.getConcatenatedLore(pieceStack)); + } + } + if (!searchableContent.toString().toLowerCase(Locale.ENGLISH).contains(SEARCH_QUERY.toLowerCase(Locale.ENGLISH))) { + excludedDonationIds.add(item.getId()); + } + } + if (resetPage) CURRENT_PAGE = 0; + updateButtons(); + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + // Render the background texture for the widget + context.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_TEXTURE, layoutX, layoutY, 1.0f, 1.0f, BACKGROUND_WIDTH, BACKGROUND_HEIGHT, 256, 256 - 10); + + // Render page count if multiple pages exist + if (this.pageCount > 1) { + Text text = Text.translatable("gui.recipebook.page", CURRENT_PAGE + 1, this.pageCount); + int width = TEXT_RENDERER.getWidth(text); + + context.drawText(TEXT_RENDERER, text, layoutX - width / 2 + 73, layoutY + 137, -1, false); + } + + // Render donation buttons + this.hoveredDonationButton = null; + for (DonationButton resultButton : donationButtons) { + resultButton.render(context, mouseX, mouseY, delta); + + if (resultButton.visible && resultButton.isHovered()) this.hoveredDonationButton = resultButton; + } + + if (this.sortButton.active) { + int iconX = this.sortButton.getX() + (this.sortButton.getWidth() - 16) / 2; + int iconY = this.sortButton.getY() + (this.sortButton.getHeight() - 16) / 2; + ItemStack stack = ITEM_SORTER.getCurrentSortingItem(); + context.drawItemWithoutEntity(stack, iconX, iconY); + this.sortButton.render(context, mouseX, mouseY, delta); + } + + if (this.filterButton.active) { + int iconX = this.filterButton.getX() + (this.filterButton.getWidth() - 16) / 2; + int iconY = this.filterButton.getY() + (this.filterButton.getHeight() - 16) / 2; + ItemStack stack = ITEM_FILTER.getCurrentFilterItem(); + context.drawItemWithoutEntity(stack, iconX, iconY); + this.filterButton.render(context, mouseX, mouseY, delta); + } + + // Render the page flip buttons + if (this.prevPageButton.active) this.prevPageButton.render(context, mouseX, mouseY, delta); + if (this.nextPageButton.active) this.nextPageButton.render(context, mouseX, mouseY, delta); + + this.searchField.render(context, mouseX, mouseY, delta); + + this.drawTooltip(context, mouseX, mouseY); + } + + public void drawTooltip(DrawContext context, int x, int y) { + // Draw the tooltip of the hovered result button if one is hovered over + if (this.hoveredDonationButton != null && !this.hoveredDonationButton.getDisplayStack().isEmpty()) { + ItemStack stack = this.hoveredDonationButton.getDisplayStack(); + Identifier tooltipStyle = stack.get(DataComponentTypes.TOOLTIP_STYLE); + + context.drawTooltip(TEXT_RENDERER, hoveredDonationButton.getItemTooltip(), x, y, tooltipStyle); + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (this.searchField.mouseClicked(mouseX, mouseY, button)) { + this.searchField.setFocused(true); + return true; + } else if (this.nextPageButton.mouseClicked(mouseX, mouseY, button)) { + CURRENT_PAGE++; + updateButtons(); + return true; + } else if (this.prevPageButton.mouseClicked(mouseX, mouseY, button)) { + CURRENT_PAGE--; + updateButtons(); + return true; + } else if (this.filterButton.mouseClicked(mouseX, mouseY, button)) { + return true; + } else if (this.sortButton.mouseClicked(mouseX, mouseY, button)) { + return true; + } + + this.searchField.setFocused(false); + this.filterButton.setFocused(false); + this.sortButton.setFocused(false); + return false; + } + + @Override + public boolean keyReleased(int keyCode, int scanCode, int modifiers) { + return super.keyReleased(keyCode, scanCode, modifiers); + } + + @Override + public boolean charTyped(char chr, int modifiers) { + if (this.searchField.charTyped(chr, modifiers)) { + updateSearchResults(true); + return true; + } + return false; + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if ((this.searchField.isActive() && INVENTORY_OPEN_KEY.matchesKey(keyCode, scanCode)) + || this.searchField.keyPressed(keyCode, scanCode, modifiers)) { + updateSearchResults(true); + return true; + } else if (WikiLookup.wikiLookup.matchesKey(keyCode, scanCode) && hoveredDonationButton != null && hoveredDonationButton.getDisplayStack() != null) { + WikiLookup.openWiki(hoveredDonationButton.getDisplayStack(), CLIENT.player); + return true; + } + return false; + } + + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) { + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java index d5b8ed5a88..e508f503b5 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java @@ -27,7 +27,7 @@ protected static double getSetCraftCost(String id) { double cost = 0; for (Donation donation : MuseumItemCache.MUSEUM_DONATIONS) { if (donation.getId().equals(id)) { - for (ObjectObjectMutablePair piece : donation.getSet()){ + for (ObjectObjectMutablePair piece : donation.getSet()) { cost += ItemUtils.getCraftCost(piece.left()); } } @@ -39,7 +39,7 @@ protected static double getSetCraftCost(String id) { * Retrieves the display name for an item or a set. * If the item is part of a set, it returns the set's name like "Divan's armor". * - * @param id the ID of the item or set + * @param id the ID of the item or set * @param isSet true if the ID refers to a set, false if it refers to an individual item * @return the display name of the item or set */ diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/PriceData.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/PriceData.java index 9aa8b29cfb..4a968c9e66 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/PriceData.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/PriceData.java @@ -4,48 +4,48 @@ import it.unimi.dsi.fastutil.objects.ObjectObjectMutablePair; public class PriceData { - private final double lBinPrice; - private final double craftCost; - private double effectivePrice; - - public PriceData(double lBinPrice, double craftCost) { - this.lBinPrice = lBinPrice; - this.craftCost = craftCost; - } - - public PriceData(Donation donation) { - if (donation.isSet()) { - double totalLBinPrice = 0, totalCraftCost = 0; - for (ObjectObjectMutablePair piece : donation.getSet()) { - double lBinPrice = ItemUtils.getItemPrice(piece.left()).leftDouble(); - double craftCost = ItemUtils.getCraftCost(piece.left()); - - totalLBinPrice += lBinPrice; - totalCraftCost += craftCost; - - piece.right(new PriceData(lBinPrice, craftCost)); - } - this.lBinPrice = totalLBinPrice; - this.craftCost = totalCraftCost; - } else { - this.lBinPrice = ItemUtils.getItemPrice(donation.getId()).leftDouble(); - this.craftCost = ItemUtils.getCraftCost(donation.getId()); - } - } - - public double getLBinPrice() { - return lBinPrice; - } - - public double getCraftCost() { - return craftCost; - } - - public double getEffectivePrice() { - return effectivePrice; - } - - public void setEffectivePrice(double effectivePrice) { - this.effectivePrice = effectivePrice; - } + private final double lBinPrice; + private final double craftCost; + private double effectivePrice; + + public PriceData(double lBinPrice, double craftCost) { + this.lBinPrice = lBinPrice; + this.craftCost = craftCost; + } + + public PriceData(Donation donation) { + if (donation.isSet()) { + double totalLBinPrice = 0, totalCraftCost = 0; + for (ObjectObjectMutablePair piece : donation.getSet()) { + double lBinPrice = ItemUtils.getItemPrice(piece.left()).leftDouble(); + double craftCost = ItemUtils.getCraftCost(piece.left()); + + totalLBinPrice += lBinPrice; + totalCraftCost += craftCost; + + piece.right(new PriceData(lBinPrice, craftCost)); + } + this.lBinPrice = totalLBinPrice; + this.craftCost = totalCraftCost; + } else { + this.lBinPrice = ItemUtils.getItemPrice(donation.getId()).leftDouble(); + this.craftCost = ItemUtils.getCraftCost(donation.getId()); + } + } + + public double getLBinPrice() { + return lBinPrice; + } + + public double getCraftCost() { + return craftCost; + } + + public double getEffectivePrice() { + return effectivePrice; + } + + public void setEffectivePrice(double effectivePrice) { + this.effectivePrice = effectivePrice; + } } From 08c01065a565716cb63d27100d1cf1b39e4e4031 Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Fri, 13 Dec 2024 15:41:11 +0100 Subject: [PATCH 08/17] fixed bug --- .../skyblocker/skyblock/museum/DonationButton.java | 3 +-- .../skyblock/museum/MuseumItemCache.java | 14 ++++++++++++++ .../skyblocker/skyblock/museum/MuseumManager.java | 3 +-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java index fd02a30dfe..c4869040f3 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java @@ -172,6 +172,5 @@ protected List getItemTooltip() { @Override - protected void appendClickableNarrations(NarrationMessageBuilder builder) { - } + protected void appendClickableNarrations(NarrationMessageBuilder builder) {} } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java index 04ebb7d447..294715b704 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java @@ -265,6 +265,7 @@ public static void handleClick(Slot slot, int slotId, DefaultedList slots) if (!itemId.isEmpty() && !profileId.isEmpty()) { if (MAPPED_IDS.containsKey(itemId)) itemId = MAPPED_IDS.get(itemId); String setId = MuseumUtils.getSetID(itemId); + Donation donation = MuseumManager.getDonation(setId != null ? setId : itemId); String uuid = Utils.getUndashedUuid(); //Be safe about access to avoid NPEs Map playerData = MUSEUM_ITEM_CACHE.computeIfAbsent(uuid, _uuid -> new Object2ObjectOpenHashMap<>()); @@ -272,6 +273,19 @@ public static void handleClick(Slot slot, int slotId, DefaultedList slots) playerData.get(profileId).collectedItemIds().add(itemId); if (setId != null) playerData.get(profileId).collectedItemIds().add(setId); + System.out.println(itemId); + System.out.println(setId); + if (donation != null && !donation.getDowngrades().isEmpty()) { + for (String downgrade : donation.getDowngrades()) { + if (donation.isSet()) { + List pieces = MuseumUtils.getPiecesBySetID(downgrade); + playerData.get(profileId).collectedItemIds().addAll(pieces); + System.out.println(pieces); + } + playerData.get(profileId).collectedItemIds().add(downgrade); + System.out.println(downgrade); + } + } } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java index a13761cf28..db75d19ab0 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java @@ -310,6 +310,5 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { } @Override - protected void appendClickableNarrations(NarrationMessageBuilder builder) { - } + protected void appendClickableNarrations(NarrationMessageBuilder builder) {} } From 79ebca6a4f6db737a46e8bb2db426b3383d2b0e3 Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Fri, 13 Dec 2024 16:12:06 +0100 Subject: [PATCH 09/17] ... --- .../de/hysky/skyblocker/skyblock/museum/MuseumManager.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java index db75d19ab0..868f5fa117 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java @@ -114,7 +114,6 @@ public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { updateSearchResults(false); Screens.getButtons(screen).add(this); - screen.setFocused(this); } /** @@ -277,8 +276,7 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { } this.searchField.setFocused(false); - this.filterButton.setFocused(false); - this.sortButton.setFocused(false); + return false; } From 5dd8e17c4ec04c8c0359aee9c3afef7bbe5480ae Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Fri, 13 Dec 2024 22:07:48 +0100 Subject: [PATCH 10/17] fixed mouse clicks not registering --- .../skyblocker/mixins/HandledScreenMixin.java | 15 +++++++++++++++ .../skyblocker/skyblock/museum/MuseumManager.java | 1 + 2 files changed, 16 insertions(+) diff --git a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java index a0d714d833..08d790302d 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java @@ -190,6 +190,21 @@ protected HandledScreenMixin(Text title) { return superClicked; } + @ModifyExpressionValue(method = "mouseClicked", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseClicked(DDI)Z")) + public boolean skyblocker$museumOverlayMouseClicked(boolean superClicked, double mouseX, double mouseY, int button) { + Optional widget = Screens.getButtons(this).stream() + .filter(MuseumManager.class::isInstance) + .findFirst(); + + if (widget.isPresent()) { + return false; + } + + return superClicked; + } + + + /** * Draws the unselected tabs in front of the background blur, but behind the main inventory, similar to creative inventory tabs */ diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java index 868f5fa117..fd020b17fc 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java @@ -114,6 +114,7 @@ public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { updateSearchResults(false); Screens.getButtons(screen).add(this); + screen.setFocused(this); } /** From 524341586a0c56af96cce129b064760858cffec3 Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Sat, 14 Dec 2024 11:37:56 +0100 Subject: [PATCH 11/17] Fix --- .../skyblocker/mixins/HandledScreenMixin.java | 15 ------------ .../skyblock/museum/MuseumManager.java | 24 +++++++------------ 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java index 08d790302d..a0d714d833 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java @@ -190,21 +190,6 @@ protected HandledScreenMixin(Text title) { return superClicked; } - @ModifyExpressionValue(method = "mouseClicked", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseClicked(DDI)Z")) - public boolean skyblocker$museumOverlayMouseClicked(boolean superClicked, double mouseX, double mouseY, int button) { - Optional widget = Screens.getButtons(this).stream() - .filter(MuseumManager.class::isInstance) - .findFirst(); - - if (widget.isPresent()) { - return false; - } - - return superClicked; - } - - - /** * Draws the unselected tabs in front of the background blur, but behind the main inventory, similar to creative inventory tabs */ diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java index fd020b17fc..4bda95ece2 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java @@ -30,8 +30,6 @@ public class MuseumManager extends ClickableWidget { private static final Identifier BACKGROUND_TEXTURE = Identifier.ofVanilla("textures/gui/recipe_book.png"); - private static final int BACKGROUND_WIDTH = 147; - private static final int BACKGROUND_HEIGHT = 166; private static final int SEARCH_FIELD_WIDTH = 69; private static final int SEARCH_FIELD_HEIGHT = 20; private static final int BUTTON_SIZE = 20; @@ -44,8 +42,6 @@ public class MuseumManager extends ClickableWidget { private static String SEARCH_QUERY = ""; private static int CURRENT_PAGE = 0; private static List donations = new ArrayList<>(); - private final int layoutX; - private final int layoutY; private final ToggleButtonWidget nextPageButton; private final ToggleButtonWidget prevPageButton; private final TextFieldWidget searchField; @@ -58,12 +54,10 @@ public class MuseumManager extends ClickableWidget { private int pageCount = 0; public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { - super(x, y, screen.width, screen.height, Text.empty()); - this.layoutX = getX() + backgroundWidth + 2; - this.layoutY = y; + super(x + backgroundWidth + 2, y, 147, 160, Text.empty()); // Initialize search field - this.searchField = new TextFieldWidget(TEXT_RENDERER, layoutX + 25, layoutY + 11, SEARCH_FIELD_WIDTH, SEARCH_FIELD_HEIGHT, Text.empty()); + this.searchField = new TextFieldWidget(TEXT_RENDERER, getX() + 25, getY() + 11, SEARCH_FIELD_WIDTH, SEARCH_FIELD_HEIGHT, Text.empty()); this.searchField.setMaxLength(60); this.searchField.setVisible(true); this.searchField.setEditableColor(0xFFFFFF); @@ -71,16 +65,16 @@ public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { this.searchField.setPlaceholder(Text.translatable("gui.recipebook.search_hint").formatted(Formatting.ITALIC).formatted(Formatting.GRAY)); // Initialize page navigation buttons - this.nextPageButton = new ToggleButtonWidget(layoutX + 93, layoutY + 133, 12, 17, false); + this.nextPageButton = new ToggleButtonWidget(getX() + 93, getY() + 133, 12, 17, false); this.nextPageButton.setTextures(RecipeBookResults.PAGE_FORWARD_TEXTURES); - this.prevPageButton = new ToggleButtonWidget(layoutX + 38, layoutY + 133, 12, 17, true); + this.prevPageButton = new ToggleButtonWidget(getX() + 38, getY() + 133, 12, 17, true); this.prevPageButton.setTextures(RecipeBookResults.PAGE_BACKWARD_TEXTURES); donations = MuseumItemCache.getDonations(); // Create donation buttons for pagination for (int i = 0; i < BUTTONS_PER_PAGE; i++) { - DonationButton button = new DonationButton(layoutX + 11 + 31 * (i % 4), layoutY + 34 + 31 * (i / 4)); + DonationButton button = new DonationButton(getX() + 11 + 31 * (i % 4), getY() + 34 + 31 * (i / 4)); this.donationButtons.add(button); } @@ -92,7 +86,7 @@ public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { updateButtons(); }) .tooltip(ITEM_SORTER.getTooltip()) - .position(layoutX + 95, layoutY + 11) + .position(getX() + 95, getY() + 11) .size(BUTTON_SIZE, BUTTON_SIZE) .build(); @@ -105,7 +99,7 @@ public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { updateButtons(); }) .tooltip(ITEM_FILTER.getTooltip()) - .position(layoutX + 116, layoutY + 11) + .position(getX() + 116, getY() + 11) .size(BUTTON_SIZE, BUTTON_SIZE) .build(); @@ -204,14 +198,14 @@ public void updateSearchResults(boolean resetPage) { @Override protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { // Render the background texture for the widget - context.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_TEXTURE, layoutX, layoutY, 1.0f, 1.0f, BACKGROUND_WIDTH, BACKGROUND_HEIGHT, 256, 256 - 10); + context.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_TEXTURE, getX(), getY(), 1.0f, 1.0f, getWidth(), getHeight(), 256, 256 - 10); // Render page count if multiple pages exist if (this.pageCount > 1) { Text text = Text.translatable("gui.recipebook.page", CURRENT_PAGE + 1, this.pageCount); int width = TEXT_RENDERER.getWidth(text); - context.drawText(TEXT_RENDERER, text, layoutX - width / 2 + 73, layoutY + 137, -1, false); + context.drawText(TEXT_RENDERER, text, getX() - width / 2 + 73, getY() + 137, -1, false); } // Render donation buttons From c2eb4130b2622016bdfcecfa045a63ad47632add Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Sat, 14 Dec 2024 11:54:39 +0100 Subject: [PATCH 12/17] ... --- .../skyblocker/mixins/HandledScreenMixin.java | 9 ++++++--- .../skyblocker/skyblock/museum/Donation.java | 2 +- .../de/hysky/skyblocker/utils/ItemUtils.java | 20 +++++++++---------- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java index a0d714d833..8fd5dce3e1 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java @@ -3,7 +3,6 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.sugar.Local; import com.mojang.blaze3d.systems.RenderSystem; - import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.SkyblockerConfigManager; @@ -13,7 +12,10 @@ import de.hysky.skyblocker.skyblock.experiment.SuperpairsSolver; import de.hysky.skyblocker.skyblock.experiment.UltrasequencerSolver; import de.hysky.skyblocker.skyblock.garden.VisitorHelper; -import de.hysky.skyblocker.skyblock.item.*; +import de.hysky.skyblocker.skyblock.item.ItemPrice; +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.SlotTextManager; import de.hysky.skyblocker.skyblock.item.tooltip.BackpackPreview; import de.hysky.skyblocker.skyblock.item.tooltip.CompactorDeletorPreview; @@ -337,7 +339,8 @@ protected HandledScreenMixin(Text title) { } } - case GenericContainerScreenHandler genericContainerScreenHandler when title.equals(MuseumItemCache.DONATION_CONFIRMATION_SCREEN_TITLE) -> //Museum Item Cache donation tracking + //Museum Item Cache donation tracking + case GenericContainerScreenHandler genericContainerScreenHandler when title.equals(MuseumItemCache.DONATION_CONFIRMATION_SCREEN_TITLE) -> MuseumItemCache.handleClick(slot, slotId, genericContainerScreenHandler.slots); case null, default -> {} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java index 5a90d95e12..5fb893fb80 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java @@ -13,7 +13,7 @@ public class Donation { private final List> set; private final List downgrades = new ArrayList<>(); private final int xp; - private List> countsTowards;// downgrades not donated + private List> countsTowards; private PriceData priceData; private ObjectDoublePair discount; private int totalXp; diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java index 985e755233..8e394160a0 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java @@ -324,8 +324,8 @@ public static PetInfo getPetInfo(ComponentHolder stack) { return DoubleBooleanPair.of(0, false); } - public static double getCraftCost(String neuId) { - NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(neuId); + public static double getCraftCost(String skyblockApiId) { + NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(skyblockApiId); if (neuItem != null && !neuItem.getRecipes().isEmpty()) { return CraftPriceTooltip.getItemCost(neuItem.getRecipes().getFirst(), 0); } @@ -489,14 +489,14 @@ public static Matcher getLoreLineIfContainsMatch(ItemStack stack, Pattern patter /** * Checks if the given item ID represents an equipment piece. */ - public static boolean isEquipment(String itemId) { - return (itemId.contains("BELT") || - itemId.contains("GLOVES") || - itemId.contains("CLOAK") || - itemId.contains("GAUNTLET") || - itemId.contains("NECKLACE") || - itemId.contains("BRACELET") || - itemId.contains("HAT")); + public static boolean isEquipment(String skyblockApiId) { + return (skyblockApiId.contains("BELT") || + skyblockApiId.contains("GLOVES") || + skyblockApiId.contains("CLOAK") || + skyblockApiId.contains("GAUNTLET") || + skyblockApiId.contains("NECKLACE") || + skyblockApiId.contains("BRACELET") || + skyblockApiId.contains("HAT")); } public static boolean isSoulbound(ItemStack stack) { From 4a307682f0d720a19a45e10efcabae79e93d368e Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Sun, 22 Dec 2024 11:39:48 +0100 Subject: [PATCH 13/17] bug fixes --- .../skyblocker/skyblock/museum/Donation.java | 1 - .../skyblock/museum/MuseumItemCache.java | 185 +++++++++--------- .../skyblock/museum/MuseumUtils.java | 3 +- 3 files changed, 95 insertions(+), 94 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java index 5fb893fb80..276daaa7a1 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java @@ -86,7 +86,6 @@ public String getId() { return id; } - public boolean isSet() { return !set.isEmpty(); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java index 294715b704..ac130512c9 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java @@ -13,6 +13,7 @@ import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.utils.*; import de.hysky.skyblocker.utils.Http.ApiResponse; +import io.github.moulberry.repo.NEURepoFile; import it.unimi.dsi.fastutil.objects.*; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; @@ -39,21 +40,22 @@ import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; public class MuseumItemCache { - public static final Map ARMOR_NAMES = new Object2ObjectArrayMap<>(); - public static final String DONATION_CONFIRMATION_SCREEN_TITLE = "Confirm Donation"; - public static final Map MAPPED_IDS = new Object2ObjectArrayMap<>(); - public static final List MUSEUM_DONATIONS = new ArrayList<>(); private static final Logger LOGGER = LoggerFactory.getLogger(MuseumItemCache.class); - private static final Path CACHE_FILE = SkyblockerMod.CONFIG_DIR.resolve("museum_item_cache.json"); - private static final Map> MUSEUM_ITEM_CACHE = new Object2ObjectOpenHashMap<>(); private static final String ERROR_LOG_TEMPLATE = "[Skyblocker] Failed to refresh museum item data for profile {}"; private static final int CONFIRM_DONATION_BUTTON_SLOT = 20; - private static final Path MUSEUM_INFO = NEURepoManager.NEU_REPO.file("constants/museum.json").getFsPath(); - public static List> ORDERED_UPGRADES = new ArrayList<>(); + private static final String CONSTANTS_MUSEUM_DATA = "constants/museum.json"; + private static final Path CACHE_FILE = SkyblockerMod.CONFIG_DIR.resolve("museum_item_cache.json"); + private static final Map> MUSEUM_ITEM_CACHE = new Object2ObjectOpenHashMap<>(); + public static final String DONATION_CONFIRMATION_SCREEN_TITLE = "Confirm Donation"; + public static final Map ARMOR_NAMES = new Object2ObjectArrayMap<>(); + public static final Map MAPPED_IDS = new Object2ObjectArrayMap<>(); + public static final ObjectArrayList MUSEUM_DONATIONS = new ObjectArrayList<>(); + public static final ObjectArrayList> ORDERED_UPGRADES = new ObjectArrayList<>(); private static CompletableFuture loaded; @Init public static void init() { + loadMuseumItems(); ClientLifecycleEvents.CLIENT_STARTED.register(MuseumItemCache::load); ClientCommandRegistrationCallback.EVENT.register(MuseumItemCache::registerCommands); } @@ -75,7 +77,6 @@ private static void registerCommands(CommandDispatcher { - loadMuseumItems(); try (BufferedReader reader = Files.newBufferedReader(CACHE_FILE)) { Map> cachedData = ProfileMuseumData.SERIALIZATION_CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).getOrThrow(); @@ -102,94 +103,98 @@ private static void save() { * Loads museum data from local repo. */ public static void loadMuseumItems() { - try (BufferedReader reader = Files.newBufferedReader(MUSEUM_INFO)) { - // Parse the JSON file - JsonObject json = JsonParser.parseReader(reader).getAsJsonObject(); - - Map setExceptions = json.get("set_exceptions").getAsJsonObject().asMap(); - Map mappedIds = json.get("mapped_ids").getAsJsonObject().asMap(); - Map itemToXp = json.get("itemToXp").getAsJsonObject().asMap(); - Map setsToItems = json.get("sets_to_items").getAsJsonObject().asMap(); - Map children = json.get("children").getAsJsonObject().asMap(); - - Map allDonations = Map.of( - "weapons", json.get("weapons").getAsJsonArray(), - "armor", json.get("armor").getAsJsonArray(), - "rarities", json.get("rarities").getAsJsonArray() - ); - - mappedIds.forEach((s, jsonElement) -> MAPPED_IDS.put(s, jsonElement.getAsString())); - - for (Map.Entry entry : allDonations.entrySet()) { - String category = entry.getKey(); - JsonArray array = entry.getValue(); - - for (JsonElement element : array) { - String itemID = element.getAsString(); - List> set = new ArrayList<>(); - if (category.equals("armor")) { - boolean isEquipment = true; - for (JsonElement jsonElement : setsToItems.get(itemID).getAsJsonArray()) { - if (isEquipment) isEquipment = ItemUtils.isEquipment(jsonElement.getAsString()); - set.add(new ObjectObjectMutablePair<>(jsonElement.getAsString(), null)); - } - String realId = itemID; - for (Map.Entry exception : setExceptions.entrySet()) { - if (exception.getValue().getAsString().equals(itemID)) { - realId = exception.getKey(); - break; + NEURepoManager.runAsyncAfterLoad(() -> { + NEURepoFile filePath = NEURepoManager.NEU_REPO.file(CONSTANTS_MUSEUM_DATA); + if (filePath == null) return; + try (BufferedReader reader = Files.newBufferedReader(filePath.getFsPath())) { + // Parse the JSON file + JsonObject json = JsonParser.parseReader(reader).getAsJsonObject(); + + Map setExceptions = json.get("set_exceptions").getAsJsonObject().asMap(); + Map mappedIds = json.get("mapped_ids").getAsJsonObject().asMap(); + Map itemToXp = json.get("itemToXp").getAsJsonObject().asMap(); + Map setsToItems = json.get("sets_to_items").getAsJsonObject().asMap(); + Map children = json.get("children").getAsJsonObject().asMap(); + + Map allDonations = Map.of( + "weapons", json.get("weapons").getAsJsonArray(), + "armor", json.get("armor").getAsJsonArray(), + "rarities", json.get("rarities").getAsJsonArray() + ); + + mappedIds.forEach((s, jsonElement) -> MAPPED_IDS.put(s, jsonElement.getAsString())); + + for (Map.Entry entry : allDonations.entrySet()) { + String category = entry.getKey(); + JsonArray array = entry.getValue(); + + for (JsonElement element : array) { + String itemID = element.getAsString(); + List> set = new ArrayList<>(); + if (category.equals("armor")) { + boolean isEquipment = true; + for (JsonElement jsonElement : setsToItems.get(itemID).getAsJsonArray()) { + if (isEquipment) isEquipment = ItemUtils.isEquipment(jsonElement.getAsString()); + set.add(new ObjectObjectMutablePair<>(jsonElement.getAsString(), null)); + } + String realId = itemID; + for (Map.Entry exception : setExceptions.entrySet()) { + if (exception.getValue().getAsString().equals(itemID)) { + realId = exception.getKey(); + break; + } } + ARMOR_NAMES.put(itemID, MuseumUtils.formatArmorName(realId, isEquipment)); } - ARMOR_NAMES.put(itemID, MuseumUtils.formatArmorName(realId, isEquipment)); - } - int itemXP = itemToXp.get(itemID).getAsInt(); - List upgrades = getUpgrades(children, itemID); - - if (!upgrades.isEmpty()) { - // Try to find an existing upgrade list that either contains itemID or overlaps with upgrades - Optional> matchingUpgrade = ORDERED_UPGRADES.stream() - .filter(orderedUpgrade -> - orderedUpgrade.contains(itemID) || - !Collections.disjoint(orderedUpgrade, upgrades)) - .findFirst(); - - if (matchingUpgrade.isPresent()) { - List orderedUpgrade = matchingUpgrade.get(); - // If the matching list has fewer or equal items, replace it with the new upgrade list - if (orderedUpgrade.size() <= upgrades.size()) { - orderedUpgrade.clear(); - orderedUpgrade.add(itemID); - orderedUpgrade.addAll(upgrades); + int itemXP = itemToXp.get(itemID).getAsInt(); + List upgrades = getUpgrades(children, itemID); + + if (!upgrades.isEmpty()) { + // Try to find an existing upgrade list that either contains itemID or overlaps with upgrades + Optional> matchingUpgrade = ORDERED_UPGRADES.stream() + .filter(orderedUpgrade -> + orderedUpgrade.contains(itemID) || + !Collections.disjoint(orderedUpgrade, upgrades)) + .findFirst(); + + if (matchingUpgrade.isPresent()) { + List orderedUpgrade = matchingUpgrade.get(); + // If the matching list has fewer or equal items, replace it with the new upgrade list + if (orderedUpgrade.size() <= upgrades.size()) { + orderedUpgrade.clear(); + orderedUpgrade.add(itemID); + orderedUpgrade.addAll(upgrades); + } + } else { + // If no match, add a new upgrade list with itemID and upgrades + ObjectArrayList newUpgrade = new ObjectArrayList<>(); + newUpgrade.add(itemID); + newUpgrade.addAll(upgrades); + ORDERED_UPGRADES.add(newUpgrade); } - } else { - // If no match, add a new upgrade list with itemID and upgrades - List newUpgrade = new ArrayList<>(); - newUpgrade.add(itemID); - newUpgrade.addAll(upgrades); - ORDERED_UPGRADES.add(newUpgrade); } - } - MUSEUM_DONATIONS.add(new Donation(category, itemID, set, itemXP)); + MUSEUM_DONATIONS.add(new Donation(category, itemID, set, itemXP)); + } } - } - MUSEUM_DONATIONS.forEach(donation -> { - for (List list : ORDERED_UPGRADES) { - int armorIndex = list.indexOf(donation.getId()); - if (armorIndex > 0) { - for (int i = armorIndex - 1; i >= 0; i--) { - donation.addDowngrade(list.get(i)); + MUSEUM_DONATIONS.forEach(donation -> { + for (List list : ORDERED_UPGRADES) { + int armorIndex = list.indexOf(donation.getId()); + if (armorIndex > 0) { + for (int i = armorIndex - 1; i >= 0; i--) { + donation.addDowngrade(list.get(i)); + } } } - } - }); + }); - LOGGER.info("[Skyblocker] Loaded museum data"); - } catch (NoSuchFileException ignored) { - } catch (IOException e) { - LOGGER.error("[Skyblocker] Failed to load donations data", e); - } + LOGGER.info("[Skyblocker] Loaded museum data"); + } catch (NoSuchFileException ignored) { + } catch (IOException e) { + LOGGER.error("[Skyblocker] Failed to load donations data", e); + } + }); } /** @@ -252,7 +257,7 @@ public static List getDonations() { return uncontributedItems; } - public static void handleClick(Slot slot, int slotId, DefaultedList slots) { + public static void handleClick(Slot ignored, int slotId, DefaultedList slots) { if (slotId == CONFIRM_DONATION_BUTTON_SLOT) { //Slots 0 to 17 can have items, well not all but thats the general range for (int i = 0; i < 17; i++) { @@ -273,17 +278,13 @@ public static void handleClick(Slot slot, int slotId, DefaultedList slots) playerData.get(profileId).collectedItemIds().add(itemId); if (setId != null) playerData.get(profileId).collectedItemIds().add(setId); - System.out.println(itemId); - System.out.println(setId); if (donation != null && !donation.getDowngrades().isEmpty()) { for (String downgrade : donation.getDowngrades()) { if (donation.isSet()) { List pieces = MuseumUtils.getPiecesBySetID(downgrade); playerData.get(profileId).collectedItemIds().addAll(pieces); - System.out.println(pieces); } playerData.get(profileId).collectedItemIds().add(downgrade); - System.out.println(downgrade); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java index e508f503b5..d6ab3e84cf 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java @@ -53,7 +53,8 @@ protected static Text getDisplayName(String id, boolean isSet) { if (!donation.get().getSet().isEmpty()) { Text pieceName = getDisplayName(donation.get().getSet().getFirst().left(), false); if (pieceName != null) { - nameStyle = pieceName.getSiblings().getFirst().getStyle(); + List siblings = pieceName.getSiblings(); + nameStyle = siblings.isEmpty() ? Style.EMPTY : siblings.getFirst().getStyle(); } } } From b88a2a00a5ffd9bd57fc7a4d47d9449c00f85b3e Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Sun, 22 Dec 2024 23:07:08 +0100 Subject: [PATCH 14/17] static block --- .../de/hysky/skyblocker/skyblock/museum/MuseumUtils.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java index d6ab3e84cf..0202aaa4ce 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java @@ -14,9 +14,12 @@ import java.util.stream.Collectors; public class MuseumUtils { - private static final NumberFormat NUMBER_FORMATTER_S = NumberFormat.getCompactNumberInstance(Locale.CANADA, NumberFormat.Style.SHORT); + static { + NUMBER_FORMATTER_S.setMaximumFractionDigits(1); + } + /** * Calculates the total crafting cost for a set associated with a given ID. * @@ -133,7 +136,6 @@ public static String formatArmorName(String id, boolean isEquipment) { * @return A formatted string (e.g., "10M", "5K", "1.2B"). */ public static String formatPrice(double value) { - NUMBER_FORMATTER_S.setMaximumFractionDigits(1); return NUMBER_FORMATTER_S.format(value); } } From 2331ad0fe0659f658c19e3366604d328a19e8755 Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Tue, 24 Dec 2024 12:08:30 +0100 Subject: [PATCH 15/17] prevent crash --- .../de/hysky/skyblocker/skyblock/museum/DonationButton.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java index c4869040f3..2f4825b2b7 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java @@ -46,6 +46,7 @@ protected ItemStack getDisplayStack() { * @param donation The donation to associate with this button. */ public void init(Donation donation) { + this.visible = false; this.donation = donation; this.textToRender = MuseumUtils.formatPrice(donation.getPriceData().getEffectivePrice()); @@ -60,7 +61,7 @@ public void init(Donation donation) { .left() ); - if (itemStack != null) { + if (itemStack != null && !itemStack.isEmpty()) { this.visible = true; createTooltip(); } From 539e0dd93ee96baf77fa87fea68c1308d9436d71 Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Fri, 10 Jan 2025 20:50:44 +0100 Subject: [PATCH 16/17] changes --- .../skyblocker/mixins/HandledScreenMixin.java | 2 +- .../skyblock/museum/DonationButton.java | 5 +-- .../skyblock/museum/ItemSorter.java | 16 ++++---- .../skyblock/museum/MuseumManager.java | 38 +++++++++---------- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java index b0301f60b0..a00014f1f5 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java @@ -133,7 +133,7 @@ protected HandledScreenMixin(Text title) { } } - @Inject(method = "close", at = @At("HEAD")) + @Inject(method = "removed", at = @At("HEAD")) private void skyblocker$removeMuseumOverlay(CallbackInfo ci) { if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().uiAndVisuals.museumOverlay && client != null && client.player != null && !client.player.isCreative() && getTitle().getString().contains("Museum")) { // Reset Overlay variables when no longer in Museum inventory diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java index 2f4825b2b7..5f3e52121f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java @@ -86,10 +86,7 @@ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float d int yOffset = 8; if (donation.hasPrice()) { - int textWidth = TEXT_RENDERER.getWidth(textToRender); - int centeredX = this.getX() + (this.width / 2) - (textWidth / 2); - int textY = this.getY() + ITEM_OFFSET + 13; - context.drawText(TEXT_RENDERER, textToRender, centeredX, textY, 0xFF00FF00, true); + context.drawCenteredTextWithShadow(TEXT_RENDERER, textToRender, this.getX() + (this.width / 2), this.getY() + ITEM_OFFSET + 13, 0xFF00FF00); yOffset -= 4; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java index b9729a1ef5..9d9a4ff04e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java @@ -27,7 +27,7 @@ public class ItemSorter { donations.forEach(donation -> updateDonationData(donation, true)); donations.sort(ItemSorter::compareCoinsPerXP); }; - private SortMode currentSortMode = SortMode.LowestBIN; + private SortMode currentSortMode = SortMode.LOWEST_BIN; // Set effective prices for the donation and its armor set pieces public static void updateDonationData(Donation donation, boolean useCraftCost) { @@ -112,14 +112,14 @@ public ItemStack getCurrentSortingItem() { } public void resetSorting() { - this.currentSortMode = SortMode.LowestBIN; + this.currentSortMode = SortMode.LOWEST_BIN; } public Tooltip getTooltip() { Text tooltip = Text.translatable("skyblocker.museum.hud.sorter").append("\n\n").formatted(Formatting.GREEN) - .append(getSortText(SortMode.LowestBIN)) - .append(getSortText(SortMode.CraftCost)) - .append(getSortText(SortMode.CoinsPerXP)) + .append(getSortText(SortMode.LOWEST_BIN)) + .append(getSortText(SortMode.CRAFT_COST)) + .append(getSortText(SortMode.COINS_PER_XP)) .append("\n").append(Text.translatable("skyblocker.museum.hud.sorter.switch").formatted(Formatting.YELLOW)); return Tooltip.of(tooltip); } @@ -131,9 +131,9 @@ private Text getSortText(SortMode mode) { } public enum SortMode { - LowestBIN(new ItemStack(Items.GOLD_INGOT), sortByLowestBIN, Text.translatable("skyblocker.museum.hud.sorter.lBin")), - CraftCost(new ItemStack(Items.CRAFTING_TABLE), sortByCraftCost, Text.translatable("skyblocker.museum.hud.sorter.craftCost")), - CoinsPerXP(new ItemStack(Items.EXPERIENCE_BOTTLE), sortByXpPerCoin, Text.translatable("skyblocker.museum.hud.sorter.ratio")); + LOWEST_BIN(new ItemStack(Items.GOLD_INGOT), sortByLowestBIN, Text.translatable("skyblocker.museum.hud.sorter.lBin")), + CRAFT_COST(new ItemStack(Items.CRAFTING_TABLE), sortByCraftCost, Text.translatable("skyblocker.museum.hud.sorter.craftCost")), + COINS_PER_XP(new ItemStack(Items.EXPERIENCE_BOTTLE), sortByXpPerCoin, Text.translatable("skyblocker.museum.hud.sorter.ratio")); private final ItemStack associatedItem; private final Consumer> sortFunction; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java index 4bda95ece2..2feb886f7c 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java @@ -29,18 +29,18 @@ import java.util.Locale; public class MuseumManager extends ClickableWidget { + private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); + private static final TextRenderer TEXT_RENDERER = CLIENT.textRenderer; + private static final KeyBinding INVENTORY_OPEN_KEY = CLIENT.options.inventoryKey; private static final Identifier BACKGROUND_TEXTURE = Identifier.ofVanilla("textures/gui/recipe_book.png"); private static final int SEARCH_FIELD_WIDTH = 69; private static final int SEARCH_FIELD_HEIGHT = 20; private static final int BUTTON_SIZE = 20; private static final int BUTTONS_PER_PAGE = 12; - private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); private static final ItemSorter ITEM_SORTER = new ItemSorter(); private static final ItemFilter ITEM_FILTER = new ItemFilter(); - private static final TextRenderer TEXT_RENDERER = CLIENT.textRenderer; - private static final KeyBinding INVENTORY_OPEN_KEY = CLIENT.options.inventoryKey; - private static String SEARCH_QUERY = ""; - private static int CURRENT_PAGE = 0; + private static String searchQuery = ""; + private static int currentPage = 0; private static List donations = new ArrayList<>(); private final ToggleButtonWidget nextPageButton; private final ToggleButtonWidget prevPageButton; @@ -61,7 +61,7 @@ public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { this.searchField.setMaxLength(60); this.searchField.setVisible(true); this.searchField.setEditableColor(0xFFFFFF); - this.searchField.setText(SEARCH_QUERY); + this.searchField.setText(searchQuery); this.searchField.setPlaceholder(Text.translatable("gui.recipebook.search_hint").formatted(Formatting.ITALIC).formatted(Formatting.GRAY)); // Initialize page navigation buttons @@ -82,7 +82,7 @@ public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { this.sortButton = ButtonWidget.builder(Text.empty(), button -> { ITEM_SORTER.cycleSortMode(filteredDonations); button.setTooltip(ITEM_SORTER.getTooltip()); - CURRENT_PAGE = 0; + currentPage = 0; updateButtons(); }) .tooltip(ITEM_SORTER.getTooltip()) @@ -95,7 +95,7 @@ public MuseumManager(Screen screen, int x, int y, int backgroundWidth) { ITEM_FILTER.cycleFilterMode(donations, filteredDonations); ITEM_SORTER.applySort(filteredDonations); button.setTooltip(ITEM_FILTER.getTooltip()); - CURRENT_PAGE = 0; + currentPage = 0; updateButtons(); }) .tooltip(ITEM_FILTER.getTooltip()) @@ -128,8 +128,8 @@ protected static Donation getDonation(String id) { * Resets the UI state including search text, current page, sorting, and filtering. */ public static void reset() { - SEARCH_QUERY = ""; - CURRENT_PAGE = 0; + searchQuery = ""; + currentPage = 0; ITEM_SORTER.resetSorting(); ITEM_FILTER.resetFilter(); } @@ -138,8 +138,8 @@ public static void reset() { * Updates visibility and content of page navigation buttons. */ private void updateNavigationButtons() { - this.prevPageButton.active = CURRENT_PAGE > 0; - this.nextPageButton.active = CURRENT_PAGE < pageCount - 1; + this.prevPageButton.active = currentPage > 0; + this.nextPageButton.active = currentPage < pageCount - 1; } /** @@ -154,7 +154,7 @@ private void updateButtons() { this.pageCount = (int) Math.ceil((double) buttonsSize / BUTTONS_PER_PAGE); for (int i = 0; i < donationButtons.size(); ++i) { - int index = CURRENT_PAGE * donationButtons.size() + i; + int index = currentPage * donationButtons.size() + i; if (index < buttonsSize) { donationButtons.get(i).init(visibleDonations.get(index)); @@ -171,7 +171,7 @@ private void updateButtons() { * @param resetPage Whether to reset to the first page. */ public void updateSearchResults(boolean resetPage) { - SEARCH_QUERY = this.searchField.getText(); + searchQuery = this.searchField.getText(); excludedDonationIds.clear(); for (Donation item : donations) { StringBuilder searchableContent = new StringBuilder(); @@ -187,11 +187,11 @@ public void updateSearchResults(boolean resetPage) { .append(ItemUtils.getConcatenatedLore(pieceStack)); } } - if (!searchableContent.toString().toLowerCase(Locale.ENGLISH).contains(SEARCH_QUERY.toLowerCase(Locale.ENGLISH))) { + if (!searchableContent.toString().toLowerCase(Locale.ENGLISH).contains(searchQuery.toLowerCase(Locale.ENGLISH))) { excludedDonationIds.add(item.getId()); } } - if (resetPage) CURRENT_PAGE = 0; + if (resetPage) currentPage = 0; updateButtons(); } @@ -202,7 +202,7 @@ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float d // Render page count if multiple pages exist if (this.pageCount > 1) { - Text text = Text.translatable("gui.recipebook.page", CURRENT_PAGE + 1, this.pageCount); + Text text = Text.translatable("gui.recipebook.page", currentPage + 1, this.pageCount); int width = TEXT_RENDERER.getWidth(text); context.drawText(TEXT_RENDERER, text, getX() - width / 2 + 73, getY() + 137, -1, false); @@ -257,11 +257,11 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { this.searchField.setFocused(true); return true; } else if (this.nextPageButton.mouseClicked(mouseX, mouseY, button)) { - CURRENT_PAGE++; + currentPage++; updateButtons(); return true; } else if (this.prevPageButton.mouseClicked(mouseX, mouseY, button)) { - CURRENT_PAGE--; + currentPage--; updateButtons(); return true; } else if (this.filterButton.mouseClicked(mouseX, mouseY, button)) { From 3463a45fa1d0a2acc21c7f714fbbe75ba093bacd Mon Sep 17 00:00:00 2001 From: 7azeemm Date: Fri, 10 Jan 2025 20:55:54 +0100 Subject: [PATCH 17/17] nothing... --- .../de/hysky/skyblocker/config/categories/MiningCategory.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java index 080e6b22e9..8fa8b4fc26 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java @@ -8,6 +8,7 @@ import de.hysky.skyblocker.skyblock.dwarven.CrystalsHudWidget; import de.hysky.skyblocker.skyblock.dwarven.PowderMiningTracker; import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; +import de.hysky.skyblocker.skyblock.tabhud.widget.CommsWidget; import de.hysky.skyblocker.utils.Location; import dev.isxander.yacl3.api.*; import dev.isxander.yacl3.api.controller.ColorControllerBuilder;