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: Accessories Helper --- .../java/de/hysky/skyblocker/SkyblockerMod.java | 2 + .../hysky/skyblocker/config/SkyblockerConfig.java | 3 + .../config/categories/GeneralCategory.java | 10 + .../skyblock/item/tooltip/AccessoriesHelper.java | 230 +++++++++++++++++++++ .../skyblock/item/tooltip/ItemTooltip.java | 23 +++ .../skyblock/item/tooltip/TooltipInfoType.java | 31 ++- .../resources/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 (limited to 'src/main') diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index e334ef86..ee82209d 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.end.TheEnd; 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 class SkyblockerMod implements ClientModInitializer { 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 a3e710c1..8d65806f 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 class SkyblockerConfig { @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 23ce7bb6..abe20e18 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 class GeneralCategory { 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 00000000..b5291af6 --- /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 1b3f402d..8a11b9bd 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.SkyblockerMod; 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 class ItemTooltip { } } } + + if (TooltipInfoType.ACCESSORIES.isTooltipEnabledAndHasOrNullWarning(internalID)) { + AccessoryReport report = AccessoriesHelper.calculateReport4Accessory(internalID); + + if (report != AccessoryReport.INELIGIBLE) { + MutableText title = Text.literal(String.format("%-19s", "Accessory: ")).withColor(0xf57542); + + Text stateText = switch (report) { + 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 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 class ItemTooltip { 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 4aba040d..d0cf91b8 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.SkyblockerConfig; 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 enum TooltipInfoType implements Runnable { 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 ff0ffa11..380bcdc1 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", -- cgit 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: Use hysky redirect --- .../java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main') 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 d0cf91b8..6edee8d6 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; -- cgit 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: Enhance the accessories helper --- .../skyblock/item/tooltip/AccessoriesHelper.java | 36 ++++++++++++---------- .../skyblock/item/tooltip/ItemTooltip.java | 13 ++++---- .../resources/assets/skyblocker/lang/en_us.json | 6 ++-- 3 files changed, 30 insertions(+), 25 deletions(-) (limited to 'src/main') 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 b5291af6..a3fc40d5 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 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.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 class AccessoriesHelper { 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 @@ public class AccessoriesHelper { 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 @@ public class AccessoriesHelper { .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 @@ public class AccessoriesHelper { .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 @@ public class AccessoriesHelper { .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 @@ public class AccessoriesHelper { .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 @@ public class AccessoriesHelper { /** * @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 @@ public class AccessoriesHelper { 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 @@ public class AccessoriesHelper { 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 8a11b9bd..637aea22 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.Constants; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.scheduler.Scheduler; +import it.unimi.dsi.fastutil.Pair; import net.minecraft.client.MinecraftClient; import net.minecraft.client.item.TooltipContext; import net.minecraft.item.DyeableItem; @@ -241,16 +242,16 @@ public class ItemTooltip { } if (TooltipInfoType.ACCESSORIES.isTooltipEnabledAndHasOrNullWarning(internalID)) { - AccessoryReport report = AccessoriesHelper.calculateReport4Accessory(internalID); + Pair 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 380bcdc1..7fd2159f 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", -- cgit 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: Page based data storage --- .../skyblock/item/tooltip/AccessoriesHelper.java | 44 ++++++++++++---------- 1 file changed, 24 insertions(+), 20 deletions(-) (limited to 'src/main') 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 a3fc40d5..951d8d5f 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.Set; 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.SkyblockerMod; 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 @@ 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+\\)"); + 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 class AccessoriesHelper { 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 @@ public class AccessoriesHelper { 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 @@ public class AccessoriesHelper { 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 @@ public class AccessoriesHelper { 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 @@ public class AccessoriesHelper { } } - 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<>()); } } -- cgit 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: Refactor AccessoriesHelper --- .../skyblock/item/tooltip/AccessoriesHelper.java | 46 +++++++--------------- 1 file changed, 15 insertions(+), 31 deletions(-) (limited to 'src/main') 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 951d8d5f..358f3d85 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.SkyblockerMod; 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 @@ 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+\\)"); + 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 @@ public class AccessoriesHelper { .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 @@ public class AccessoriesHelper { 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 @@ public class AccessoriesHelper { .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 @@ public class AccessoriesHelper { 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 @@ public class AccessoriesHelper { 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 } } -- cgit 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: Show accessory tier change --- .../de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/main') 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 358f3d85..69bc6f1c 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 @@ public class AccessoriesHelper { .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} -- cgit