From 1d3303178635566b14a6f24b5741bbf331113f84 Mon Sep 17 00:00:00 2001 From: Aaron <51387595+AzureAaron@users.noreply.github.com> Date: Sun, 24 Mar 2024 02:27:13 -0400 Subject: [PATCH 1/6] Accessories Helper --- .../de/hysky/skyblocker/SkyblockerMod.java | 2 + .../skyblocker/config/SkyblockerConfig.java | 3 + .../config/categories/GeneralCategory.java | 10 + .../item/tooltip/AccessoriesHelper.java | 230 ++++++++++++++++++ .../skyblock/item/tooltip/ItemTooltip.java | 23 ++ .../item/tooltip/TooltipInfoType.java | 31 ++- .../assets/skyblocker/lang/en_us.json | 7 + 7 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index e334ef860a..ee82209d03 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java @@ -25,6 +25,7 @@ import de.hysky.skyblocker.skyblock.garden.FarmingHud; import de.hysky.skyblocker.skyblock.garden.VisitorHelper; import de.hysky.skyblocker.skyblock.item.*; +import de.hysky.skyblocker.skyblock.item.tooltip.AccessoriesHelper; import de.hysky.skyblocker.skyblock.item.tooltip.BackpackPreview; import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; @@ -102,6 +103,7 @@ public void onInitializeClient() { PlayerHeadHashCache.init(); HotbarSlotLock.init(); ItemTooltip.init(); + AccessoriesHelper.init(); WikiLookup.init(); FairySouls.init(); Relics.init(); diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java index a3e710c1ba..8d65806fc2 100644 --- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java @@ -555,6 +555,9 @@ public static class ItemTooltip { @SerialEntry public boolean enableExoticTooltip = true; + + @SerialEntry + public boolean enableAccessoriesHelper = true; } public static class ItemInfoDisplay { diff --git a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java index 23ce7bb63c..abe20e183e 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java @@ -434,6 +434,16 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig newValue -> config.general.itemTooltip.enableExoticTooltip = newValue) .controller(ConfigUtils::createBooleanController) .build()) + .option(Option.createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[0]"), Text.literal("\n\n✔ Collected").formatted(Formatting.GREEN), Text.translatable("text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[1]"), + Text.literal("\n✦ Upgrade").withColor(0x218bff), Text.translatable("text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[2]"), Text.literal("\n↑ Upgradable").withColor(0xf8d048), Text.translatable("text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[3]"), + Text.literal("\n↓ Downgrade").formatted(Formatting.GRAY), Text.translatable("text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[4]"), Text.literal("\n✖ Missing").formatted(Formatting.RED), Text.translatable("text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[5]"))) + .binding(defaults.general.itemTooltip.enableAccessoriesHelper, + () -> config.general.itemTooltip.enableAccessoriesHelper, + newValue -> config.general.itemTooltip.enableAccessoriesHelper = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) .build()) //Item Info Display diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java new file mode 100644 index 0000000000..b5291af67a --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java @@ -0,0 +1,230 @@ +package de.hysky.skyblocker.skyblock.item.tooltip; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; +import java.util.function.ToIntFunction; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.slf4j.Logger; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.logging.LogUtils; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import com.mojang.util.UndashedUuid; + +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.Utils; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; +import net.minecraft.screen.GenericContainerScreenHandler; +import net.minecraft.screen.slot.Slot; + +public class AccessoriesHelper { + private static final Logger LOGGER = LogUtils.getLogger(); + private static final Path FILE = SkyblockerMod.CONFIG_DIR.resolve("collected_accessories.json"); + private static final Pattern ACCESSORY_BAG_TITLE = Pattern.compile("Accessory Bag \\(\\d+\\/\\d+\\)"); + //UUID -> Profile Id & Data + private static final Map> COLLECTED_ACCESSORIES = new Object2ObjectOpenHashMap<>(); + private static final Predicate NON_EMPTY = s -> !s.isEmpty(); + private static final Predicate HAS_FAMILY = Accessory::hasFamily; + private static final ToIntFunction ACCESSORY_TIER = Accessory::tier; + + private static Map ACCESSORY_DATA = new Object2ObjectOpenHashMap<>(); + //remove?? + private static CompletableFuture loaded; + + public static void init() { + ClientLifecycleEvents.CLIENT_STARTED.register((_client) -> load()); + ClientLifecycleEvents.CLIENT_STOPPING.register((_client) -> save()); + ScreenEvents.BEFORE_INIT.register((_client, screen, _scaledWidth, _scaledHeight) -> { + if (Utils.isOnSkyblock() && TooltipInfoType.ACCESSORIES.isTooltipEnabled() && !Utils.getProfileId().isEmpty() && screen instanceof GenericContainerScreen genericContainerScreen) { + if (ACCESSORY_BAG_TITLE.matcher(genericContainerScreen.getTitle().getString()).matches()) { + ScreenEvents.afterRender(screen).register((_screen, _context, _mouseX, _mouseY, _delta) -> { + GenericContainerScreenHandler handler = genericContainerScreen.getScreenHandler(); + + collectAccessories(handler.slots.subList(0, handler.getRows() * 9)); + }); + } + } + }); + } + + private static void load() { + loaded = CompletableFuture.runAsync(() -> { + try (BufferedReader reader = Files.newBufferedReader(FILE)) { + COLLECTED_ACCESSORIES.putAll(ProfileAccessoryData.SERIALIZATION_CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).result().orElseThrow()); + } catch (NoSuchFileException ignored) { + } catch (Exception e) { + LOGGER.error("[Skyblocker Accessory Helper] Failed to load accessory file!", e); + } + }); + } + + private static void save() { + try (BufferedWriter writer = Files.newBufferedWriter(FILE)) { + SkyblockerMod.GSON.toJson(ProfileAccessoryData.SERIALIZATION_CODEC.encodeStart(JsonOps.INSTANCE, COLLECTED_ACCESSORIES).result().orElseThrow(), writer); + } catch (Exception e) { + LOGGER.error("[Skyblocker Accessory Helper] Failed to save accessory file!", e); + } + } + + private static void collectAccessories(List slots) { + //Is this even needed? + if (!loaded.isDone()) return; + + List accessoryIds = slots.stream() + .map(Slot::getStack) + .map(ItemUtils::getItemId) + .filter(NON_EMPTY) + .collect(Collectors.toUnmodifiableList()); + + String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull()); + + Map playerData = COLLECTED_ACCESSORIES.computeIfAbsent(uuid, _uuid -> new Object2ObjectOpenHashMap<>()); + playerData.putIfAbsent(Utils.getProfileId(), ProfileAccessoryData.createDefault()); + + ProfileAccessoryData profileData = playerData.get(Utils.getProfileId()); + + profileData.accessoryIds().addAll(accessoryIds); + } + + static AccessoryReport calculateReport4Accessory(String accessoryId) { + if (!ACCESSORY_DATA.containsKey(accessoryId) || Utils.getProfileId().isEmpty()) return AccessoryReport.INELIGIBLE; + + Accessory accessory = ACCESSORY_DATA.get(accessoryId); + String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull()); + Set collectedAccessories = COLLECTED_ACCESSORIES.get(uuid).get(Utils.getProfileId()).accessoryIds().stream() + .filter(ACCESSORY_DATA::containsKey) + .map(ACCESSORY_DATA::get) + .collect(Collectors.toSet()); + + //If the player has this accessory and it doesn't belong to a family + if (collectedAccessories.contains(accessory) && accessory.family().isEmpty()) return AccessoryReport.HAS_HIGHEST_TIER; + + Predicate HAS_SAME_FAMILY = accessory::hasSameFamily; + Set collectedAccessoriesInTheSameFamily = collectedAccessories.stream() + .filter(HAS_FAMILY) + .filter(HAS_SAME_FAMILY) + .collect(Collectors.toSet()); + + //If the player doesn't have any collected accessories with same family + if (collectedAccessoriesInTheSameFamily.isEmpty()) return AccessoryReport.MISSING; + + Set accessoriesInTheSameFamily = ACCESSORY_DATA.values().stream() + .filter(HAS_FAMILY) + .filter(HAS_SAME_FAMILY) + .collect(Collectors.toSet()); + + ///If the player has the highest tier accessory in this family + //Take the the accessories in the same family as {@code accessory}, then get the one with the highest tier + Optional highestTierOfFamily = accessoriesInTheSameFamily.stream() + .max(Comparator.comparingInt(ACCESSORY_TIER)); + + if (highestTierOfFamily.isPresent()) { + Accessory highestTier = highestTierOfFamily.orElseThrow(); + + if (collectedAccessoriesInTheSameFamily.contains(highestTier)) return AccessoryReport.HAS_HIGHEST_TIER; + + //For when the highest tier is tied + if (highestTier.hasSameFamily(accessory) && collectedAccessoriesInTheSameFamily.stream().allMatch(ca -> ca.tier() == highestTier.tier())) return AccessoryReport.HAS_HIGHEST_TIER; + } + + //If this accessory is a higher tier than all of other collected accessories in the same family + OptionalInt highestTierOfAllCollectedInFamily = collectedAccessoriesInTheSameFamily.stream() + .mapToInt(ACCESSORY_TIER) + .max(); + + if (highestTierOfAllCollectedInFamily.isPresent() && accessory.tier() > highestTierOfAllCollectedInFamily.orElseThrow()) return AccessoryReport.IS_GREATER_TIER; + + //If this accessory is a lower tier than one already obtained from same family + if (highestTierOfAllCollectedInFamily.isPresent() && accessory.tier() < highestTierOfAllCollectedInFamily.orElseThrow()) return AccessoryReport.OWNS_BETTER_TIER; + + //If there is an accessory in the same family that has a higher tier + //Take the accessories in the same family, then check if there is an accessory whose tier is greater than {@code accessory} + boolean hasGreaterTierInFamily = accessoriesInTheSameFamily.stream() + .anyMatch(ca -> ca.tier() > accessory.tier()); + + if (hasGreaterTierInFamily) return AccessoryReport.HAS_GREATER_TIER; + + return AccessoryReport.MISSING; + } + + static void refreshData(JsonObject data) { + try { + Map accessoryData = Accessory.MAP_CODEC.parse(JsonOps.INSTANCE, data).result().orElseThrow(); + + ACCESSORY_DATA = accessoryData; + } catch (Exception e) { + LOGGER.error("[Skyblocker Accessory Helper] Failed to parse data!", e); + } + } + + private record ProfileAccessoryData(Set accessoryIds) { + private static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.listOf() + .xmap(ObjectOpenHashSet::new, ObjectArrayList::new) + .fieldOf("accessoryIds") + .forGetter(i -> new ObjectOpenHashSet(i.accessoryIds()))) + .apply(instance, ProfileAccessoryData::new)); + //Mojang's internal Codec implementation uses ImmutableMaps so we'll just xmap those away and type safety while we're at it :') + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static final Codec>> SERIALIZATION_CODEC = Codec.unboundedMap(Codec.STRING, Codec.unboundedMap(Codec.STRING, CODEC) + .xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new)) + .xmap(Object2ObjectOpenHashMap::new, m -> (Map) new Object2ObjectOpenHashMap(m)); + + private static ProfileAccessoryData createDefault() { + return new ProfileAccessoryData(new ObjectOpenHashSet<>()); + } + } + + /** + * @author AzureAaron + * @implSpec Aaron's Mod + */ + private record Accessory(String id, Optional family, int tier) { + private static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.fieldOf("id").forGetter(Accessory::id), + Codec.STRING.optionalFieldOf("family").forGetter(Accessory::family), + Codec.INT.optionalFieldOf("tier", 0).forGetter(Accessory::tier)) + .apply(instance, Accessory::new)); + private static final Codec> MAP_CODEC = Codec.unboundedMap(Codec.STRING, CODEC); + + private boolean hasFamily() { + return family.isPresent(); + } + + private boolean hasSameFamily(Accessory other) { + return other.family().equals(this.family); + } + } + + enum AccessoryReport { + HAS_HIGHEST_TIER, //You've collected the highest tier - Collected + IS_GREATER_TIER, //This accessory is an upgrade from the one in the same family that you already have - Upgrade + HAS_GREATER_TIER, //This accessory has a higher tier upgrade - Upgradable + OWNS_BETTER_TIER, //You've collected an accessory in this family with a higher tier - Downgrade + MISSING, //You don't have any accessories in this family - Missing + INELIGIBLE; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java index 1b3f402d80..8a11b9bd56 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java @@ -5,6 +5,7 @@ import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.item.MuseumItemCache; +import de.hysky.skyblocker.skyblock.item.tooltip.AccessoriesHelper.AccessoryReport; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; @@ -238,6 +239,27 @@ public static void getTooltip(ItemStack stack, TooltipContext context, List Text.literal("✔ Collected").formatted(Formatting.GREEN); + case IS_GREATER_TIER -> Text.literal("✦ Upgrade").withColor(0x218bff); + case HAS_GREATER_TIER -> Text.literal("↑ Upgradable").withColor(0xf8d048); + case OWNS_BETTER_TIER -> Text.literal("↓ Downgrade").formatted(Formatting.GRAY); + case MISSING -> Text.literal("✖ Missing").formatted(Formatting.RED); + + //Should never be the case + default -> Text.literal("? Unknown").formatted(Formatting.GRAY); + }; + + lines.add(title.append(stateText)); + } + } } private static void addExoticTooltip(List lines, String internalID, NbtCompound nbt, String colorHex, String expectedHex, String existingTooltip) { @@ -390,6 +412,7 @@ public static void init() { TooltipInfoType.MOTES.downloadIfEnabled(futureList); TooltipInfoType.MUSEUM.downloadIfEnabled(futureList); TooltipInfoType.COLOR.downloadIfEnabled(futureList); + TooltipInfoType.ACCESSORIES.downloadIfEnabled(futureList); CompletableFuture.allOf(futureList.toArray(CompletableFuture[]::new)).exceptionally(e -> { LOGGER.error("Encountered unknown error while downloading tooltip data", e); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java index 4aba040d6c..d0cf91b8d4 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java @@ -6,13 +6,15 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.utils.Http; import de.hysky.skyblocker.utils.Utils; -import org.jetbrains.annotations.Nullable; import java.net.http.HttpHeaders; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; import java.util.function.Predicate; +import org.jetbrains.annotations.Nullable; + public enum TooltipInfoType implements Runnable { NPC("https://hysky.de/api/npcprice", itemTooltip -> itemTooltip.enableNPCPrice, true), BAZAAR("https://hysky.de/api/bazaar", itemTooltip -> itemTooltip.enableBazaarPrice || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.croesusProfit || SkyblockerConfigManager.get().general.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableBazaarPrice, false), @@ -22,7 +24,8 @@ public enum TooltipInfoType implements Runnable { MOTES("https://hysky.de/api/motesprice", itemTooltip -> itemTooltip.enableMotesPrice, itemTooltip -> itemTooltip.enableMotesPrice && Utils.isInTheRift(), true), OBTAINED(itemTooltip -> itemTooltip.enableObtainedDate), MUSEUM("https://hysky.de/api/museum", itemTooltip -> itemTooltip.enableMuseumInfo, true), - COLOR("https://hysky.de/api/color", itemTooltip -> itemTooltip.enableExoticTooltip, true); + COLOR("https://hysky.de/api/color", itemTooltip -> itemTooltip.enableExoticTooltip, true), + ACCESSORIES("https://api.azureaaron.net/skyblock/accessories", itemTooltip -> itemTooltip.enableAccessoriesHelper, true, AccessoriesHelper::refreshData); private final String address; private final Predicate dataEnabled; @@ -30,12 +33,23 @@ public enum TooltipInfoType implements Runnable { private JsonObject data; private final boolean cacheable; private long hash; + private final Consumer callback; /** * Use this for when you're adding tooltip info that has no data associated with it */ TooltipInfoType(Predicate enabled) { - this(null, itemTooltip -> false, enabled, null, false); + this(null, itemTooltip -> false, enabled, false, null); + } + + /** + * @param address the address to download the data from + * @param enabled the predicate to check if the data should be downloaded and the tooltip should be shown + * @param cacheable whether the data should be cached + * @param callback called when the {@code data} is refreshed + */ + TooltipInfoType(String address, Predicate enabled, boolean cacheable, Consumer callback) { + this(address, enabled, enabled, cacheable, callback); } /** @@ -44,7 +58,7 @@ public enum TooltipInfoType implements Runnable { * @param cacheable whether the data should be cached */ TooltipInfoType(String address, Predicate enabled, boolean cacheable) { - this(address, enabled, enabled, null, cacheable); + this(address, enabled, enabled, cacheable, null); } /** @@ -54,7 +68,7 @@ public enum TooltipInfoType implements Runnable { * @param cacheable whether the data should be cached */ TooltipInfoType(String address, Predicate dataEnabled, Predicate tooltipEnabled, boolean cacheable) { - this(address, dataEnabled, tooltipEnabled, null, cacheable); + this(address, dataEnabled, tooltipEnabled, cacheable, null); } /** @@ -64,12 +78,13 @@ public enum TooltipInfoType implements Runnable { * @param data the data * @param cacheable whether the data should be cached */ - TooltipInfoType(String address, Predicate dataEnabled, Predicate tooltipEnabled, @Nullable JsonObject data, boolean cacheable) { + TooltipInfoType(String address, Predicate dataEnabled, Predicate tooltipEnabled, boolean cacheable, @Nullable Consumer callback) { this.address = address; this.dataEnabled = dataEnabled; this.tooltipEnabled = tooltipEnabled; - this.data = data; + this.data = null; this.cacheable = cacheable; + this.callback = callback; } /** @@ -146,6 +161,8 @@ public void run() { else this.hash = hash; } data = SkyblockerMod.GSON.fromJson(Http.sendGetRequest(address), JsonObject.class); + + if (callback != null) callback.accept(data); } catch (Exception e) { ItemTooltip.LOGGER.warn("[Skyblocker] Failed to download " + this + " prices!", e); } diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index ff0ffa11ee..380bcdc1eb 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -90,6 +90,13 @@ "text.autoconfig.skyblocker.option.general.itemTooltip.enableMuseumInfo.@Tooltip": "If this item is donatable to the museum, then the item's category in the musuem is displayed. It also displays a marker indicating whether you've donated that item to your musuem or not (freebies not yet supported).\n\nMake sure to enable your Museum API for accurate information!", "text.autoconfig.skyblocker.option.general.itemTooltip.enableExoticTooltip": "Enable Exotic Tooltip", "text.autoconfig.skyblocker.option.general.itemTooltip.enableExoticTooltip.@Tooltip": "Displays the type of exotic below the item's name if an armor piece is exotic.", + "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper": "Enable Accessories Helper", + "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[0]": "When hovering over an accessory you are informed about whether you already have it or not, and whether it's worse than what you have already collected or better. List of Statuses:", + "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[1]": "You have the highest tier accessory from that family.", + "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[2]": "This accessory is an upgrade from the one in the same family that you already have.", + "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[3]": "This accessory can be upgraded.", + "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[4]": "You already own an accessory in the same family that is better than this one.", + "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[5]": "You don't own any accessory from this family.", "text.autoconfig.skyblocker.option.general.dungeonQuality": "Dungeon Quality", "text.autoconfig.skyblocker.option.general.dungeonQuality.@Tooltip": "Displays quality and tier of dungeon drops from mobs.\n\n\nReminder:\nTier 1-3 dropped from F1-F3\nTier 4-7 dropped from F4-F7 or M1-M4\nTier 8-10 are dropped only from M5-M7", "text.autoconfig.skyblocker.option.general.itemInfoDisplay": "Item Info Display", From b21515cfdcd6a4a14cc256131d5ae6bb6666792d Mon Sep 17 00:00:00 2001 From: Aaron <51387595+AzureAaron@users.noreply.github.com> Date: Sun, 24 Mar 2024 16:05:09 -0400 Subject: [PATCH 2/6] Use hysky redirect --- .../hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java index d0cf91b8d4..6edee8d672 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java @@ -25,7 +25,7 @@ public enum TooltipInfoType implements Runnable { OBTAINED(itemTooltip -> itemTooltip.enableObtainedDate), MUSEUM("https://hysky.de/api/museum", itemTooltip -> itemTooltip.enableMuseumInfo, true), COLOR("https://hysky.de/api/color", itemTooltip -> itemTooltip.enableExoticTooltip, true), - ACCESSORIES("https://api.azureaaron.net/skyblock/accessories", itemTooltip -> itemTooltip.enableAccessoriesHelper, true, AccessoriesHelper::refreshData); + ACCESSORIES("https://hysky.de/api/accessories", itemTooltip -> itemTooltip.enableAccessoriesHelper, true, AccessoriesHelper::refreshData); private final String address; private final Predicate dataEnabled; From fad85d138db8761ca2a3edfe08520b32e08ae3dc Mon Sep 17 00:00:00 2001 From: Aaron <51387595+AzureAaron@users.noreply.github.com> Date: Fri, 29 Mar 2024 21:44:37 -0400 Subject: [PATCH 3/6] Enhance the accessories helper --- .../item/tooltip/AccessoriesHelper.java | 36 ++++++++++--------- .../skyblock/item/tooltip/ItemTooltip.java | 13 +++---- .../assets/skyblocker/lang/en_us.json | 6 ++-- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java index b5291af67a..a3fc40d50f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java @@ -30,6 +30,7 @@ import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; +import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectArrayList; @@ -60,7 +61,7 @@ public static void init() { ScreenEvents.BEFORE_INIT.register((_client, screen, _scaledWidth, _scaledHeight) -> { if (Utils.isOnSkyblock() && TooltipInfoType.ACCESSORIES.isTooltipEnabled() && !Utils.getProfileId().isEmpty() && screen instanceof GenericContainerScreen genericContainerScreen) { if (ACCESSORY_BAG_TITLE.matcher(genericContainerScreen.getTitle().getString()).matches()) { - ScreenEvents.afterRender(screen).register((_screen, _context, _mouseX, _mouseY, _delta) -> { + ScreenEvents.afterTick(screen).register(_screen -> { GenericContainerScreenHandler handler = genericContainerScreen.getScreenHandler(); collectAccessories(handler.slots.subList(0, handler.getRows() * 9)); @@ -109,8 +110,8 @@ private static void collectAccessories(List slots) { profileData.accessoryIds().addAll(accessoryIds); } - static AccessoryReport calculateReport4Accessory(String accessoryId) { - if (!ACCESSORY_DATA.containsKey(accessoryId) || Utils.getProfileId().isEmpty()) return AccessoryReport.INELIGIBLE; + static Pair calculateReport4Accessory(String accessoryId) { + if (!ACCESSORY_DATA.containsKey(accessoryId) || Utils.getProfileId().isEmpty()) return Pair.of(AccessoryReport.INELIGIBLE, null); Accessory accessory = ACCESSORY_DATA.get(accessoryId); String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull()); @@ -120,7 +121,7 @@ static AccessoryReport calculateReport4Accessory(String accessoryId) { .collect(Collectors.toSet()); //If the player has this accessory and it doesn't belong to a family - if (collectedAccessories.contains(accessory) && accessory.family().isEmpty()) return AccessoryReport.HAS_HIGHEST_TIER; + if (collectedAccessories.contains(accessory) && accessory.family().isEmpty()) return Pair.of(AccessoryReport.HAS_HIGHEST_TIER, null); Predicate HAS_SAME_FAMILY = accessory::hasSameFamily; Set collectedAccessoriesInTheSameFamily = collectedAccessories.stream() @@ -129,7 +130,7 @@ static AccessoryReport calculateReport4Accessory(String accessoryId) { .collect(Collectors.toSet()); //If the player doesn't have any collected accessories with same family - if (collectedAccessoriesInTheSameFamily.isEmpty()) return AccessoryReport.MISSING; + if (collectedAccessoriesInTheSameFamily.isEmpty()) return Pair.of(AccessoryReport.MISSING, null); Set accessoriesInTheSameFamily = ACCESSORY_DATA.values().stream() .filter(HAS_FAMILY) @@ -142,12 +143,12 @@ static AccessoryReport calculateReport4Accessory(String accessoryId) { .max(Comparator.comparingInt(ACCESSORY_TIER)); if (highestTierOfFamily.isPresent()) { - Accessory highestTier = highestTierOfFamily.orElseThrow(); + Accessory highestTier = highestTierOfFamily.get(); - if (collectedAccessoriesInTheSameFamily.contains(highestTier)) return AccessoryReport.HAS_HIGHEST_TIER; + if (collectedAccessoriesInTheSameFamily.contains(highestTier)) return Pair.of(AccessoryReport.HAS_HIGHEST_TIER, null); //For when the highest tier is tied - if (highestTier.hasSameFamily(accessory) && collectedAccessoriesInTheSameFamily.stream().allMatch(ca -> ca.tier() == highestTier.tier())) return AccessoryReport.HAS_HIGHEST_TIER; + if (highestTier.hasSameFamily(accessory) && collectedAccessoriesInTheSameFamily.stream().allMatch(ca -> ca.tier() == highestTier.tier())) return Pair.of(AccessoryReport.HAS_HIGHEST_TIER, null); } //If this accessory is a higher tier than all of other collected accessories in the same family @@ -155,19 +156,21 @@ static AccessoryReport calculateReport4Accessory(String accessoryId) { .mapToInt(ACCESSORY_TIER) .max(); - if (highestTierOfAllCollectedInFamily.isPresent() && accessory.tier() > highestTierOfAllCollectedInFamily.orElseThrow()) return AccessoryReport.IS_GREATER_TIER; + int maxTierInFamily = highestTierOfFamily.orElse(Accessory.EMPTY).tier(); + + if (highestTierOfAllCollectedInFamily.isPresent() && accessory.tier() > highestTierOfAllCollectedInFamily.getAsInt()) return Pair.of(AccessoryReport.IS_GREATER_TIER, String.format("(%d/%d)", accessory.tier(), maxTierInFamily)); //If this accessory is a lower tier than one already obtained from same family - if (highestTierOfAllCollectedInFamily.isPresent() && accessory.tier() < highestTierOfAllCollectedInFamily.orElseThrow()) return AccessoryReport.OWNS_BETTER_TIER; + if (highestTierOfAllCollectedInFamily.isPresent() && accessory.tier() < highestTierOfAllCollectedInFamily.getAsInt()) return Pair.of(AccessoryReport.OWNS_BETTER_TIER, String.format("(%d/%d)", highestTierOfAllCollectedInFamily.orElse(0), maxTierInFamily)); //If there is an accessory in the same family that has a higher tier //Take the accessories in the same family, then check if there is an accessory whose tier is greater than {@code accessory} boolean hasGreaterTierInFamily = accessoriesInTheSameFamily.stream() .anyMatch(ca -> ca.tier() > accessory.tier()); - if (hasGreaterTierInFamily) return AccessoryReport.HAS_GREATER_TIER; + if (hasGreaterTierInFamily) return Pair.of(AccessoryReport.HAS_GREATER_TIER, String.format("(%d/%d)", highestTierOfAllCollectedInFamily.orElse(0), maxTierInFamily)); - return AccessoryReport.MISSING; + return Pair.of(AccessoryReport.MISSING, null); } static void refreshData(JsonObject data) { @@ -200,7 +203,7 @@ private static ProfileAccessoryData createDefault() { /** * @author AzureAaron - * @implSpec Aaron's Mod + * @implSpec Aaron's Mod */ private record Accessory(String id, Optional family, int tier) { private static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( @@ -209,6 +212,7 @@ private record Accessory(String id, Optional family, int tier) { Codec.INT.optionalFieldOf("tier", 0).forGetter(Accessory::tier)) .apply(instance, Accessory::new)); private static final Codec> MAP_CODEC = Codec.unboundedMap(Codec.STRING, CODEC); + private static final Accessory EMPTY = new Accessory("", Optional.empty(), 0); private boolean hasFamily() { return family.isPresent(); @@ -221,9 +225,9 @@ private boolean hasSameFamily(Accessory other) { enum AccessoryReport { HAS_HIGHEST_TIER, //You've collected the highest tier - Collected - IS_GREATER_TIER, //This accessory is an upgrade from the one in the same family that you already have - Upgrade - HAS_GREATER_TIER, //This accessory has a higher tier upgrade - Upgradable - OWNS_BETTER_TIER, //You've collected an accessory in this family with a higher tier - Downgrade + IS_GREATER_TIER, //This accessory is an upgrade from the one in the same family that you already have - Upgrade -- Shows you what tier this accessory is in it's family + HAS_GREATER_TIER, //This accessory has a higher tier upgrade - Upgradable -- Shows you the highest tier accessory you've collected in that family + OWNS_BETTER_TIER, //You've collected an accessory in this family with a higher tier - Downgrade -- Shows you the highest tier accessory you've collected in that family MISSING, //You don't have any accessories in this family - Missing INELIGIBLE; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java index 8a11b9bd56..637aea228a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java @@ -10,6 +10,7 @@ import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.scheduler.Scheduler; +import it.unimi.dsi.fastutil.Pair; import net.minecraft.client.MinecraftClient; import net.minecraft.client.item.TooltipContext; import net.minecraft.item.DyeableItem; @@ -241,16 +242,16 @@ public static void getTooltip(ItemStack stack, TooltipContext context, List report = AccessoriesHelper.calculateReport4Accessory(internalID); - if (report != AccessoryReport.INELIGIBLE) { + if (report.left() != AccessoryReport.INELIGIBLE) { MutableText title = Text.literal(String.format("%-19s", "Accessory: ")).withColor(0xf57542); - Text stateText = switch (report) { + Text stateText = switch (report.left()) { case HAS_HIGHEST_TIER -> Text.literal("✔ Collected").formatted(Formatting.GREEN); - case IS_GREATER_TIER -> Text.literal("✦ Upgrade").withColor(0x218bff); - case HAS_GREATER_TIER -> Text.literal("↑ Upgradable").withColor(0xf8d048); - case OWNS_BETTER_TIER -> Text.literal("↓ Downgrade").formatted(Formatting.GRAY); + case IS_GREATER_TIER -> Text.literal("✦ Upgrade ").withColor(0x218bff).append(Text.literal(report.right()).withColor(0xf8f8ff)); + case HAS_GREATER_TIER -> Text.literal("↑ Upgradable ").withColor(0xf8d048).append(Text.literal(report.right()).withColor(0xf8f8ff)); + case OWNS_BETTER_TIER -> Text.literal("↓ Downgrade ").formatted(Formatting.GRAY).append(Text.literal(report.right()).withColor(0xf8f8ff)); case MISSING -> Text.literal("✖ Missing").formatted(Formatting.RED); //Should never be the case diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 380bcdc1eb..7fd2159f0e 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -93,9 +93,9 @@ "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper": "Enable Accessories Helper", "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[0]": "When hovering over an accessory you are informed about whether you already have it or not, and whether it's worse than what you have already collected or better. List of Statuses:", "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[1]": "You have the highest tier accessory from that family.", - "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[2]": "This accessory is an upgrade from the one in the same family that you already have.", - "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[3]": "This accessory can be upgraded.", - "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[4]": "You already own an accessory in the same family that is better than this one.", + "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[2]": "This accessory is an upgrade from the one in the same family that you already have. Also shows you what tier this accessory is in it's family.", + "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[3]": "This accessory can be upgraded. Also tells you what tier of accessory you have in that family.", + "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[4]": "You already own an accessory in the same family that is better than this one. Also tells you what tier of accessory you have in that family.", "text.autoconfig.skyblocker.option.general.itemTooltip.enableAccessoriesHelper.@Tooltip[5]": "You don't own any accessory from this family.", "text.autoconfig.skyblocker.option.general.dungeonQuality": "Dungeon Quality", "text.autoconfig.skyblocker.option.general.dungeonQuality.@Tooltip": "Displays quality and tier of dungeon drops from mobs.\n\n\nReminder:\nTier 1-3 dropped from F1-F3\nTier 4-7 dropped from F4-F7 or M1-M4\nTier 8-10 are dropped only from M5-M7", From bbea21ac0a8419f1cf5c133747416152bb70ccb5 Mon Sep 17 00:00:00 2001 From: Aaron <51387595+AzureAaron@users.noreply.github.com> Date: Sun, 31 Mar 2024 22:20:55 -0400 Subject: [PATCH 4/6] Page based data storage --- .../item/tooltip/AccessoriesHelper.java | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java index a3fc40d50f..951d8d5f76 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java @@ -14,6 +14,7 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; import java.util.function.ToIntFunction; +import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -31,9 +32,11 @@ import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; import net.minecraft.client.MinecraftClient; @@ -44,9 +47,9 @@ public class AccessoriesHelper { private static final Logger LOGGER = LogUtils.getLogger(); private static final Path FILE = SkyblockerMod.CONFIG_DIR.resolve("collected_accessories.json"); - private static final Pattern ACCESSORY_BAG_TITLE = Pattern.compile("Accessory Bag \\(\\d+\\/\\d+\\)"); + private static final Pattern ACCESSORY_BAG_TITLE = Pattern.compile("Accessory Bag \\((?\\d+)\\/\\d+\\)"); //UUID -> Profile Id & Data - private static final Map> COLLECTED_ACCESSORIES = new Object2ObjectOpenHashMap<>(); + private static final Object2ObjectOpenHashMap> COLLECTED_ACCESSORIES = new Object2ObjectOpenHashMap<>(); private static final Predicate NON_EMPTY = s -> !s.isEmpty(); private static final Predicate HAS_FAMILY = Accessory::hasFamily; private static final ToIntFunction ACCESSORY_TIER = Accessory::tier; @@ -60,21 +63,24 @@ public static void init() { ClientLifecycleEvents.CLIENT_STOPPING.register((_client) -> save()); ScreenEvents.BEFORE_INIT.register((_client, screen, _scaledWidth, _scaledHeight) -> { if (Utils.isOnSkyblock() && TooltipInfoType.ACCESSORIES.isTooltipEnabled() && !Utils.getProfileId().isEmpty() && screen instanceof GenericContainerScreen genericContainerScreen) { - if (ACCESSORY_BAG_TITLE.matcher(genericContainerScreen.getTitle().getString()).matches()) { + Matcher matcher = ACCESSORY_BAG_TITLE.matcher(genericContainerScreen.getTitle().getString()); + + if (matcher.matches()) { ScreenEvents.afterTick(screen).register(_screen -> { GenericContainerScreenHandler handler = genericContainerScreen.getScreenHandler(); - collectAccessories(handler.slots.subList(0, handler.getRows() * 9)); + collectAccessories(handler.slots.subList(0, handler.getRows() * 9), Integer.parseInt(matcher.group("page"))); }); } } }); } + //Note: JsonOps.COMPRESSED must be used if you're using maps with non-string keys private static void load() { loaded = CompletableFuture.runAsync(() -> { try (BufferedReader reader = Files.newBufferedReader(FILE)) { - COLLECTED_ACCESSORIES.putAll(ProfileAccessoryData.SERIALIZATION_CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).result().orElseThrow()); + COLLECTED_ACCESSORIES.putAll(ProfileAccessoryData.SERIALIZATION_CODEC.parse(JsonOps.COMPRESSED, JsonParser.parseReader(reader)).result().orElseThrow()); } catch (NoSuchFileException ignored) { } catch (Exception e) { LOGGER.error("[Skyblocker Accessory Helper] Failed to load accessory file!", e); @@ -84,13 +90,13 @@ private static void load() { private static void save() { try (BufferedWriter writer = Files.newBufferedWriter(FILE)) { - SkyblockerMod.GSON.toJson(ProfileAccessoryData.SERIALIZATION_CODEC.encodeStart(JsonOps.INSTANCE, COLLECTED_ACCESSORIES).result().orElseThrow(), writer); + SkyblockerMod.GSON.toJson(ProfileAccessoryData.SERIALIZATION_CODEC.encodeStart(JsonOps.COMPRESSED, COLLECTED_ACCESSORIES).result().orElseThrow(), writer); } catch (Exception e) { LOGGER.error("[Skyblocker Accessory Helper] Failed to save accessory file!", e); } } - private static void collectAccessories(List slots) { + private static void collectAccessories(List slots, int page) { //Is this even needed? if (!loaded.isDone()) return; @@ -107,7 +113,7 @@ private static void collectAccessories(List slots) { ProfileAccessoryData profileData = playerData.get(Utils.getProfileId()); - profileData.accessoryIds().addAll(accessoryIds); + profileData.pages().put(page, new ObjectOpenHashSet<>(accessoryIds)); } static Pair calculateReport4Accessory(String accessoryId) { @@ -115,7 +121,9 @@ static Pair calculateReport4Accessory(String accessoryI Accessory accessory = ACCESSORY_DATA.get(accessoryId); String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull()); - Set collectedAccessories = COLLECTED_ACCESSORIES.get(uuid).get(Utils.getProfileId()).accessoryIds().stream() + Set collectedAccessories = COLLECTED_ACCESSORIES.computeIfAbsent(uuid, _uuid -> new Object2ObjectOpenHashMap<>()).computeIfAbsent(Utils.getProfileId(), profileId -> ProfileAccessoryData.createDefault()).pages().int2ObjectEntrySet().stream() + .map(Entry::getValue) + .flatMap(ObjectOpenHashSet::stream) .filter(ACCESSORY_DATA::containsKey) .map(ACCESSORY_DATA::get) .collect(Collectors.toSet()); @@ -183,21 +191,17 @@ static void refreshData(JsonObject data) { } } - private record ProfileAccessoryData(Set accessoryIds) { + private record ProfileAccessoryData(Int2ObjectOpenHashMap> pages) { private static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - Codec.STRING.listOf() - .xmap(ObjectOpenHashSet::new, ObjectArrayList::new) - .fieldOf("accessoryIds") - .forGetter(i -> new ObjectOpenHashSet(i.accessoryIds()))) + Codec.unboundedMap(Codec.INT, Codec.STRING.listOf().xmap(ObjectOpenHashSet::new, ObjectArrayList::new)) + .xmap(Int2ObjectOpenHashMap::new, Int2ObjectOpenHashMap::new).fieldOf("pages").forGetter(ProfileAccessoryData::pages)) .apply(instance, ProfileAccessoryData::new)); - //Mojang's internal Codec implementation uses ImmutableMaps so we'll just xmap those away and type safety while we're at it :') - @SuppressWarnings({ "unchecked", "rawtypes" }) - private static final Codec>> SERIALIZATION_CODEC = Codec.unboundedMap(Codec.STRING, Codec.unboundedMap(Codec.STRING, CODEC) + private static final Codec>> SERIALIZATION_CODEC = Codec.unboundedMap(Codec.STRING, Codec.unboundedMap(Codec.STRING, CODEC) .xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new)) - .xmap(Object2ObjectOpenHashMap::new, m -> (Map) new Object2ObjectOpenHashMap(m)); + .xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new); private static ProfileAccessoryData createDefault() { - return new ProfileAccessoryData(new ObjectOpenHashSet<>()); + return new ProfileAccessoryData(new Int2ObjectOpenHashMap<>()); } } From 0a2fa0a57c817cabb64b0890a7271acb491b6e98 Mon Sep 17 00:00:00 2001 From: Kevinthegreat <92656833+kevinthegreat1@users.noreply.github.com> Date: Wed, 3 Apr 2024 17:38:36 -0400 Subject: [PATCH 5/6] Refactor AccessoriesHelper --- .../item/tooltip/AccessoriesHelper.java | 46 ++++++------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java index 951d8d5f76..358f3d85bf 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java @@ -32,7 +32,6 @@ import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import it.unimi.dsi.fastutil.Pair; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; @@ -47,7 +46,7 @@ public class AccessoriesHelper { private static final Logger LOGGER = LogUtils.getLogger(); private static final Path FILE = SkyblockerMod.CONFIG_DIR.resolve("collected_accessories.json"); - private static final Pattern ACCESSORY_BAG_TITLE = Pattern.compile("Accessory Bag \\((?\\d+)\\/\\d+\\)"); + private static final Pattern ACCESSORY_BAG_TITLE = Pattern.compile("Accessory Bag \\((?\\d+)/\\d+\\)"); //UUID -> Profile Id & Data private static final Object2ObjectOpenHashMap> COLLECTED_ACCESSORIES = new Object2ObjectOpenHashMap<>(); private static final Predicate NON_EMPTY = s -> !s.isEmpty(); @@ -104,16 +103,12 @@ private static void collectAccessories(List slots, int page) { .map(Slot::getStack) .map(ItemUtils::getItemId) .filter(NON_EMPTY) - .collect(Collectors.toUnmodifiableList()); + .toList(); String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull()); - Map playerData = COLLECTED_ACCESSORIES.computeIfAbsent(uuid, _uuid -> new Object2ObjectOpenHashMap<>()); - playerData.putIfAbsent(Utils.getProfileId(), ProfileAccessoryData.createDefault()); - - ProfileAccessoryData profileData = playerData.get(Utils.getProfileId()); - - profileData.pages().put(page, new ObjectOpenHashSet<>(accessoryIds)); + COLLECTED_ACCESSORIES.computeIfAbsent(uuid, _uuid -> new Object2ObjectOpenHashMap<>()).computeIfAbsent(Utils.getProfileId(), profileId -> ProfileAccessoryData.createDefault()).pages() + .put(page, new ObjectOpenHashSet<>(accessoryIds)); } static Pair calculateReport4Accessory(String accessoryId) { @@ -121,14 +116,13 @@ static Pair calculateReport4Accessory(String accessoryI Accessory accessory = ACCESSORY_DATA.get(accessoryId); String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull()); - Set collectedAccessories = COLLECTED_ACCESSORIES.computeIfAbsent(uuid, _uuid -> new Object2ObjectOpenHashMap<>()).computeIfAbsent(Utils.getProfileId(), profileId -> ProfileAccessoryData.createDefault()).pages().int2ObjectEntrySet().stream() - .map(Entry::getValue) + Set collectedAccessories = COLLECTED_ACCESSORIES.computeIfAbsent(uuid, _uuid -> new Object2ObjectOpenHashMap<>()).computeIfAbsent(Utils.getProfileId(), profileId -> ProfileAccessoryData.createDefault()).pages().values().stream() .flatMap(ObjectOpenHashSet::stream) .filter(ACCESSORY_DATA::containsKey) .map(ACCESSORY_DATA::get) .collect(Collectors.toSet()); - //If the player has this accessory and it doesn't belong to a family + //If the player has this accessory, and it doesn't belong to a family if (collectedAccessories.contains(accessory) && accessory.family().isEmpty()) return Pair.of(AccessoryReport.HAS_HIGHEST_TIER, null); Predicate HAS_SAME_FAMILY = accessory::hasSameFamily; @@ -146,30 +140,22 @@ static Pair calculateReport4Accessory(String accessoryI .collect(Collectors.toSet()); ///If the player has the highest tier accessory in this family - //Take the the accessories in the same family as {@code accessory}, then get the one with the highest tier + //Take the accessories in the same family as {@code accessory}, then get the one with the highest tier Optional highestTierOfFamily = accessoriesInTheSameFamily.stream() .max(Comparator.comparingInt(ACCESSORY_TIER)); + int maxTierInFamily = highestTierOfFamily.orElse(Accessory.EMPTY).tier(); - if (highestTierOfFamily.isPresent()) { - Accessory highestTier = highestTierOfFamily.get(); - - if (collectedAccessoriesInTheSameFamily.contains(highestTier)) return Pair.of(AccessoryReport.HAS_HIGHEST_TIER, null); + if (collectedAccessoriesInTheSameFamily.stream().anyMatch(ca -> ca.tier() == maxTierInFamily)) return Pair.of(AccessoryReport.HAS_HIGHEST_TIER, null); - //For when the highest tier is tied - if (highestTier.hasSameFamily(accessory) && collectedAccessoriesInTheSameFamily.stream().allMatch(ca -> ca.tier() == highestTier.tier())) return Pair.of(AccessoryReport.HAS_HIGHEST_TIER, null); - } - - //If this accessory is a higher tier than all of other collected accessories in the same family + //If this accessory is a higher tier than all the other collected accessories in the same family OptionalInt highestTierOfAllCollectedInFamily = collectedAccessoriesInTheSameFamily.stream() .mapToInt(ACCESSORY_TIER) .max(); - int maxTierInFamily = highestTierOfFamily.orElse(Accessory.EMPTY).tier(); - - if (highestTierOfAllCollectedInFamily.isPresent() && accessory.tier() > highestTierOfAllCollectedInFamily.getAsInt()) return Pair.of(AccessoryReport.IS_GREATER_TIER, String.format("(%d/%d)", accessory.tier(), maxTierInFamily)); + if (accessory.tier() > highestTierOfAllCollectedInFamily.getAsInt()) return Pair.of(AccessoryReport.IS_GREATER_TIER, String.format("(%d/%d)", accessory.tier(), maxTierInFamily)); //If this accessory is a lower tier than one already obtained from same family - if (highestTierOfAllCollectedInFamily.isPresent() && accessory.tier() < highestTierOfAllCollectedInFamily.getAsInt()) return Pair.of(AccessoryReport.OWNS_BETTER_TIER, String.format("(%d/%d)", highestTierOfAllCollectedInFamily.orElse(0), maxTierInFamily)); + if (accessory.tier() < highestTierOfAllCollectedInFamily.getAsInt()) return Pair.of(AccessoryReport.OWNS_BETTER_TIER, String.format("(%d/%d)", highestTierOfAllCollectedInFamily.orElse(0), maxTierInFamily)); //If there is an accessory in the same family that has a higher tier //Take the accessories in the same family, then check if there is an accessory whose tier is greater than {@code accessory} @@ -183,9 +169,7 @@ static Pair calculateReport4Accessory(String accessoryI static void refreshData(JsonObject data) { try { - Map accessoryData = Accessory.MAP_CODEC.parse(JsonOps.INSTANCE, data).result().orElseThrow(); - - ACCESSORY_DATA = accessoryData; + ACCESSORY_DATA = Accessory.MAP_CODEC.parse(JsonOps.INSTANCE, data).result().orElseThrow(); } catch (Exception e) { LOGGER.error("[Skyblocker Accessory Helper] Failed to parse data!", e); } @@ -229,10 +213,10 @@ private boolean hasSameFamily(Accessory other) { enum AccessoryReport { HAS_HIGHEST_TIER, //You've collected the highest tier - Collected - IS_GREATER_TIER, //This accessory is an upgrade from the one in the same family that you already have - Upgrade -- Shows you what tier this accessory is in it's family + IS_GREATER_TIER, //This accessory is an upgrade from the one in the same family that you already have - Upgrade -- Shows you what tier this accessory is in its family HAS_GREATER_TIER, //This accessory has a higher tier upgrade - Upgradable -- Shows you the highest tier accessory you've collected in that family OWNS_BETTER_TIER, //You've collected an accessory in this family with a higher tier - Downgrade -- Shows you the highest tier accessory you've collected in that family MISSING, //You don't have any accessories in this family - Missing - INELIGIBLE; + INELIGIBLE } } From 69d925788dcb7ef23157acdd97e184b66f08e622 Mon Sep 17 00:00:00 2001 From: Kevinthegreat <92656833+kevinthegreat1@users.noreply.github.com> Date: Wed, 3 Apr 2024 17:56:24 -0400 Subject: [PATCH 6/6] Show accessory tier change --- .../skyblocker/skyblock/item/tooltip/AccessoriesHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java index 358f3d85bf..69bc6f1cba 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java @@ -152,10 +152,10 @@ static Pair calculateReport4Accessory(String accessoryI .mapToInt(ACCESSORY_TIER) .max(); - if (accessory.tier() > highestTierOfAllCollectedInFamily.getAsInt()) return Pair.of(AccessoryReport.IS_GREATER_TIER, String.format("(%d/%d)", accessory.tier(), maxTierInFamily)); + if (accessory.tier() > highestTierOfAllCollectedInFamily.getAsInt()) return Pair.of(AccessoryReport.IS_GREATER_TIER, String.format("(%d→%d/%d)", highestTierOfAllCollectedInFamily.orElse(0), accessory.tier(), maxTierInFamily)); //If this accessory is a lower tier than one already obtained from same family - if (accessory.tier() < highestTierOfAllCollectedInFamily.getAsInt()) return Pair.of(AccessoryReport.OWNS_BETTER_TIER, String.format("(%d/%d)", highestTierOfAllCollectedInFamily.orElse(0), maxTierInFamily)); + if (accessory.tier() < highestTierOfAllCollectedInFamily.getAsInt()) return Pair.of(AccessoryReport.OWNS_BETTER_TIER, String.format("(%d→%d/%d)", highestTierOfAllCollectedInFamily.orElse(0), accessory.tier(), maxTierInFamily)); //If there is an accessory in the same family that has a higher tier //Take the accessories in the same family, then check if there is an accessory whose tier is greater than {@code accessory}