From cb5f8faf39fee4077543c2599120e951be79c5c4 Mon Sep 17 00:00:00 2001 From: Kevin <92656833+kevinthegreat1@users.noreply.github.com> Date: Wed, 9 Jul 2025 14:12:37 +0800 Subject: Refactor NEURepoManager (#1480) * Refactor NEURepoManager * Clear ItemRepository caches * Fix import --- .../skyblocker/skyblock/ItemPickupWidget.java | 7 +- .../skyblocker/skyblock/SackItemAutocomplete.java | 4 +- .../skyblocker/skyblock/chat/SackMessagePrice.java | 34 +-- .../skyblock/garden/visitor/VisitorHelper.java | 5 +- .../item/tooltip/adders/CraftPriceTooltip.java | 4 +- .../skyblock/itemlist/ItemRepository.java | 228 +++++++------- .../skyblock/itemlist/ItemStackBuilder.java | 2 +- .../skyblock/itemlist/StackOverlays.java | 2 +- .../skyblock/profileviewer/inventory/Pet.java | 13 +- .../skyblock/searchoverlay/SearchOverManager.java | 6 +- .../skyblocker/skyblock/waypoint/FairySouls.java | 4 +- .../de/hysky/skyblocker/utils/NEURepoManager.java | 326 +++++++++++++-------- 12 files changed, 354 insertions(+), 281 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/de/hysky/skyblocker/skyblock/ItemPickupWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/ItemPickupWidget.java index b6f5265b..9ca0d1b3 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/ItemPickupWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/ItemPickupWidget.java @@ -59,13 +59,12 @@ public class ItemPickupWidget extends ComponentBasedWidget { */ private static ItemStack getItem(String itemName) { if (NEURepoManager.isLoading() || !ItemRepository.filesImported()) return new ItemStack(Items.BARRIER); - return NEURepoManager.NEU_REPO.getItems().getItems() - .values().stream() - .filter(item -> Formatting.strip(item.getDisplayName()).equals(itemName)) + return NEURepoManager.getItemByName(itemName) + .stream() .findFirst() .map(NEUItem::getSkyblockItemId) .map(ItemRepository::getItemStack) - .orElse(new ItemStack(Items.BARRIER)); + .orElseGet(() -> new ItemStack(Items.BARRIER)); } /** diff --git a/src/main/java/de/hysky/skyblocker/skyblock/SackItemAutocomplete.java b/src/main/java/de/hysky/skyblocker/skyblock/SackItemAutocomplete.java index 8646300c..7168b24d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/SackItemAutocomplete.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/SackItemAutocomplete.java @@ -44,7 +44,7 @@ public class SackItemAutocomplete { } private static void loadSackItems() { - try (InputStream stream = NEURepoManager.NEU_REPO.file("constants/sacks.json").stream()) { + try (InputStream stream = NEURepoManager.file("constants/sacks.json").stream()) { JsonObject sacks = JsonParser.parseString(new String(stream.readAllBytes())).getAsJsonObject().getAsJsonObject("sacks"); Set sackItemIds = sacks.entrySet().stream() @@ -56,7 +56,7 @@ public class SackItemAutocomplete { .collect(Collectors.toUnmodifiableSet()); Set sackItems = sackItemIds.stream() .map(neuId -> { - NEUItem stack = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(neuId); + NEUItem stack = NEURepoManager.getItemByNeuId(neuId); return stack != null ? Formatting.strip(stack.getDisplayName()) : neuId; }) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/SackMessagePrice.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/SackMessagePrice.java index 57205e6f..565d3346 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chat/SackMessagePrice.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/SackMessagePrice.java @@ -9,7 +9,6 @@ import de.hysky.skyblocker.utils.NEURepoManager; import de.hysky.skyblocker.utils.RegexUtils; import io.github.moulberry.repo.data.NEUItem; import it.unimi.dsi.fastutil.objects.*; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientWorldEvents; import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; import net.minecraft.screen.ScreenTexts; import net.minecraft.text.HoverEvent.ShowText; @@ -36,21 +35,9 @@ import java.util.regex.Pattern; public class SackMessagePrice { private static final Pattern ITEM_COUNT_PATTERN = Pattern.compile("([-+][\\d,]+)"); private static final Logger LOGGER = LoggerFactory.getLogger(SackMessagePrice.class); - /** - *

- * Cache that holds item name to NEU ID mappings. - * This helps over time when farming similar items in the same world, as it avoids repeated lookups in a very large item list. - * The Sack message is only sent like every 30s in a normal farming case, so there's not much performance impact anyway, but it still helps. - *

- *

- * Additionally, there are 2 identical hover events per message, and they are processed separately, so this cache will be hit at least once per message, cutting the lookup time in half or more. - *

- */ - private static final Object2ObjectOpenHashMap NAME_2_ID_CACHE = new Object2ObjectOpenHashMap<>(); @Init public static void init() { - ClientWorldEvents.AFTER_CLIENT_WORLD_CHANGE.register(((client, world) -> NAME_2_ID_CACHE.clear())); ClientReceiveMessageEvents.MODIFY_GAME.register(SackMessagePrice::onMessage); } @@ -156,19 +143,14 @@ public class SackMessagePrice { @Nullable private static String getNeuId(@NotNull String itemName) { - return NAME_2_ID_CACHE.computeIfAbsent(itemName, ignored -> - NEURepoManager.NEU_REPO.getItems() - .getItems() - .values() - .stream() - .filter(item -> Formatting.strip(item.getDisplayName()).equals(itemName)) - .findFirst() - .map(NEUItem::getSkyblockItemId) - .orElseGet(() -> { - LOGGER.warn("Failed to find item ID for item: {}", itemName); - return null; // This won't be entered into the cache, nor will it be used to calculate the price - }) - ); + return NEURepoManager.getItemByName(itemName) + .stream() + .findFirst() + .map(NEUItem::getSkyblockItemId) + .orElseGet(() -> { + LOGGER.warn("Failed to find the NEU item ID for item: {}", itemName); + return null; // This won't be used to calculate the price + }); } @NotNull diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/visitor/VisitorHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/visitor/VisitorHelper.java index 49085eb1..b26c44ee 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/visitor/VisitorHelper.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/visitor/VisitorHelper.java @@ -141,9 +141,8 @@ public class VisitorHelper { return cachedItems.computeIfAbsent(cleanName, name -> { if (NEURepoManager.isLoading() || !ItemRepository.filesImported()) return null; - return NEURepoManager.NEU_REPO.getItems().getItems() - .values().stream() - .filter(item -> Formatting.strip(item.getDisplayName()).equals(name)) + return NEURepoManager.getItemByName(itemName) + .stream() .findFirst() .map(NEUItem::getSkyblockItemId) .map(ItemRepository::getItemStack) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java index 35171251..2dbd65a5 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java @@ -41,7 +41,7 @@ public class CraftPriceTooltip extends SimpleTooltipAdder { return; } - NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(stack.getNeuName()); + NEUItem neuItem = NEURepoManager.getItemByNeuId(stack.getNeuName()); if (neuItem == null) return; List neuRecipes = neuItem.getRecipes(); @@ -87,7 +87,7 @@ public class CraftPriceTooltip extends SimpleTooltipAdder { cachedCraftCosts.put(inputItemName, itemCost); } - NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(inputItemName); + NEUItem neuItem = NEURepoManager.getItemByNeuId(inputItemName); if (neuItem != null) { List neuRecipes = neuItem.getRecipes(); if (!neuRecipes.isEmpty()) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRepository.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRepository.java index ac58cdcf..f304d03f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRepository.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRepository.java @@ -7,10 +7,10 @@ import de.hysky.skyblocker.skyblock.itemlist.recipes.SkyblockRecipe; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.NEURepoManager; import io.github.moulberry.repo.data.NEUCraftingRecipe; +import io.github.moulberry.repo.data.NEUForgeRecipe; import io.github.moulberry.repo.data.NEUItem; import io.github.moulberry.repo.data.NEURecipe; import io.github.moulberry.repo.util.NEUId; -import io.github.moulberry.repo.data.*; import net.minecraft.item.ItemStack; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -20,127 +20,137 @@ import java.util.*; import java.util.stream.Stream; public class ItemRepository { - protected static final Logger LOGGER = LoggerFactory.getLogger(ItemRepository.class); + protected static final Logger LOGGER = LoggerFactory.getLogger(ItemRepository.class); - private static final List items = new ArrayList<>(); - private static final Map itemsMap = new HashMap<>(); - private static final List recipes = new ArrayList<>(); + private static final List items = new ArrayList<>(); + private static final Map itemsMap = new HashMap<>(); + private static final List recipes = new ArrayList<>(); private static final HashMap bazaarStocks = new HashMap<>(); - private static boolean filesImported = false; - - @Init - public static void init() { - NEURepoManager.runAsyncAfterLoad(ItemStackBuilder::loadPetNums); - NEURepoManager.runAsyncAfterLoad(ItemRepository::importItemFiles); + /** + * Consumers must check this field when accessing `items` and `itemsMap`, or else thread safety is not guaranteed. + */ + private static boolean itemsImported = false; + /** + * Consumers must check this field when accessing `recipes`, or else thread safety is not guaranteed. + */ + private static boolean filesImported = false; + + @Init + public static void init() { + NEURepoManager.runAsyncAfterLoad(ItemStackBuilder::loadPetNums); + NEURepoManager.runAsyncAfterLoad(ItemRepository::importItemFiles); NEURepoManager.runAsyncAfterLoad(ItemRepository::loadBazaarStocks); - } - - private static void importItemFiles() { - NEURepoManager.NEU_REPO.getItems().getItems().values().forEach(ItemRepository::loadItem); - NEURepoManager.NEU_REPO.getItems().getItems().values().forEach(ItemRepository::loadRecipes); - - items.sort((lhs, rhs) -> { - String lhsInternalName = ItemUtils.getItemId(lhs); - String lhsFamilyName = lhsInternalName.replaceAll(".\\d+$", ""); - String rhsInternalName = ItemUtils.getItemId(rhs); - String rhsFamilyName = rhsInternalName.replaceAll(".\\d+$", ""); - if (lhsFamilyName.equals(rhsFamilyName)) { - if (lhsInternalName.length() != rhsInternalName.length()) - return lhsInternalName.length() - rhsInternalName.length(); - else return lhsInternalName.compareTo(rhsInternalName); - } - return lhsFamilyName.compareTo(rhsFamilyName); - }); - filesImported = true; - } - - private static void loadItem(NEUItem item) { - try { - ItemStack stack = ItemStackBuilder.fromNEUItem(item); - StackOverlays.applyOverlay(item, stack); - - items.add(stack); - itemsMap.put(item.getSkyblockItemId(), stack); - } catch (Exception e) { - LOGGER.error("[Skyblocker Item Repo Loader] Failed to load item, please report this! Skyblock Id: {}", item.getSkyblockItemId(), e); - } - } - - private static void loadRecipes(NEUItem item) { - item.getRecipes().stream().map(ItemRepository::toSkyblockRecipe).filter(Objects::nonNull).forEach(recipes::add); - } + } + + private static void importItemFiles() { + itemsImported = false; + filesImported = false; + + items.clear(); + itemsMap.clear(); + recipes.clear(); + + NEURepoManager.forEachItem(ItemRepository::loadItem); + items.sort(Comparator.comparing(stack -> ItemUtils.getItemId(stack).replaceAll(".\\d+$", "")) + .thenComparingInt(stack -> ItemUtils.getItemId(stack).length()) + .thenComparing(ItemUtils::getItemId) + ); + itemsImported = true; + + NEURepoManager.forEachItem(ItemRepository::loadRecipes); + filesImported = true; + } + + private static void loadItem(NEUItem item) { + try { + ItemStack stack = ItemStackBuilder.fromNEUItem(item); + StackOverlays.applyOverlay(item, stack); + + items.add(stack); + itemsMap.put(item.getSkyblockItemId(), stack); + } catch (Exception e) { + LOGGER.error("[Skyblocker Item Repo Loader] Failed to load item, please report this! Skyblock Id: {}", item.getSkyblockItemId(), e); + } + } + + private static void loadRecipes(NEUItem item) { + item.getRecipes().stream().map(ItemRepository::toSkyblockRecipe).filter(Objects::nonNull).forEach(recipes::add); + } private static void loadBazaarStocks() { bazaarStocks.clear(); - NEURepoManager.NEU_REPO.getConstants().getBazaarStocks().getStocks().forEach((String neuId, String skyblockId) -> bazaarStocks.put(skyblockId, neuId)); - } - - public static String getWikiLink(String neuId, boolean useOfficial) { - NEUItem item = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(neuId); - if (item == null || item.getInfo() == null || item.getInfo().isEmpty()) { - return null; - } - - List info = item.getInfo(); - String wikiLink0 = info.getFirst(); - String wikiLink1 = info.size() > 1 ? info.get(1) : ""; - String wikiDomain = useOfficial ? "https://wiki.hypixel.net" : "https://hypixel-skyblock.fandom.com"; - if (wikiLink0.startsWith(wikiDomain)) { - return wikiLink0; - } else if (wikiLink1.startsWith(wikiDomain)) { - return wikiLink1; - } - return null; - } - - public static List getRecipesAndUsages(ItemStack stack) { - return Stream.concat(getRecipes(stack), getUsages(stack)).toList(); - } - - public static boolean filesImported() { - return filesImported; - } - - public static void setFilesImported(boolean filesImported) { - ItemRepository.filesImported = filesImported; - } - - public static List getItems() { - return items; - } - - public static Stream getItemsStream() { - if (!filesImported) return Stream.empty(); - return items.stream(); - } + NEURepoManager.getConstants().getBazaarStocks().getStocks().forEach((String neuId, String skyblockId) -> bazaarStocks.put(skyblockId, neuId)); + } + + public static String getWikiLink(String neuId, boolean useOfficial) { + NEUItem item = NEURepoManager.getItemByNeuId(neuId); + if (item == null || item.getInfo() == null || item.getInfo().isEmpty()) { + return null; + } + + List info = item.getInfo(); + String wikiLink0 = info.getFirst(); + String wikiLink1 = info.size() > 1 ? info.get(1) : ""; + String wikiDomain = useOfficial ? "https://wiki.hypixel.net" : "https://hypixel-skyblock.fandom.com"; + if (wikiLink0.startsWith(wikiDomain)) { + return wikiLink0; + } else if (wikiLink1.startsWith(wikiDomain)) { + return wikiLink1; + } + return null; + } + + public static List getRecipesAndUsages(ItemStack stack) { + return Stream.concat(getRecipes(stack), getUsages(stack)).toList(); + } + + public static boolean filesImported() { + return filesImported; + } + + public static void setFilesImported() { + itemsImported = false; + filesImported = false; + } + + public static List getItems() { + return itemsImported ? items : List.of(); + } + + public static Stream getItemsStream() { + return itemsImported ? items.stream() : Stream.empty(); + } public static Map getBazaarStocks() { + // This is not protected by `filesImported` because it is loaded asynchronously separately from `items`, `itemsMap`, and `recipes`. return bazaarStocks; } - /** - * @param neuId the NEU item id gotten through {@link NEUItem#getSkyblockItemId()}, {@link ItemStack#getNeuName()}, or {@link ItemUtils#getNeuId(ItemStack) ItemTooltip#getNeuName(String, String)} - */ - @Nullable - public static ItemStack getItemStack(String neuId) { - return itemsMap.get(neuId); - } + /** + * @param neuId the NEU item id gotten through {@link NEUItem#getSkyblockItemId()}, {@link ItemStack#getNeuName()}, or {@link ItemUtils#getNeuId(ItemStack) ItemTooltip#getNeuName(String, String)} + */ + @Nullable + public static ItemStack getItemStack(String neuId) { + return itemsImported ? itemsMap.get(neuId) : null; + } - public static Stream getRecipesStream() {return recipes.stream(); } + public static Stream getRecipesStream() { + return filesImported ? recipes.stream() : Stream.empty(); + } - public static Stream getRecipes(ItemStack stack) { - return NEURepoManager.RECIPE_CACHE.getRecipes().getOrDefault(stack.getNeuName(), Set.of()).stream().map(ItemRepository::toSkyblockRecipe).filter(Objects::nonNull); - } + public static Stream getRecipes(ItemStack stack) { + return NEURepoManager.getRecipes().getOrDefault(stack.getNeuName(), Set.of()).stream().map(ItemRepository::toSkyblockRecipe).filter(Objects::nonNull); + } - public static Stream getUsages(ItemStack stack) { - return NEURepoManager.RECIPE_CACHE.getUsages().getOrDefault(stack.getNeuName(), Set.of()).stream().map(ItemRepository::toSkyblockRecipe).filter(Objects::nonNull); - } + public static Stream getUsages(ItemStack stack) { + return NEURepoManager.getUsages().getOrDefault(stack.getNeuName(), Set.of()).stream().map(ItemRepository::toSkyblockRecipe).filter(Objects::nonNull); + } - private static SkyblockRecipe toSkyblockRecipe(NEURecipe neuRecipe) { - return switch (neuRecipe) { - case NEUCraftingRecipe craftingRecipe -> new SkyblockCraftingRecipe(craftingRecipe); - case NEUForgeRecipe forgeRecipe -> new SkyblockForgeRecipe(forgeRecipe); - case null, default -> null; - }; - } + private static SkyblockRecipe toSkyblockRecipe(NEURecipe neuRecipe) { + return switch (neuRecipe) { + case NEUCraftingRecipe craftingRecipe -> new SkyblockCraftingRecipe(craftingRecipe); + case NEUForgeRecipe forgeRecipe -> new SkyblockForgeRecipe(forgeRecipe); + case null, default -> null; + }; + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java index 636523ca..fe06352e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java @@ -27,7 +27,7 @@ public class ItemStackBuilder { protected static void loadPetNums() { try { - petNums = NEURepoManager.NEU_REPO.getConstants().getPetNumbers(); + petNums = NEURepoManager.getConstants().getPetNumbers(); } catch (Exception e) { ItemRepository.LOGGER.error("Failed to load petnums.json"); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/StackOverlays.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/StackOverlays.java index 314ec244..aeaf4f09 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/StackOverlays.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/StackOverlays.java @@ -32,7 +32,7 @@ public class StackOverlays { */ protected static void applyOverlay(NEUItem neuItem, ItemStack stack) { try { - NEURepoFile file = NEURepoManager.NEU_REPO.file(OVERLAY_DIRECTORY + "/" + neuItem.getSkyblockItemId() + ".snbt"); + NEURepoFile file = NEURepoManager.file(OVERLAY_DIRECTORY + "/" + neuItem.getSkyblockItemId() + ".snbt"); //The returned file is null if it does not exist if (file != null) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java index 503155e6..89b49aa2 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java @@ -96,18 +96,16 @@ public class Pet { private ItemStack createIcon() { if (NEURepoManager.isLoading() || !ItemRepository.filesImported()) return Ico.BARRIER; - Map items = NEURepoManager.NEU_REPO.getItems().getItems(); - if (items == null) return Ico.BARRIER; String targetItemId = this.getName() + ";" + (this.getTier() + (heldItem.isPresent() && heldItem.get().equals("PET_ITEM_TIER_BOOST") ? 1 : 0)); - NEUItem item = NEURepoManager.NEU_REPO.getItems().getItems().get(targetItemId); + NEUItem item = NEURepoManager.getItemByNeuId(targetItemId); // For cases life RIFT_FERRET Where it can be tier boosted into a pet that otherwise can't exist if (item == null && heldItem.isPresent() && heldItem.get().equals("PET_ITEM_TIER_BOOST")) { - item = NEURepoManager.NEU_REPO.getItems().getItems().get(getName() + ";" + getTier()); + item = NEURepoManager.getItemByNeuId(getName() + ";" + getTier()); } - return fromNEUItem(item, this.heldItem.map(ItemRepository::getItemStack).orElse(null)); + return item == null ? Ico.BARRIER : fromNEUItem(item, this.heldItem.map(ItemRepository::getItemStack).orElse(null)); } /** @@ -148,7 +146,8 @@ public class Pet { // Skin Head Texture if (skinTexture.isPresent() && skin.isPresent()) { - formattedLore.set(0, Text.of(formattedLore.getFirst().getString() + ", " + Formatting.strip(NEURepoManager.NEU_REPO.getItems().getItems().get("PET_SKIN_" + skin.get()).getDisplayName()))); + NEUItem skinItem = NEURepoManager.getItemByNeuId("PET_SKIN_" + skin.get()); + if (skinItem != null) formattedLore.set(0, Text.of(formattedLore.getFirst().getString() + ", " + Formatting.strip(skinItem.getDisplayName()))); petStack.set(DataComponentTypes.PROFILE, skinTexture.get()); } @@ -169,7 +168,7 @@ public class Pet { * @return Formatted lore with injected stats inserted into the tooltip */ private List processLore(List lore, ItemStack heldItem) { - Map> petNums = NEURepoManager.NEU_REPO.getConstants().getPetNumbers(); + Map> petNums = NEURepoManager.getConstants().getPetNumbers(); Rarity rarity = Rarity.values()[getTier()]; PetNumbers data = petNums.get(getName()).get(rarity); List formattedLore = new ArrayList<>(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/searchoverlay/SearchOverManager.java b/src/main/java/de/hysky/skyblocker/skyblock/searchoverlay/SearchOverManager.java index 721db667..c06e2609 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/searchoverlay/SearchOverManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/searchoverlay/SearchOverManager.java @@ -145,7 +145,7 @@ public class SearchOverManager { } //look up id for name - NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(id); + NEUItem neuItem = NEURepoManager.getItemByNeuId(id); if (neuItem != null) { name = Formatting.strip(neuItem.getDisplayName()); bazaarItems.add(name); @@ -158,7 +158,7 @@ public class SearchOverManager { //get auction items try { - Set<@NEUId String> essenceCosts = NEURepoManager.NEU_REPO.getConstants().getEssenceCost().getCosts().keySet(); + Set<@NEUId String> essenceCosts = NEURepoManager.getConstants().getEssenceCost().getCosts().keySet(); if (TooltipInfoType.THREE_DAY_AVERAGE.getData() == null) { TooltipInfoType.THREE_DAY_AVERAGE.run(); } @@ -166,7 +166,7 @@ public class SearchOverManager { String id = entry.getKey(); //look up in NEU repo. id = id.split("[+-]")[0]; - NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(id); + NEUItem neuItem = NEURepoManager.getItemByNeuId(id); if (neuItem != null) { String name = Formatting.strip(neuItem.getDisplayName()); //add names that are pets to the list of pets to work with the lvl 100 button diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/FairySouls.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/FairySouls.java index d695bc19..2c4ec4b8 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/FairySouls.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/FairySouls.java @@ -71,8 +71,8 @@ public class FairySouls { private static void loadFairySouls() { fairySoulsLoaded = NEURepoManager.runAsyncAfterLoad(() -> { - maxSouls = NEURepoManager.NEU_REPO.getConstants().getFairySouls().getMaxSouls(); - NEURepoManager.NEU_REPO.getConstants().getFairySouls().getSoulLocations().forEach((location, fairiesForLocation) -> fairySouls.put(location, fairiesForLocation.stream().map(coordinate -> new BlockPos(coordinate.getX(), coordinate.getY(), coordinate.getZ())).collect(Collectors.toUnmodifiableMap(pos -> pos, pos -> new FairySoul(pos, TYPE_SUPPLIER, ColorUtils.getFloatComponents(DyeColor.GREEN), ColorUtils.getFloatComponents(DyeColor.RED)))))); + maxSouls = NEURepoManager.getConstants().getFairySouls().getMaxSouls(); + NEURepoManager.getConstants().getFairySouls().getSoulLocations().forEach((location, fairiesForLocation) -> fairySouls.put(location, fairiesForLocation.stream().map(coordinate -> new BlockPos(coordinate.getX(), coordinate.getY(), coordinate.getZ())).collect(Collectors.toUnmodifiableMap(pos -> pos, pos -> new FairySoul(pos, TYPE_SUPPLIER, ColorUtils.getFloatComponents(DyeColor.GREEN), ColorUtils.getFloatComponents(DyeColor.RED)))))); LOGGER.debug("[Skyblocker] Loaded {} fairy souls across {} locations", fairySouls.values().stream().mapToInt(Map::size).sum(), fairySouls.size()); try (BufferedReader reader = Files.newBufferedReader(SkyblockerMod.CONFIG_DIR.resolve("found_fairy_souls.json"))) { diff --git a/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java b/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java index 465a263f..46ff8318 100644 --- a/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java +++ b/src/main/java/de/hysky/skyblocker/utils/NEURepoManager.java @@ -1,13 +1,20 @@ package de.hysky.skyblocker.utils; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; import com.mojang.brigadier.Command; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.events.SkyblockEvents; -import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.utils.scheduler.Scheduler; +import io.github.moulberry.repo.NEUConstants; import io.github.moulberry.repo.NEURecipeCache; +import io.github.moulberry.repo.NEURepoFile; import io.github.moulberry.repo.NEURepository; +import io.github.moulberry.repo.data.NEUItem; +import io.github.moulberry.repo.data.NEURecipe; +import io.github.moulberry.repo.util.NEUId; import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.minecraft.client.MinecraftClient; @@ -15,139 +22,216 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.packet.s2c.play.SynchronizeRecipesS2CPacket; import net.minecraft.recipe.display.CuttingRecipeDisplay; import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import org.apache.commons.lang3.function.Consumers; +import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; /** * Initializes the NEU repo, which contains item metadata and fairy souls location data. Clones the repo if it does not exist and checks for updates. Use {@link #runAsyncAfterLoad(Runnable)} to run code after the repo is initialized. */ public class NEURepoManager { - private static final Logger LOGGER = LoggerFactory.getLogger(NEURepoManager.class); - public static final String REMOTE_REPO_URL = "https://github.com/NotEnoughUpdates/NotEnoughUpdates-REPO.git"; - /** - * Use {@link #NEU_REPO}. - */ - private static final Path LOCAL_REPO_DIR = SkyblockerMod.CONFIG_DIR.resolve("item-repo"); // TODO rename to NotEnoughUpdates-REPO - private static CompletableFuture REPO_LOADING = loadRepository().thenAccept(Consumers.nop()); - public static final NEURepository NEU_REPO = NEURepository.of(LOCAL_REPO_DIR); - public static final NEURecipeCache RECIPE_CACHE = NEURecipeCache.forRepo(NEU_REPO); - - /** - * Adds command to update repository manually from ingame. - *

- * TODO A button could be added to the settings menu that will trigger this command. - */ - @Init - public static void init() { - ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> - dispatcher.register(ClientCommandManager.literal(SkyblockerMod.NAMESPACE) - .then(ClientCommandManager.literal("updateRepository").executes(context -> { - deleteAndDownloadRepository(context.getSource().getPlayer()); - return Command.SINGLE_SUCCESS; - })) - ) - ); - SkyblockEvents.JOIN.register(NEURepoManager::handleRecipeSynchronization); - } - - /** - * load the recipe manually because Hypixel doesn't send any vanilla recipes to the client - */ - private static void handleRecipeSynchronization() { - MinecraftClient client = MinecraftClient.getInstance(); - if (client.world != null && client.getNetworkHandler() != null) { - //FIXME not sure if we even need this - depends on how REI, EMI, and JEI adapt to the changes - SynchronizeRecipesS2CPacket packet = new SynchronizeRecipesS2CPacket(Map.of(), CuttingRecipeDisplay.Grouping.empty()); - - try { - client.getNetworkHandler().onSynchronizeRecipes(packet); - } catch (Exception e) { - LOGGER.info("[Skyblocker NEU Repo] recipe sync error", e); - } - } - } - - public static boolean isLoading() { - return REPO_LOADING != null && !REPO_LOADING.isDone(); - } - - private static CompletableFuture loadRepository() { - return CompletableFuture.supplyAsync(() -> { - boolean success = true; - try { - if (Files.isDirectory(NEURepoManager.LOCAL_REPO_DIR)) { - try (Git localRepo = Git.open(NEURepoManager.LOCAL_REPO_DIR.toFile())) { - localRepo.pull().setRebase(true).call(); - LOGGER.info("[Skyblocker NEU Repo] NEU Repository Updated"); - } - } else { - Git.cloneRepository().setURI(REMOTE_REPO_URL).setDirectory(NEURepoManager.LOCAL_REPO_DIR.toFile()).setBranchesToClone(List.of("refs/heads/master")).setBranch("refs/heads/master").call().close(); - LOGGER.info("[Skyblocker NEU Repo] NEU Repository Downloaded"); - } - } catch (TransportException e) { - LOGGER.error("[Skyblocker NEU Repo] Transport operation failed. Most likely unable to connect to the remote NEU repo on github", e); - success = false; - } catch (RepositoryNotFoundException e) { - LOGGER.warn("[Skyblocker NEU Repo] Local NEU Repository not found or corrupted, downloading new one", e); - Scheduler.INSTANCE.schedule(() -> deleteAndDownloadRepository(MinecraftClient.getInstance().player), 1); - success = false; - } catch (Exception e) { - LOGGER.error("[Skyblocker NEU Repo] Encountered unknown exception while downloading NEU Repository", e); - success = false; - } - - try { - NEU_REPO.reload(); - } catch (Exception e) { - LOGGER.error("[Skyblocker NEU Repo] Encountered unknown exception while loading NEU Repository", e); - success = false; - } - return success; - }); - } - - private static void deleteAndDownloadRepository(PlayerEntity player) { - if (isLoading()) { - sendMessage(player, Constants.PREFIX.get().append(Text.translatable("skyblocker.updateRepository.loading"))); - return; - } - sendMessage(player, Constants.PREFIX.get().append(Text.translatable("skyblocker.updateRepository.start"))); - - REPO_LOADING = CompletableFuture.runAsync(() -> { - try { - ItemRepository.setFilesImported(false); - FileUtils.recursiveDelete(NEURepoManager.LOCAL_REPO_DIR); - sendMessage(player, Constants.PREFIX.get().append(Text.translatable("skyblocker.updateRepository.deleted"))); - sendMessage(player, Constants.PREFIX.get().append(Text.translatable(loadRepository().join() ? "skyblocker.updateRepository.success" : "skyblocker.updateRepository.failed"))); - } catch (Exception e) { - LOGGER.error("[Skyblocker NEU Repo] Encountered unknown exception while deleting the NEU repo", e); - sendMessage(player, Constants.PREFIX.get().append(Text.translatable("skyblocker.updateRepository.error"))); - } - }); - } - - /** - * Runs the given runnable after the NEU repo is initialized. - * @param runnable the runnable to run - * @return a completable future of the given runnable - */ - public static CompletableFuture runAsyncAfterLoad(Runnable runnable) { - return REPO_LOADING.thenRunAsync(runnable); - } - - private static void sendMessage(PlayerEntity player, Text text) { - if (player != null) { - player.sendMessage(text, false); - } - } + private static final Logger LOGGER = LoggerFactory.getLogger(NEURepoManager.class); + private static final String REMOTE_REPO_URL = "https://github.com/NotEnoughUpdates/NotEnoughUpdates-REPO.git"; + /** + * @see #NEU_REPO + */ + private static final Path LOCAL_REPO_DIR = SkyblockerMod.CONFIG_DIR.resolve("item-repo"); // TODO rename to NotEnoughUpdates-REPO + /** + * @see #isLoading() + */ + private static CompletableFuture REPO_LOADING = loadRepository().thenAccept(Consumers.nop()); + /** + * Use public methods instead of this field. + * + * @see #getItemByName(String) + * @see #getConstants() + */ + private static final NEURepository NEU_REPO = NEURepository.of(LOCAL_REPO_DIR); + /** + * @see #getRecipes() + * @see #getUsages() + */ + private static final NEURecipeCache RECIPE_CACHE = NEURecipeCache.forRepo(NEU_REPO); + /** + * Store after load runnables so we can execute them after each time the repository is (re)loaded. + */ + private static final List afterLoadTasks = new ArrayList<>(); + /** + * A cache containing NEUItems indexed by their display name. + */ + private static Multimap nameToNEUItem = HashMultimap.create(); + + /** + * Adds command to update the repository manually from ingame. + *

+ * TODO A button could be added to the settings menu that will trigger this command. + */ + @Init + public static void init() { + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> + dispatcher.register(ClientCommandManager.literal(SkyblockerMod.NAMESPACE) + .then(ClientCommandManager.literal("updateRepository").executes(context -> { + deleteAndDownloadRepository(context.getSource().getPlayer()); + return Command.SINGLE_SUCCESS; + })) + ) + ); + SkyblockEvents.JOIN.register(NEURepoManager::handleRecipeSynchronization); + runAsyncAfterLoad(NEURepoManager::loadNameToNEUItemMap); // Loads the NEUItem name cache after the repository is laoded. + } + + /** + * load the recipe manually because Hypixel doesn't send any vanilla recipes to the client + */ + private static void handleRecipeSynchronization() { + MinecraftClient client = MinecraftClient.getInstance(); + if (client.world != null && client.getNetworkHandler() != null) { + //FIXME not sure if we even need this - depends on how REI, EMI, and JEI adapt to the changes + SynchronizeRecipesS2CPacket packet = new SynchronizeRecipesS2CPacket(Map.of(), CuttingRecipeDisplay.Grouping.empty()); + + try { + client.getNetworkHandler().onSynchronizeRecipes(packet); + } catch (Exception e) { + LOGGER.info("[Skyblocker NEU Repo] recipe sync error", e); + } + } + } + + public static boolean isLoading() { + return REPO_LOADING != null && !REPO_LOADING.isDone(); + } + + private static CompletableFuture loadRepository() { + return CompletableFuture.supplyAsync(() -> { + boolean success = true; + try { + if (Files.isDirectory(NEURepoManager.LOCAL_REPO_DIR)) { + try (Git localRepo = Git.open(NEURepoManager.LOCAL_REPO_DIR.toFile())) { + localRepo.pull().setRebase(true).call(); + LOGGER.info("[Skyblocker NEU Repo] NEU Repository Updated"); + } + } else { + Git.cloneRepository().setURI(REMOTE_REPO_URL).setDirectory(NEURepoManager.LOCAL_REPO_DIR.toFile()).setBranchesToClone(List.of("refs/heads/master")).setBranch("refs/heads/master").call().close(); + LOGGER.info("[Skyblocker NEU Repo] NEU Repository Downloaded"); + } + } catch (TransportException e) { + LOGGER.error("[Skyblocker NEU Repo] Transport operation failed. Most likely unable to connect to the remote NEU repo on github", e); + success = false; + } catch (RepositoryNotFoundException e) { + LOGGER.warn("[Skyblocker NEU Repo] Local NEU Repository not found or corrupted, downloading new one", e); + Scheduler.INSTANCE.schedule(() -> deleteAndDownloadRepository(MinecraftClient.getInstance().player), 1); + success = false; + } catch (Exception e) { + LOGGER.error("[Skyblocker NEU Repo] Encountered unknown exception while downloading NEU Repository", e); + success = false; + } + + try { + NEU_REPO.reload(); + } catch (Exception e) { + LOGGER.error("[Skyblocker NEU Repo] Encountered unknown exception while loading NEU Repository", e); + success = false; + } + return success; + }).thenApplyAsync(success -> { + CompletableFuture.allOf(afterLoadTasks.stream().map(CompletableFuture::runAsync).toArray(CompletableFuture[]::new)).exceptionally(e -> { + LOGGER.error("[Skyblocker NEU Repo] Encountered unknown exception while running after load tasks", e); + return null; + }); + return success; + }); + } + + /** + * Caches NEUItems by display name using the NEU repository. + */ + private static void loadNameToNEUItemMap() { + nameToNEUItem = NEU_REPO.getItems() + .getItems() + .values() + .stream() + .collect(Multimaps.toMultimap(item -> Formatting.strip(item.getDisplayName()), Function.identity(), HashMultimap::create)); + } + + private static void deleteAndDownloadRepository(PlayerEntity player) { + if (isLoading()) { + sendMessage(player, Constants.PREFIX.get().append(Text.translatable("skyblocker.updateRepository.loading"))); + return; + } + sendMessage(player, Constants.PREFIX.get().append(Text.translatable("skyblocker.updateRepository.start"))); + + REPO_LOADING = CompletableFuture.runAsync(() -> { + try { + FileUtils.recursiveDelete(NEURepoManager.LOCAL_REPO_DIR); + sendMessage(player, Constants.PREFIX.get().append(Text.translatable("skyblocker.updateRepository.deleted"))); + sendMessage(player, Constants.PREFIX.get().append(Text.translatable(loadRepository().join() ? "skyblocker.updateRepository.success" : "skyblocker.updateRepository.failed"))); + } catch (Exception e) { + LOGGER.error("[Skyblocker NEU Repo] Encountered unknown exception while deleting the NEU repo", e); + sendMessage(player, Constants.PREFIX.get().append(Text.translatable("skyblocker.updateRepository.error"))); + } + }); + } + + private static void sendMessage(PlayerEntity player, Text text) { + if (player != null) { + player.sendMessage(text, false); + } + } + + /** + * Runs the given runnable after the NEU repo is initialized. + * + * @param runnable the runnable to run + * @return a completable future of the given runnable + */ + public static CompletableFuture runAsyncAfterLoad(Runnable runnable) { + return REPO_LOADING.thenRunAsync(runnable).exceptionally(e -> { + LOGGER.error("[Skyblocker NEU Repo] Encountered unknown exception while running after load task", e); + return null; + }).thenRun(() -> afterLoadTasks.add(runnable)); // Add to the list after so it doesn't get executed twice. + } + + public static void forEachItem(Consumer consumer) { + NEU_REPO.getItems().getItems().values().forEach(consumer); + } + + public static @Nullable NEUItem getItemByNeuId(String neuId) { + return NEU_REPO.getItems().getItemBySkyblockId(neuId); + } + + /** + * Gets the {@link NEUItem} by display name. + */ + public static Collection getItemByName(String displayName) { + return nameToNEUItem.get(displayName); + } + + public static NEUConstants getConstants() { + return NEU_REPO.getConstants(); + } + + public static @Nullable NEURepoFile file(@NotNull String path) { + return NEU_REPO.file(path); + } + + public static Map<@NEUId String, Set> getRecipes() { + return RECIPE_CACHE.getRecipes(); + } + + public static Map<@NEUId String, Set> getUsages() { + return RECIPE_CACHE.getUsages(); + } } -- cgit