From 84d5a94630af38d7f82bd02ebda297da9cc04cc1 Mon Sep 17 00:00:00 2001 From: Relentless Date: Tue, 18 Apr 2023 23:55:06 +0200 Subject: [PATCH] port 1.19.2 changes --- CHANGELOG.md | 25 +++ Common/build.gradle.kts | 4 +- .../almostreliable/unified/AlmostUnified.java | 28 +++- .../unified/AlmostUnifiedFallbackRuntime.java | 22 ++- .../unified/AlmostUnifiedLookupImpl.java | 2 +- .../unified/AlmostUnifiedRuntimeImpl.java | 53 ++++--- .../unified/api/StoneStrataHandler.java | 26 +++- .../unified/api/recipe/RecipeConstants.java | 2 + .../unified/compat/AlmostJEI.java | 1 + .../unified/compat/HideHelper.java | 98 +++++++++--- .../unified/compat/RecipeIndicator.java | 2 + .../unified/config/DebugConfig.java | 2 +- .../unified/config/ServerConfigs.java | 33 ++++ .../unified/config/UnifyConfig.java | 65 ++++++-- .../unified/mixin/JeiRecipeLayoutMixin.java | 31 ++++ .../unified/mixin/JeiRecipesGuiMixin.java | 84 ---------- .../unified/mixin/RecipeManagerMixin.java | 2 +- .../mixin/ReloadableServerResourcesMixin.java | 25 --- .../unified/mixin/TagLoaderMixin.java | 36 +++++ .../unified/mixin/TagManagerMixin.java | 23 +++ .../unified/recipe/RecipeContextImpl.java | 19 ++- .../unified/recipe/RecipeTransformer.java | 1 - .../unified/utils/ReplacementMap.java | 17 +-- .../almostreliable/unified/utils/TagMap.java | 102 +++++++++---- .../unified/utils/TagOwnerships.java | 144 ++++++++++++++++++ .../almostunified-common.mixins.json | 5 +- .../com/almostreliable/unified/TestUtils.java | 7 +- .../unified/utils/TagMapTests.java | 12 +- Fabric/build.gradle.kts | 2 +- Fabric/src/main/resources/fabric.mod.json | 4 + Forge/build.gradle.kts | 2 +- .../unified/compat/MekanismRecipeUnifier.java | 10 +- Forge/src/main/resources/META-INF/mods.toml | 21 +++ build.gradle.kts | 2 + gradle.properties | 4 +- 35 files changed, 666 insertions(+), 250 deletions(-) create mode 100644 Common/src/main/java/com/almostreliable/unified/config/ServerConfigs.java create mode 100644 Common/src/main/java/com/almostreliable/unified/mixin/JeiRecipeLayoutMixin.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/mixin/JeiRecipesGuiMixin.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/mixin/ReloadableServerResourcesMixin.java create mode 100644 Common/src/main/java/com/almostreliable/unified/mixin/TagLoaderMixin.java create mode 100644 Common/src/main/java/com/almostreliable/unified/mixin/TagManagerMixin.java create mode 100644 Common/src/main/java/com/almostreliable/unified/utils/TagOwnerships.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 69ecc9f..31b99df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog], and this project adheres to [Semantic Versioning]. +## [0.4.0] - 2023-04-18 + +### WARNING +This update features a new config option inside the `unify.json` called `tagOwnerships`.
+Since it's a top-level option, this won't reset your config and all other options should be preserved. However, +an automatic backup will be created in case something goes wrong. + +### Added +- tag ownerships + - a new milestone feature allowing tags to be converted to other tags + - this allows unifying inconsistent tags like `forge:coals` and `forge:gems/coal` + - you can read more about it in the [wiki][tag-ownerships] + +### Changed +- improved stone strata lookup speed + +### Fixed +- some Mekanism recipes not being unified +- log spam on multiple preferred tags +- JEI indicator not showing anymore with new JEI versions + + +[tag-ownerships]: https://github.com/AlmostReliable/almostunified/wiki/Unification-Config#tag-ownerships + ## [0.3.8] - 2023-04-06 ### Added @@ -22,5 +46,6 @@ Initial 1.19.4 release! [semantic versioning]: https://semver.org/spec/v2.0.0.html +[0.4.0]: https://github.com/AlmostReliable/almostunified/releases/tag/v1.19.4-0.4.0-beta [0.3.8]: https://github.com/AlmostReliable/almostunified/releases/tag/v1.19.4-0.3.8-beta [0.3.7]: https://github.com/AlmostReliable/almostunified/releases/tag/v1.19.4-0.3.7-beta diff --git a/Common/build.gradle.kts b/Common/build.gradle.kts index f6cf0b7..1461b5f 100644 --- a/Common/build.gradle.kts +++ b/Common/build.gradle.kts @@ -29,8 +29,8 @@ dependencies { modCompileOnly("me.shedaniel:RoughlyEnoughItems-api:$reiVersion") // required for common rei plugin compileOnly("me.shedaniel:REIPluginCompatibilities-forge-annotations:9.+") // required to disable rei compat layer on jei plugin testCompileOnly("me.shedaniel:REIPluginCompatibilities-forge-annotations:9.+") // don't question this, it's required for compiling - modCompileOnlyApi("mezz.jei:jei-$minecraftVersion-common:$jeiVersion") // required for common jei plugin - modCompileOnly("mezz.jei:jei-$minecraftVersion-gui:$jeiVersion") // required for jei mixin + modCompileOnly("mezz.jei:jei-$minecraftVersion-lib:$jeiVersion") // required for common jei plugin and mixin + modCompileOnly("mezz.jei:jei-$minecraftVersion-common-api:$jeiVersion") // required for common jei plugin and mixin // The Fabric loader is required here to use the @Environment annotations and to get the mixin dependencies. // Do NOT use other classes from the Fabric loader! diff --git a/Common/src/main/java/com/almostreliable/unified/AlmostUnified.java b/Common/src/main/java/com/almostreliable/unified/AlmostUnified.java index cab8431..bdbb3f3 100644 --- a/Common/src/main/java/com/almostreliable/unified/AlmostUnified.java +++ b/Common/src/main/java/com/almostreliable/unified/AlmostUnified.java @@ -1,7 +1,10 @@ package com.almostreliable.unified; import com.almostreliable.unified.config.Config; +import com.almostreliable.unified.config.ServerConfigs; import com.almostreliable.unified.config.StartupConfig; +import com.almostreliable.unified.utils.TagOwnerships; +import com.google.common.base.Preconditions; import net.minecraft.tags.TagManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -15,6 +18,8 @@ public final class AlmostUnified { @Nullable private static AlmostUnifiedRuntime RUNTIME; @Nullable private static TagManager TAG_MANGER; @Nullable private static StartupConfig STARTUP_CONFIG; + @Nullable private static ServerConfigs SERVER_CONFIGS; + @Nullable private static TagOwnerships TAG_OWNERSHIPS; public static StartupConfig getStartupConfig() { if (STARTUP_CONFIG == null) { @@ -34,15 +39,22 @@ public final class AlmostUnified { return RUNTIME; } - public static void reloadRuntime() { - if (TAG_MANGER == null) { - throw new IllegalStateException("Internal error. TagManager was not updated correctly"); - } - - RUNTIME = AlmostUnifiedRuntimeImpl.create(TAG_MANGER); + public static void onTagManagerReload(TagManager tagManager) { + TAG_MANGER = tagManager; + SERVER_CONFIGS = ServerConfigs.load(); + var uc = SERVER_CONFIGS.getUnifyConfig(); + TAG_OWNERSHIPS = new TagOwnerships(uc.bakeTags(), uc.getTagOwnerships()); } - public static void updateTagManager(TagManager tm) { - TAG_MANGER = tm; + public static void onReloadRecipeManager() { + Preconditions.checkNotNull(TAG_MANGER, "TagManager was not loaded correctly"); + Preconditions.checkNotNull(SERVER_CONFIGS, "ServerConfigs were not loaded correctly"); + + RUNTIME = AlmostUnifiedRuntimeImpl.create(TAG_MANGER, SERVER_CONFIGS); + } + + public static TagOwnerships getTagOwnerships() { + Preconditions.checkNotNull(TAG_OWNERSHIPS, "TagOwnerships were not loaded correctly"); + return TAG_OWNERSHIPS; } } diff --git a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedFallbackRuntime.java b/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedFallbackRuntime.java index 0e4e30e..6a05a5a 100644 --- a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedFallbackRuntime.java +++ b/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedFallbackRuntime.java @@ -5,21 +5,25 @@ import com.almostreliable.unified.config.Config; import com.almostreliable.unified.config.UnifyConfig; import com.almostreliable.unified.utils.ReplacementMap; import com.almostreliable.unified.utils.TagMap; +import com.almostreliable.unified.utils.TagOwnerships; import com.almostreliable.unified.utils.UnifyTag; import com.google.gson.JsonElement; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; import javax.annotation.Nullable; -import java.util.*; +import java.util.Map; +import java.util.Optional; +import java.util.Set; // TODO: Implement sync so it's not just a fallback public class AlmostUnifiedFallbackRuntime implements AlmostUnifiedRuntime { @Nullable private static AlmostUnifiedFallbackRuntime INSTANCE; @Nullable private UnifyConfig config; - @Nullable private ReplacementMap replacementMap; @Nullable private TagMap filteredTagMap; + @Nullable private ReplacementMap replacementMap; + @Nullable private TagOwnerships tagOwnerships; public static AlmostUnifiedFallbackRuntime getInstance() { if (INSTANCE == null) { @@ -32,8 +36,9 @@ public class AlmostUnifiedFallbackRuntime implements AlmostUnifiedRuntime { public void reload() { config = null; - replacementMap = null; filteredTagMap = null; + replacementMap = null; + tagOwnerships = null; build(); } @@ -46,11 +51,12 @@ public class AlmostUnifiedFallbackRuntime implements AlmostUnifiedRuntime { } public void build() { - var config = getConfig(); - List> unifyTags = config.bakeTags(); - filteredTagMap = TagMap.create(unifyTags).filtered($ -> true, config::includeItem); - StoneStrataHandler stoneStrataHandler = getStoneStrataHandler(config); - replacementMap = new ReplacementMap(Objects.requireNonNull(filteredTagMap), stoneStrataHandler, config); + var uc = getConfig(); + Set> unifyTags = uc.bakeTags(); + filteredTagMap = TagMap.create(unifyTags).filtered($ -> true, uc::includeItem); + StoneStrataHandler stoneStrataHandler = getStoneStrataHandler(uc); + replacementMap = new ReplacementMap(filteredTagMap, stoneStrataHandler, uc); + tagOwnerships = new TagOwnerships(unifyTags, uc.getTagOwnerships()); } private static StoneStrataHandler getStoneStrataHandler(UnifyConfig config) { diff --git a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedLookupImpl.java b/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedLookupImpl.java index ae427d8..67faa38 100644 --- a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedLookupImpl.java +++ b/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedLookupImpl.java @@ -65,7 +65,7 @@ public class AlmostUnifiedLookupImpl implements AlmostUnifiedLookup { .getRuntime() .getFilteredTagMap() .map(tagMap -> tagMap - .getItems(asUnifyTag) + .getItemsByTag(asUnifyTag) .stream() .flatMap(rl -> BuiltInRegistries.ITEM.getOptional(rl).stream()) .collect(Collectors.toSet())) diff --git a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedRuntimeImpl.java b/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedRuntimeImpl.java index 82d82a6..fa24e7e 100644 --- a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedRuntimeImpl.java +++ b/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedRuntimeImpl.java @@ -1,9 +1,9 @@ package com.almostreliable.unified; import com.almostreliable.unified.api.StoneStrataHandler; -import com.almostreliable.unified.config.Config; import com.almostreliable.unified.config.DebugConfig; import com.almostreliable.unified.config.DuplicationConfig; +import com.almostreliable.unified.config.ServerConfigs; import com.almostreliable.unified.config.UnifyConfig; import com.almostreliable.unified.recipe.RecipeDumper; import com.almostreliable.unified.recipe.RecipeTransformer; @@ -11,20 +11,17 @@ import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory; import com.almostreliable.unified.utils.FileUtils; import com.almostreliable.unified.utils.ReplacementMap; import com.almostreliable.unified.utils.TagMap; -import com.almostreliable.unified.utils.UnifyTag; import com.google.gson.JsonElement; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagManager; -import net.minecraft.world.item.Item; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; public final class AlmostUnifiedRuntimeImpl implements AlmostUnifiedRuntime { + private final RecipeHandlerFactory recipeHandlerFactory; private final TagMap filteredTagMap; private final DuplicationConfig dupConfig; @@ -32,32 +29,48 @@ public final class AlmostUnifiedRuntimeImpl implements AlmostUnifiedRuntime { private final DebugConfig debugConfig; private final ReplacementMap replacementMap; - private AlmostUnifiedRuntimeImpl(RecipeHandlerFactory recipeHandlerFactory, TagMap tagMap, DuplicationConfig dupConfig, UnifyConfig unifyConfig, DebugConfig debugConfig) { + private AlmostUnifiedRuntimeImpl( + RecipeHandlerFactory recipeHandlerFactory, + TagMap filteredTagMap, + ReplacementMap replacementMap, + DuplicationConfig dupConfig, + UnifyConfig unifyConfig, + DebugConfig debugConfig + ) { this.recipeHandlerFactory = recipeHandlerFactory; + this.filteredTagMap = filteredTagMap; + this.replacementMap = replacementMap; this.dupConfig = dupConfig; this.unifyConfig = unifyConfig; this.debugConfig = debugConfig; - List> allowedTags = unifyConfig.bakeTags(); - this.filteredTagMap = tagMap.filtered(allowedTags::contains, unifyConfig::includeItem); - StoneStrataHandler stoneStrataHandler = StoneStrataHandler.create(unifyConfig.getStoneStrata(), - AlmostUnifiedPlatform.INSTANCE.getStoneStrataTags(unifyConfig.getStoneStrata()), - tagMap); - this.replacementMap = new ReplacementMap(filteredTagMap, stoneStrataHandler, unifyConfig); } - public static AlmostUnifiedRuntimeImpl create(TagManager tagManager) { - Objects.requireNonNull(tagManager); - + public static AlmostUnifiedRuntimeImpl create(TagManager tagManager, ServerConfigs serverConfigs) { createGitIgnoreIfNotExists(); - DuplicationConfig dupConfig = Config.load(DuplicationConfig.NAME, new DuplicationConfig.Serializer()); - UnifyConfig unifyConfig = Config.load(UnifyConfig.NAME, new UnifyConfig.Serializer()); - DebugConfig debugConfig = Config.load(DebugConfig.NAME, new DebugConfig.Serializer()); + DuplicationConfig dupConfig = serverConfigs.getDupConfig(); + UnifyConfig unifyConfig = serverConfigs.getUnifyConfig(); + DebugConfig debugConfig = serverConfigs.getDebugConfig(); RecipeHandlerFactory factory = new RecipeHandlerFactory(); AlmostUnifiedPlatform.INSTANCE.bindRecipeHandlers(factory); - TagMap tagMap = TagMap.create(tagManager); - return new AlmostUnifiedRuntimeImpl(factory, tagMap, dupConfig, unifyConfig, debugConfig); + var allowedTags = unifyConfig.bakeTags(); + TagMap globalTagMap = TagMap.create(tagManager); + TagMap filteredTagMap = globalTagMap.filtered(allowedTags::contains, unifyConfig::includeItem); + + StoneStrataHandler stoneStrataHandler = StoneStrataHandler.create(unifyConfig.getStoneStrata(), + AlmostUnifiedPlatform.INSTANCE.getStoneStrataTags(unifyConfig.getStoneStrata()), globalTagMap); + + var replacementMap = new ReplacementMap(filteredTagMap, stoneStrataHandler, unifyConfig); + + return new AlmostUnifiedRuntimeImpl( + factory, + filteredTagMap, + replacementMap, + dupConfig, + unifyConfig, + debugConfig + ); } private static void createGitIgnoreIfNotExists() { diff --git a/Common/src/main/java/com/almostreliable/unified/api/StoneStrataHandler.java b/Common/src/main/java/com/almostreliable/unified/api/StoneStrataHandler.java index 37de67a..9e7ebdc 100644 --- a/Common/src/main/java/com/almostreliable/unified/api/StoneStrataHandler.java +++ b/Common/src/main/java/com/almostreliable/unified/api/StoneStrataHandler.java @@ -14,13 +14,18 @@ public class StoneStrataHandler { private final List stoneStrata; private final Pattern tagMatcher; private final TagMap stoneStrataTagMap; + + // don't clear the caches, so they are available for the runtime and KubeJS binding + // the runtime holding this handler is automatically yeeted on reload private final Map, Boolean> stoneStrataTagCache; + private final Map stoneStrataCache; private StoneStrataHandler(List stoneStrata, Pattern tagMatcher, TagMap stoneStrataTagMap) { this.stoneStrata = createSortedStoneStrata(stoneStrata); this.tagMatcher = tagMatcher; this.stoneStrataTagMap = stoneStrataTagMap; this.stoneStrataTagCache = new HashMap<>(); + this.stoneStrataCache = new HashMap<>(); } /** @@ -46,16 +51,27 @@ public class StoneStrataHandler { } /** - * Returns the stone strata from given item. Method works on the requirement that it's an item which has a stone strata. - * Use {@link #isStoneStrataTag(UnifyTag)} to fill this requirement. + * Returns the stone strata from the given item. Assumes that the item has a stone strata tag. + * Use {@link #isStoneStrataTag(UnifyTag)} to ensure this requirement. * * @param item The item to get the stone strata from. * @return The stone strata of the item. Clean stone strata returns an empty string for later sorting as a * fallback variant. */ public String getStoneStrata(ResourceLocation item) { + return stoneStrataCache.computeIfAbsent(item, this::computeStoneStrata); + } + + /** + * Implementation logic for {@link #getStoneStrata(ResourceLocation)}. + * + * @param item The item to get the stone strata from. + * @return The stone strata of the item. Clean stone strata returns an empty string for later sorting as a + * fallback variant. + */ + private String computeStoneStrata(ResourceLocation item) { String strata = stoneStrataTagMap - .getTags(item) + .getTagsByItem(item) .stream() .findFirst() .map(UnifyTag::location) @@ -88,8 +104,4 @@ public class StoneStrataHandler { public boolean isStoneStrataTag(UnifyTag tag) { return stoneStrataTagCache.computeIfAbsent(tag, t -> tagMatcher.matcher(t.location().toString()).matches()); } - - public void clearCache() { - stoneStrataTagCache.clear(); - } } diff --git a/Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeConstants.java b/Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeConstants.java index 8986407..66d2701 100644 --- a/Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeConstants.java +++ b/Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeConstants.java @@ -29,6 +29,8 @@ public final class RecipeConstants { // mekanism public static final String MAIN_INPUT = "mainInput"; public static final String MAIN_OUTPUT = "mainOutput"; + public static final String ITEM_INPUT = "itemInput"; + public static final String ITEM_OUTPUT = "itemOutput"; public static final String SECONDARY_OUTPUT = "secondaryOutput"; // modern industrialization diff --git a/Common/src/main/java/com/almostreliable/unified/compat/AlmostJEI.java b/Common/src/main/java/com/almostreliable/unified/compat/AlmostJEI.java index f241ed1..f58d5c4 100644 --- a/Common/src/main/java/com/almostreliable/unified/compat/AlmostJEI.java +++ b/Common/src/main/java/com/almostreliable/unified/compat/AlmostJEI.java @@ -48,6 +48,7 @@ public class AlmostJEI implements IModPlugin { var recipeId = recipeCategory.getRegistryName(recipe); if (recipeId == null) return; + var link = CRTLookup.getLink(recipeId); if (link == null) return; diff --git a/Common/src/main/java/com/almostreliable/unified/compat/HideHelper.java b/Common/src/main/java/com/almostreliable/unified/compat/HideHelper.java index 1e85479..a1129a0 100644 --- a/Common/src/main/java/com/almostreliable/unified/compat/HideHelper.java +++ b/Common/src/main/java/com/almostreliable/unified/compat/HideHelper.java @@ -5,12 +5,15 @@ import com.almostreliable.unified.AlmostUnifiedRuntime; import com.almostreliable.unified.utils.ReplacementMap; import com.almostreliable.unified.utils.TagMap; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import java.util.ArrayList; import java.util.Collection; -import java.util.List; +import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; @@ -18,33 +21,51 @@ public class HideHelper { public static Collection createHidingList(AlmostUnifiedRuntime runtime) { ReplacementMap repMap = runtime.getReplacementMap().orElse(null); - TagMap filteredTagMap = runtime.getFilteredTagMap().orElse(null); + TagMap tagMap = runtime.getFilteredTagMap().orElse(null); - if (repMap == null || filteredTagMap == null) return new ArrayList<>(); + if (repMap == null || tagMap == null) return new ArrayList<>(); - return filteredTagMap.getTags().stream().map(unifyTag -> { - Collection itemsByTag = filteredTagMap.getItems(unifyTag); + Set hidingList = new HashSet<>(); + + for (var unifyTag : tagMap.getTags()) { + var itemsByTag = tagMap.getItemsByTag(unifyTag); // avoid hiding single entries and tags that only contain the same namespace for all items long namespaces = itemsByTag.stream().map(ResourceLocation::getNamespace).distinct().count(); - if (namespaces <= 1) return new ArrayList(); + if (namespaces <= 1) continue; - Set replacements = itemsByTag - .stream() - .map(item -> getReplacementForItem(repMap, item)) - .collect(Collectors.toSet()); - List toHide = itemsByTag.stream().filter(rl -> !replacements.contains(rl)).toList(); - - if (!toHide.isEmpty()) { - AlmostUnified.LOG.info("Hiding {}/{} items for tag {} -> {}", - toHide.size(), - itemsByTag.size(), - unifyTag.location(), - toHide); + Set replacements = new HashSet<>(); + for (ResourceLocation item : itemsByTag) { + replacements.add(getReplacementForItem(repMap, item)); } - return toHide.stream().flatMap(rl -> BuiltInRegistries.ITEM.getOptional(rl).stream()).map(ItemStack::new).toList(); - }).flatMap(Collection::stream).toList(); + Set toHide = new HashSet<>(); + for (ResourceLocation item : itemsByTag) { + if (!replacements.contains(item)) { + toHide.add(item); + } + } + + if (toHide.isEmpty()) continue; + + AlmostUnified.LOG.info( + "[AutoHiding] Hiding {}/{} items for tag '#{}' -> {}", + toHide.size(), + itemsByTag.size(), + unifyTag.location(), + toHide + ); + + hidingList.addAll(toHide); + } + + hidingList.addAll(getRefItems(repMap)); + + return hidingList + .stream() + .flatMap(rl -> BuiltInRegistries.ITEM.getOptional(rl).stream()) + .map(ItemStack::new) + .collect(Collectors.toList()); } /** @@ -61,4 +82,41 @@ public class HideHelper { if (replacement == null) return item; return replacement; } + + /** + * Returns a set of all items that are contained in the reference tags. + * + * @return A set of all items that are contained in the reference tags. + */ + private static Set getRefItems(ReplacementMap repMap) { + Set hidingList = new HashSet<>(); + + for (var ref : AlmostUnified.getTagOwnerships().getRefs()) { + var owner = AlmostUnified.getTagOwnerships().getOwnerByTag(ref); + assert owner != null; + + var dominantItem = repMap.getPreferredItemForTag(owner, $ -> true); + + TagKey asTagKey = TagKey.create(Registries.ITEM, ref.location()); + Set refItems = new HashSet<>(); + BuiltInRegistries.ITEM.getTagOrEmpty(asTagKey).forEach(holder -> { + ResourceLocation item = BuiltInRegistries.ITEM.getKey(holder.value()); + if (item.equals(dominantItem)) return; // don't hide if the item is a dominant one + refItems.add(item); + }); + + if (refItems.isEmpty()) continue; + + AlmostUnified.LOG.info( + "[AutoHiding] Hiding reference tag '#{}' of owner tag '#{}' -> {}", + ref.location(), + owner.location(), + refItems + ); + + hidingList.addAll(refItems); + } + + return hidingList; + } } diff --git a/Common/src/main/java/com/almostreliable/unified/compat/RecipeIndicator.java b/Common/src/main/java/com/almostreliable/unified/compat/RecipeIndicator.java index e66c19c..a072d40 100644 --- a/Common/src/main/java/com/almostreliable/unified/compat/RecipeIndicator.java +++ b/Common/src/main/java/com/almostreliable/unified/compat/RecipeIndicator.java @@ -52,4 +52,6 @@ public final class RecipeIndicator { GuiComponent.blit(poseStack, 0, 0, 0, 0, TEXTURE_SIZE, TEXTURE_SIZE, TEXTURE_SIZE, TEXTURE_SIZE); poseStack.popPose(); } + + public record RenderEntry(int pX, int pY) {} } diff --git a/Common/src/main/java/com/almostreliable/unified/config/DebugConfig.java b/Common/src/main/java/com/almostreliable/unified/config/DebugConfig.java index 915e5ca..56e50fb 100644 --- a/Common/src/main/java/com/almostreliable/unified/config/DebugConfig.java +++ b/Common/src/main/java/com/almostreliable/unified/config/DebugConfig.java @@ -40,7 +40,7 @@ public class DebugConfig extends Config { .stream() .sorted(Comparator.comparing(t -> t.location().toString())) .map(t -> StringUtils.rightPad(t.location().toString(), 40) + " => " + tagMap - .getItems(t) + .getItemsByTag(t) .stream() .map(ResourceLocation::toString) .sorted() diff --git a/Common/src/main/java/com/almostreliable/unified/config/ServerConfigs.java b/Common/src/main/java/com/almostreliable/unified/config/ServerConfigs.java new file mode 100644 index 0000000..7ba43a8 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/config/ServerConfigs.java @@ -0,0 +1,33 @@ +package com.almostreliable.unified.config; + +public class ServerConfigs { + + private final UnifyConfig unifyConfig; + private final DuplicationConfig dupConfig; + private final DebugConfig debugConfig; + + public static ServerConfigs load() { + UnifyConfig unifyConfig = Config.load(UnifyConfig.NAME, new UnifyConfig.Serializer()); + DuplicationConfig dupConfig = Config.load(DuplicationConfig.NAME, new DuplicationConfig.Serializer()); + DebugConfig debugConfig = Config.load(DebugConfig.NAME, new DebugConfig.Serializer()); + return new ServerConfigs(unifyConfig, dupConfig, debugConfig); + } + + private ServerConfigs(UnifyConfig unifyConfig, DuplicationConfig dupConfig, DebugConfig debugConfig) { + this.unifyConfig = unifyConfig; + this.dupConfig = dupConfig; + this.debugConfig = debugConfig; + } + + public UnifyConfig getUnifyConfig() { + return unifyConfig; + } + + public DuplicationConfig getDupConfig() { + return dupConfig; + } + + public DebugConfig getDebugConfig() { + return debugConfig; + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/config/UnifyConfig.java b/Common/src/main/java/com/almostreliable/unified/config/UnifyConfig.java index e1d1e19..d1f6095 100644 --- a/Common/src/main/java/com/almostreliable/unified/config/UnifyConfig.java +++ b/Common/src/main/java/com/almostreliable/unified/config/UnifyConfig.java @@ -9,6 +9,7 @@ import com.google.gson.JsonPrimitive; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; +import javax.annotation.Nullable; import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -21,19 +22,35 @@ public class UnifyConfig extends Config { private final List unbakedTags; private final List materials; private final Map priorityOverrides; + private final Map> tagOwnerships; private final Set> ignoredTags; private final Set ignoredItems; private final Set ignoredRecipeTypes; private final Set ignoredRecipes; private final boolean hideJeiRei; - private final Map ignoredRecipeTypesCache; - public UnifyConfig(List modPriorities, List stoneStrata, List unbakedTags, List materials, Map priorityOverrides, Set> ignoredTags, Set ignoredItems, Set ignoredRecipeTypes, Set ignoredRecipes, boolean hideJeiRei) { + private final Map ignoredRecipeTypesCache; + @Nullable private Set> bakedTagsCache; + + public UnifyConfig( + List modPriorities, + List stoneStrata, + List unbakedTags, + List materials, + Map priorityOverrides, + Map> tagOwnerships, + Set> ignoredTags, + Set ignoredItems, + Set ignoredRecipeTypes, + Set ignoredRecipes, + boolean hideJeiRei + ) { this.modPriorities = modPriorities; this.stoneStrata = stoneStrata; this.unbakedTags = unbakedTags; this.materials = materials; this.priorityOverrides = priorityOverrides; + this.tagOwnerships = tagOwnerships; this.ignoredTags = ignoredTags; this.ignoredItems = ignoredItems; this.ignoredRecipeTypes = ignoredRecipeTypes; @@ -50,8 +67,12 @@ public class UnifyConfig extends Config { return Collections.unmodifiableList(stoneStrata); } - public List> bakeTags() { - List> result = new ArrayList<>(); + public Set> bakeTags() { + if (bakedTagsCache != null) { + return bakedTagsCache; + } + + Set> result = new HashSet<>(); for (String tag : unbakedTags) { for (String material : materials) { @@ -59,15 +80,17 @@ public class UnifyConfig extends Config { ResourceLocation asRL = ResourceLocation.tryParse(replace); if (asRL == null) { AlmostUnified.LOG.warn("Could not bake tag <{}> with material <{}>", tag, material); - } else { - UnifyTag t = UnifyTag.item(asRL); - if (!ignoredTags.contains(t)) { - result.add(t); - } + continue; + } + + UnifyTag t = UnifyTag.item(asRL); + if (!ignoredTags.contains(t)) { + result.add(t); } } } + bakedTagsCache = result; return result; } @@ -81,6 +104,10 @@ public class UnifyConfig extends Config { return Collections.unmodifiableMap(priorityOverrides); } + public Map> getTagOwnerships() { + return Collections.unmodifiableMap(tagOwnerships); + } + public boolean includeItem(ResourceLocation item) { for (Pattern pattern : ignoredItems) { if (pattern.matcher(item.toString()).matches()) { @@ -125,6 +152,7 @@ public class UnifyConfig extends Config { public static final String TAGS = "tags"; public static final String MATERIALS = "materials"; public static final String PRIORITY_OVERRIDES = "priorityOverrides"; + public static final String TAG_OWNERSHIPS = "tagOwnerships"; public static final String IGNORED_TAGS = "ignoredTags"; public static final String IGNORED_ITEMS = "ignoredItems"; public static final String IGNORED_RECIPE_TYPES = "ignoredRecipeTypes"; @@ -149,6 +177,18 @@ public class UnifyConfig extends Config { entry -> entry.getValue().getAsString(), (a, b) -> b, HashMap::new)), new HashMap<>()); + Map> tagOwnerships = safeGet(() -> json + .getAsJsonObject(TAG_OWNERSHIPS) + .entrySet() + .stream() + .collect(Collectors.toMap( + entry -> new ResourceLocation(entry.getKey()), + entry -> JsonUtils.toList(entry.getValue().getAsJsonArray()) + .stream() + .map(ResourceLocation::new) + .collect(Collectors.toSet()), + (a, b) -> b, + HashMap::new)), new HashMap<>()); Set> ignoredTags = safeGet(() -> JsonUtils .toList(json.getAsJsonArray(IGNORED_TAGS)) .stream() @@ -166,6 +206,7 @@ public class UnifyConfig extends Config { tags, materials, priorityOverrides, + tagOwnerships, ignoredTags, ignoredItems, ignoredRecipeTypes, @@ -186,6 +227,12 @@ public class UnifyConfig extends Config { priorityOverrides.add(tag.toString(), new JsonPrimitive(mod)); }); json.add(PRIORITY_OVERRIDES, priorityOverrides); + JsonObject tagOwnerships = new JsonObject(); + config.tagOwnerships.forEach((parent, child) -> { + tagOwnerships.add(parent.toString(), + JsonUtils.toArray(child.stream().map(ResourceLocation::toString).toList())); + }); + json.add(TAG_OWNERSHIPS, tagOwnerships); json.add(IGNORED_TAGS, JsonUtils.toArray(config.ignoredTags .stream() diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/JeiRecipeLayoutMixin.java b/Common/src/main/java/com/almostreliable/unified/mixin/JeiRecipeLayoutMixin.java new file mode 100644 index 0000000..565e785 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/mixin/JeiRecipeLayoutMixin.java @@ -0,0 +1,31 @@ +package com.almostreliable.unified.mixin; + +import com.almostreliable.unified.compat.AlmostJEI; +import com.almostreliable.unified.compat.RecipeIndicator; +import com.mojang.blaze3d.vertex.PoseStack; +import mezz.jei.api.gui.drawable.IDrawable; +import mezz.jei.api.recipe.category.IRecipeCategory; +import mezz.jei.library.gui.recipes.RecipeLayout; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(RecipeLayout.class) +public abstract class JeiRecipeLayoutMixin { + + @Shadow(remap = false) @Final + private IRecipeCategory recipeCategory; + @Shadow(remap = false) @Final + private R recipe; + + @Inject(method = "drawRecipe", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;pushPose()V", ordinal = 1), locals = LocalCapture.CAPTURE_FAILHARD) + private void unified$catchLayoutInfo(PoseStack stack, int mouseX, int mouseY, CallbackInfo ci, IDrawable background, int mX, int mY, int x, int y) { + var posX = x - RecipeIndicator.RENDER_SIZE; + var posY = y - RecipeIndicator.RENDER_SIZE; + AlmostJEI.handleIndicator(stack, mX, mY, posX, posY, recipeCategory, recipe); + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/JeiRecipesGuiMixin.java b/Common/src/main/java/com/almostreliable/unified/mixin/JeiRecipesGuiMixin.java deleted file mode 100644 index 502134b..0000000 --- a/Common/src/main/java/com/almostreliable/unified/mixin/JeiRecipesGuiMixin.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.almostreliable.unified.mixin; - -import com.almostreliable.unified.compat.AlmostJEI; -import com.almostreliable.unified.compat.RecipeIndicator; -import com.almostreliable.unified.utils.Utils; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.PoseStack; -import mezz.jei.api.gui.IRecipeLayoutDrawable; -import mezz.jei.common.util.ImmutableRect2i; -import mezz.jei.gui.recipes.RecipeTransferButton; -import mezz.jei.gui.recipes.RecipesGui; -import net.minecraft.client.renderer.Rect2i; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import org.spongepowered.asm.mixin.injection.callback.LocalCapture; - -import java.util.Iterator; -import java.util.List; -import java.util.Optional; - -@Mixin(RecipesGui.class) -public abstract class JeiRecipesGuiMixin { - - private static final int BORDER_PADDING = 2; - - @Shadow(remap = false) @Final - private List recipeTransferButtons; - - @Inject( - method = "drawLayouts", - at = @At(value = "INVOKE", target = "Lmezz/jei/api/gui/IRecipeLayoutDrawable;drawRecipe(Lcom/mojang/blaze3d/vertex/PoseStack;II)V", shift = At.Shift.AFTER), - locals = LocalCapture.CAPTURE_FAILHARD, - remap = false - ) - private void unified$drawIndicator( - PoseStack poseStack, int mX, int mY, CallbackInfoReturnable>> cir, - IRecipeLayoutDrawable hoveredLayout, Iterator i, IRecipeLayoutDrawable recipeLayout - ) { - RenderSystem.disableBlend(); - RenderSystem.disableDepthTest(); - - var buttonArea = recipeLayout.getRecipeTransferButtonArea(); - - RecipeTransferButton transferButton = null; - for (var button : recipeTransferButtons) { - if (!button.visible || !buttonsMatch(buttonArea, button.getArea())) continue; - transferButton = button; - } - - int posX; - int posY; - if (transferButton == null) { - var layoutArea = recipeLayout.getRect(); - posX = layoutArea.getX() + layoutArea.getWidth() - RecipeIndicator.RENDER_SIZE / 2; - posY = layoutArea.getY() + layoutArea.getHeight() - RecipeIndicator.RENDER_SIZE / 2 + BORDER_PADDING; - } else { - posX = buttonArea.getX() + BORDER_PADDING; - posY = buttonArea.getY() - RecipeIndicator.RENDER_SIZE - 2 + BORDER_PADDING; - } - - AlmostJEI.handleIndicator( - poseStack, - mX, - mY, - posX, - posY, - Utils.cast(recipeLayout.getRecipeCategory()), - recipeLayout.getRecipe() - ); - RenderSystem.enableBlend(); - RenderSystem.enableDepthTest(); - } - - private static boolean buttonsMatch(Rect2i a, ImmutableRect2i b) { - return a.getX() == b.getX() && - a.getY() == b.getY() && - a.getWidth() == b.getWidth() && - a.getHeight() == b.getHeight(); - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/RecipeManagerMixin.java b/Common/src/main/java/com/almostreliable/unified/mixin/RecipeManagerMixin.java index fee13b8..0d22e7d 100644 --- a/Common/src/main/java/com/almostreliable/unified/mixin/RecipeManagerMixin.java +++ b/Common/src/main/java/com/almostreliable/unified/mixin/RecipeManagerMixin.java @@ -20,7 +20,7 @@ public class RecipeManagerMixin { @Inject(method = "apply(Ljava/util/Map;Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/util/profiling/ProfilerFiller;)V", at = @At("HEAD")) private void runTransformation(Map recipes, ResourceManager resourceManager, ProfilerFiller profiler, CallbackInfo ci) { try { - AlmostUnified.reloadRuntime(); + AlmostUnified.onReloadRecipeManager(); AlmostUnified.getRuntime().run(recipes, AlmostUnified.getStartupConfig().isServerOnly()); } catch (Exception e) { AlmostUnified.LOG.error(e.getMessage(), e); diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/ReloadableServerResourcesMixin.java b/Common/src/main/java/com/almostreliable/unified/mixin/ReloadableServerResourcesMixin.java deleted file mode 100644 index 66d57e7..0000000 --- a/Common/src/main/java/com/almostreliable/unified/mixin/ReloadableServerResourcesMixin.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.almostreliable.unified.mixin; - -import com.almostreliable.unified.AlmostUnified; -import net.minecraft.commands.Commands; -import net.minecraft.core.RegistryAccess; -import net.minecraft.server.ReloadableServerResources; -import net.minecraft.tags.TagManager; -import net.minecraft.world.flag.FeatureFlagSet; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(ReloadableServerResources.class) -public class ReloadableServerResourcesMixin { - - @Shadow @Final private TagManager tagManager; - - @Inject(method = "", at = @At("RETURN")) - private void yoinkServerResources(RegistryAccess.Frozen frozen, FeatureFlagSet featureFlagSet, Commands.CommandSelection commandSelection, int i, CallbackInfo ci) { - AlmostUnified.updateTagManager(tagManager); - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/TagLoaderMixin.java b/Common/src/main/java/com/almostreliable/unified/mixin/TagLoaderMixin.java new file mode 100644 index 0000000..8ce6711 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/mixin/TagLoaderMixin.java @@ -0,0 +1,36 @@ +package com.almostreliable.unified.mixin; + +import com.almostreliable.unified.AlmostUnified; +import com.almostreliable.unified.utils.Utils; +import net.minecraft.core.Holder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagLoader; +import net.minecraft.world.item.Item; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +@Mixin(TagLoader.class) +public class TagLoaderMixin { + + @Shadow @Final private String directory; + + @Inject(method = "build(Ljava/util/Map;)Ljava/util/Map;", at = @At("RETURN")) + private void onCreateLoadResult(Map> map, CallbackInfoReturnable>> cir) { + if (directory.equals("tags/items")) { + try { + Map>> rawTags = Utils.cast(cir.getReturnValue()); + AlmostUnified.getTagOwnerships().applyOwnershipToRawTags(rawTags); + } catch (Exception e) { + AlmostUnified.LOG.error("Error applying tag ownerships to raw tags", e); + } + } + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/TagManagerMixin.java b/Common/src/main/java/com/almostreliable/unified/mixin/TagManagerMixin.java new file mode 100644 index 0000000..87748d0 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/mixin/TagManagerMixin.java @@ -0,0 +1,23 @@ +package com.almostreliable.unified.mixin; + +import com.almostreliable.unified.AlmostUnified; +import net.minecraft.server.packs.resources.PreparableReloadListener; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.tags.TagManager; +import net.minecraft.util.profiling.ProfilerFiller; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +@Mixin(TagManager.class) +public class TagManagerMixin { + + @Inject(method = "reload", at = @At("HEAD")) + private void onReload(PreparableReloadListener.PreparationBarrier preparationBarrier, ResourceManager resourceManager, ProfilerFiller profilerFiller, ProfilerFiller profilerFiller2, Executor executor, Executor executor2, CallbackInfoReturnable> cir) { + AlmostUnified.onTagManagerReload((TagManager) (Object) this); + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/recipe/RecipeContextImpl.java b/Common/src/main/java/com/almostreliable/unified/recipe/RecipeContextImpl.java index 66f6d1e..f410559 100644 --- a/Common/src/main/java/com/almostreliable/unified/recipe/RecipeContextImpl.java +++ b/Common/src/main/java/com/almostreliable/unified/recipe/RecipeContextImpl.java @@ -1,5 +1,6 @@ package com.almostreliable.unified.recipe; +import com.almostreliable.unified.AlmostUnified; import com.almostreliable.unified.api.recipe.RecipeConstants; import com.almostreliable.unified.api.recipe.RecipeContext; import com.almostreliable.unified.utils.JsonUtils; @@ -64,20 +65,28 @@ public class RecipeContextImpl implements RecipeContext { return null; } JsonElement copy = element.deepCopy(); - tryReplacingItemInIngredient(copy); + tryCreateIngredientReplacement(copy); return element.equals(copy) ? null : copy; } - private void tryReplacingItemInIngredient(@Nullable JsonElement element) { + private void tryCreateIngredientReplacement(@Nullable JsonElement element) { if (element instanceof JsonArray array) { for (JsonElement e : array) { - tryReplacingItemInIngredient(e); + tryCreateIngredientReplacement(e); } } if (element instanceof JsonObject object) { - tryReplacingItemInIngredient(object.get(RecipeConstants.VALUE)); - tryReplacingItemInIngredient(object.get(RecipeConstants.INGREDIENT)); + tryCreateIngredientReplacement(object.get(RecipeConstants.VALUE)); + tryCreateIngredientReplacement(object.get(RecipeConstants.INGREDIENT)); + + if (object.get(RecipeConstants.TAG) instanceof JsonPrimitive primitive) { + UnifyTag tag = Utils.toItemTag(primitive.getAsString()); + UnifyTag ownershipTag = AlmostUnified.getTagOwnerships().getOwnerByTag(tag); + if (ownershipTag != null) { + object.add(RecipeConstants.TAG, new JsonPrimitive(ownershipTag.location().toString())); + } + } if (object.get(RecipeConstants.ITEM) instanceof JsonPrimitive primitive) { ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString()); diff --git a/Common/src/main/java/com/almostreliable/unified/recipe/RecipeTransformer.java b/Common/src/main/java/com/almostreliable/unified/recipe/RecipeTransformer.java index 1819147..ebdf5be 100644 --- a/Common/src/main/java/com/almostreliable/unified/recipe/RecipeTransformer.java +++ b/Common/src/main/java/com/almostreliable/unified/recipe/RecipeTransformer.java @@ -78,7 +78,6 @@ public class RecipeTransformer { unifyConfig.clearCache(); duplicationConfig.clearCache(); - replacementMap.getStoneStrataHandler().clearCache(); if (tracker != null) recipes.putAll(tracker.compute()); return result; diff --git a/Common/src/main/java/com/almostreliable/unified/utils/ReplacementMap.java b/Common/src/main/java/com/almostreliable/unified/utils/ReplacementMap.java index b0f2b40..7c25cca 100644 --- a/Common/src/main/java/com/almostreliable/unified/utils/ReplacementMap.java +++ b/Common/src/main/java/com/almostreliable/unified/utils/ReplacementMap.java @@ -7,9 +7,7 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; import javax.annotation.Nullable; -import java.util.Collection; -import java.util.Comparator; -import java.util.List; +import java.util.*; import java.util.function.Predicate; public class ReplacementMap { @@ -17,26 +15,29 @@ public class ReplacementMap { private final TagMap tagMap; private final UnifyConfig unifyConfig; private final StoneStrataHandler stoneStrataHandler; + private final Set warnings; public ReplacementMap(TagMap tagMap, StoneStrataHandler stoneStrataHandler, UnifyConfig unifyConfig) { this.tagMap = tagMap; this.unifyConfig = unifyConfig; this.stoneStrataHandler = stoneStrataHandler; + this.warnings = new HashSet<>(); } @Nullable public UnifyTag getPreferredTagForItem(ResourceLocation item) { - Collection> tags = tagMap.getTags(item); + Collection> tags = tagMap.getTagsByItem(item); if (tags.isEmpty()) { return null; } - if (tags.size() > 1) { + if (tags.size() > 1 && !warnings.contains(item)) { AlmostUnified.LOG.warn( "Item '{}' has multiple preferred tags '{}' for recipe replacement. This needs to be manually fixed by the user.", item, tags.stream().map(UnifyTag::location).toList()); + warnings.add(item); } return tags.iterator().next(); @@ -60,7 +61,7 @@ public class ReplacementMap { @Nullable public ResourceLocation getPreferredItemForTag(UnifyTag tag, Predicate itemFilter) { List items = tagMap - .getItems(tag) + .getItemsByTag(tag) .stream() .filter(itemFilter) // Helps us to get the clean stone variant first in case of a stone strata tag @@ -103,8 +104,4 @@ public class ReplacementMap { } return null; } - - public StoneStrataHandler getStoneStrataHandler() { - return stoneStrataHandler; - } } diff --git a/Common/src/main/java/com/almostreliable/unified/utils/TagMap.java b/Common/src/main/java/com/almostreliable/unified/utils/TagMap.java index ea0791f..a9da403 100644 --- a/Common/src/main/java/com/almostreliable/unified/utils/TagMap.java +++ b/Common/src/main/java/com/almostreliable/unified/utils/TagMap.java @@ -13,12 +13,25 @@ import java.util.*; import java.util.function.Predicate; public class TagMap { + private final Map, Set> tagsToItems = new HashMap<>(); private final Map>> itemsToTags = new HashMap<>(); protected TagMap() {} - public static TagMap create(Collection> unifyTags) { + /** + * Creates a tag map from a set of unify tags. + *

+ * This should only be used for client-side tag maps or for tests.
+ * It requires the registry to be loaded in order to validate the tags + * and fetch the holder from it. + *

+ * For the server, use {@link #create(TagManager)} instead. + * + * @param unifyTags The unify tags. + * @return A new tag map. + */ + public static TagMap create(Set> unifyTags) { TagMap tagMap = new TagMap(); unifyTags.forEach(ut -> { @@ -33,43 +46,41 @@ public class TagMap { } /** - * Creates a {@link TagMap} from a vanilla {@link TagManager}. + * Creates a tag map from the vanilla {@link TagManager}. + *

+ * This should only be used on the server.
+ * It will fetch all tags and items from the manager and store them. This tag map should later + * be filtered by using {@link #filtered(Predicate, Predicate)}. + *

+ * For the client, use {@link #create(Set)} instead. * * @param tagManager The vanilla tag manager. - * @return A new {@link TagMap}. + * @return A new tag map. */ public static TagMap create(TagManager tagManager) { - Objects.requireNonNull(tagManager, "Requires a non-null tag manager"); - var tags = tagManager - .getResult() - .stream() - .filter(result -> result.key() == Registries.ITEM) - .findFirst() - .map(TagManager.LoadResult::tags) - .orElseThrow(() -> new IllegalStateException("No item tag result found")); - + var tags = unpackTagManager(tagManager); TagMap tagMap = new TagMap(); for (var entry : tags.entrySet()) { - UnifyTag tag = UnifyTag.item(entry.getKey()); - var holderTag = entry.getValue(); - for (Holder holder : holderTag) { + UnifyTag unifyTag = UnifyTag.item(entry.getKey()); + for (Holder holder : entry.getValue()) { holder .unwrapKey() .map(ResourceKey::location) .filter(BuiltInRegistries.ITEM::containsKey) - .ifPresent(itemId -> tagMap.put(tag, itemId)); + .ifPresent(itemId -> tagMap.put(unifyTag, itemId)); } } + return tagMap; } /** - * Creates a filtered {@link TagMap}. + * Creates a filtered tag map copy. * * @param tagFilter A filter to determine which tags to include. * @param itemFilter A filter to determine which items to include. - * @return A new {@link TagMap}. + * @return A filtered copy of this tag map. */ public TagMap filtered(Predicate> tagFilter, Predicate itemFilter) { TagMap tagMap = new TagMap(); @@ -84,19 +95,6 @@ public class TagMap { return tagMap; } - protected void put(UnifyTag tag, ResourceLocation item) { - tagsToItems.computeIfAbsent(tag, k -> new HashSet<>()).add(item); - itemsToTags.computeIfAbsent(item, k -> new HashSet<>()).add(tag); - } - - public Collection getItems(UnifyTag tag) { - return Collections.unmodifiableSet(tagsToItems.getOrDefault(tag, Collections.emptySet())); - } - - public Collection> getTags(ResourceLocation items) { - return Collections.unmodifiableSet(itemsToTags.getOrDefault(items, Collections.emptySet())); - } - public int tagSize() { return tagsToItems.size(); } @@ -105,7 +103,47 @@ public class TagMap { return itemsToTags.size(); } - public Collection> getTags() { + public Set getItemsByTag(UnifyTag tag) { + return Collections.unmodifiableSet(tagsToItems.getOrDefault(tag, Collections.emptySet())); + } + + public Set> getTagsByItem(ResourceLocation items) { + return Collections.unmodifiableSet(itemsToTags.getOrDefault(items, Collections.emptySet())); + } + + public Set> getTags() { return Collections.unmodifiableSet(tagsToItems.keySet()); } + + /** + * Helper function to build a relationship between a tag and an item. + *

+ * If the entries don't exist in the internal maps yet, they will be created. That means + * it needs to be checked whether the tag or item is valid before calling this method. + * + * @param tag The tag. + * @param item The item. + */ + protected void put(UnifyTag tag, ResourceLocation item) { + tagsToItems.computeIfAbsent(tag, k -> new HashSet<>()).add(item); + itemsToTags.computeIfAbsent(item, k -> new HashSet<>()).add(tag); + } + + /** + * Helper function to fetch all item tags and their item holders from the tag manager. + * + * @param tagManager The tag manager. + * @return A map of all item tags and their item holders. + */ + private static Map>> unpackTagManager(TagManager tagManager) { + var tags = tagManager + .getResult() + .stream() + .filter(result -> result.key() == Registries.ITEM) + .findFirst() + .map(TagManager.LoadResult::tags) + .orElseThrow(() -> new IllegalStateException("No item tag result found")); + + return Utils.cast(tags); + } } diff --git a/Common/src/main/java/com/almostreliable/unified/utils/TagOwnerships.java b/Common/src/main/java/com/almostreliable/unified/utils/TagOwnerships.java new file mode 100644 index 0000000..a280d90 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/utils/TagOwnerships.java @@ -0,0 +1,144 @@ +package com.almostreliable.unified.utils; + +import com.almostreliable.unified.AlmostUnified; +import com.google.common.collect.*; +import net.minecraft.core.Holder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +public class TagOwnerships { + + /** + * A map holding relationships between reference tags and their owner tags. + *

+ * Example:
+ * If the map contains the entry {@code minecraft:logs -> minecraft:planks}, + * any recipes where the tag {@code minecraft:logs} is being used, it will + * replace the tag with {@code minecraft:planks}. + *

+ * Map Key = Tag to replace
+ * Map Value = Tag to replace with + */ + private final Map, UnifyTag> refsToOwner; + private final Multimap, UnifyTag> ownerToRefs; + + /** + * Creates a new TagOwnerships instance that contains immutable maps of all tag ownership relationships. + *

+ * It is ensured that all owner tags are present in the {@code unifyTags} set, and that all reference tags + * aren't present in the {@code unifyTags} set. + * + * @param unifyTags The set of all unify tags in use. + * @param tagOwnershipConfig The map of all tag ownership relationships. + */ + public TagOwnerships(Set> unifyTags, Map> tagOwnershipConfig) { + ImmutableMap.Builder, UnifyTag> refsToOwnerBuilder = ImmutableMap.builder(); + ImmutableMultimap.Builder, UnifyTag> ownerToRefsBuilder = ImmutableMultimap.builder(); + + tagOwnershipConfig.forEach((rawOwner, rawRefs) -> { + for (ResourceLocation rawRef : rawRefs) { + UnifyTag owner = UnifyTag.item(rawOwner); + UnifyTag ref = UnifyTag.item(rawRef); + + if (!unifyTags.contains(owner)) { + AlmostUnified.LOG.warn( + "[TagOwnerships] Owner tag '#{}' is not present in the unify tag list!", + owner.location() + ); + continue; + } + + if (unifyTags.contains(ref)) { + AlmostUnified.LOG.warn( + "[TagOwnerships] Reference tag '#{}' of owner tag '#{}' is present in the unify tag list!", + ref.location(), + owner.location() + ); + continue; + } + + refsToOwnerBuilder.put(ref, owner); + ownerToRefsBuilder.put(owner, ref); + } + }); + + this.refsToOwner = refsToOwnerBuilder.build(); + this.ownerToRefs = ownerToRefsBuilder.build(); + } + + /** + * Applies tag ownerships to the provided raw tags. + *

+ * The raw tags are then processed by the game and actual tags are created. + * + * @param rawTags The raw tags to apply ownerships to. + */ + public void applyOwnershipToRawTags(Map>> rawTags) { + Multimap changedTags = HashMultimap.create(); + + ownerToRefs.asMap().forEach((owner, refs) -> { + var rawHolders = rawTags.get(owner.location()); + if (rawHolders == null) { + AlmostUnified.LOG.warn("[TagOwnerships] Owner tag '#{}' does not exist!", owner.location()); + return; + } + + ImmutableSet.Builder> holders = ImmutableSet.builder(); + holders.addAll(rawHolders); + boolean changed = false; + + for (UnifyTag ref : refs) { + var refHolders = rawTags.get(ref.location()); + if (refHolders == null) { + AlmostUnified.LOG.warn( + "[TagOwnerships] Reference tag '#{}' of owner tag '#{}' does not exist!", + ref.location(), + owner.location() + ); + continue; + } + + for (Holder holder : refHolders) { + holders.add(holder); + holder.unwrapKey().ifPresent(key -> changedTags.put(ref.location(), key.location())); + changed = true; + } + } + + if (changed) { + rawTags.put(owner.location(), holders.build()); + } + }); + + if (!changedTags.isEmpty()) { + changedTags.asMap().forEach((tag, items) -> { + AlmostUnified.LOG.info("[TagOwnerships] Modified tag '#{}', added {}", tag, items); + }); + } + } + + /** + * Gets the owner tag for the provided reference tag. + * + * @param tag The reference tag to get the owner for. + * @return The owner tag, or null if the provided tag is not a reference tag. + */ + @Nullable + public UnifyTag getOwnerByTag(UnifyTag tag) { + return refsToOwner.get(tag); + } + + /** + * Gets all reference tags for all owner tags. + * + * @return A set of all reference tags. + */ + public Set> getRefs() { + return refsToOwner.keySet(); + } +} diff --git a/Common/src/main/resources/almostunified-common.mixins.json b/Common/src/main/resources/almostunified-common.mixins.json index 8c28cde..a801a21 100644 --- a/Common/src/main/resources/almostunified-common.mixins.json +++ b/Common/src/main/resources/almostunified-common.mixins.json @@ -6,11 +6,12 @@ "plugin": "com.almostreliable.unified.mixin.AlmostMixinPlugin", "mixins": [ "RecipeManagerMixin", - "ReloadableServerResourcesMixin" + "TagLoaderMixin", + "TagManagerMixin" ], "client": [ "ClientPacketListenerMixin", - "JeiRecipesGuiMixin" + "JeiRecipeLayoutMixin" ], "injectors": { "defaultRequire": 1 diff --git a/Common/src/test/java/com/almostreliable/unified/TestUtils.java b/Common/src/test/java/com/almostreliable/unified/TestUtils.java index 827b7d2..ade5546 100644 --- a/Common/src/test/java/com/almostreliable/unified/TestUtils.java +++ b/Common/src/test/java/com/almostreliable/unified/TestUtils.java @@ -41,6 +41,7 @@ public final class TestUtils { Defaults.getTags(Platform.FORGE), TEST_MOD_PRIORITIES, new HashMap<>(), + new HashMap<>(), new HashSet<>(), new HashSet<>(), new HashSet<>(), @@ -104,7 +105,11 @@ public final class TestUtils { } public static StoneStrataHandler createTestStrataHandler() { - return StoneStrataHandler.create(List.of(), Set.of(), TagMap.create(List.of())); + return StoneStrataHandler.create( + List.of(), + Set.of(), + TagMap.create(Set.of()) + ); } public static RecipeTransformer basicTransformer(Consumer consumer) { diff --git a/Common/src/test/java/com/almostreliable/unified/utils/TagMapTests.java b/Common/src/test/java/com/almostreliable/unified/utils/TagMapTests.java index bdd501d..66d4a97 100644 --- a/Common/src/test/java/com/almostreliable/unified/utils/TagMapTests.java +++ b/Common/src/test/java/com/almostreliable/unified/utils/TagMapTests.java @@ -44,12 +44,12 @@ public class TagMapTests { tagMap.put(bronzeOreTag, TestUtils.mod4RL("bronze_ore")); tagMap.put(bronzeOreTag, TestUtils.mod5RL("bronze_ore")); - assertEquals(tagMap.getItems(bronzeOreTag).size(), 5); - assertEquals(tagMap.getTags(TestUtils.mod1RL("bronze_ore")).size(), 1); - assertEquals(tagMap.getTags(TestUtils.mod2RL("bronze_ore")).size(), 1); - assertEquals(tagMap.getTags(TestUtils.mod3RL("bronze_ore")).size(), 1); - assertEquals(tagMap.getTags(TestUtils.mod4RL("bronze_ore")).size(), 1); - assertEquals(tagMap.getTags(TestUtils.mod5RL("bronze_ore")).size(), 1); + assertEquals(tagMap.getItemsByTag(bronzeOreTag).size(), 5); + assertEquals(tagMap.getTagsByItem(TestUtils.mod1RL("bronze_ore")).size(), 1); + assertEquals(tagMap.getTagsByItem(TestUtils.mod2RL("bronze_ore")).size(), 1); + assertEquals(tagMap.getTagsByItem(TestUtils.mod3RL("bronze_ore")).size(), 1); + assertEquals(tagMap.getTagsByItem(TestUtils.mod4RL("bronze_ore")).size(), 1); + assertEquals(tagMap.getTagsByItem(TestUtils.mod5RL("bronze_ore")).size(), 1); tagMap.put(UnifyTag.item(new ResourceLocation("forge:ores/invar")), TestUtils.mod1RL("invar_ore")); diff --git a/Fabric/build.gradle.kts b/Fabric/build.gradle.kts index 59bb571..b951f72 100644 --- a/Fabric/build.gradle.kts +++ b/Fabric/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { modCompileOnly("me.shedaniel:RoughlyEnoughItems-api-fabric:$reiVersion") // required for common rei plugin compileOnly("me.shedaniel:REIPluginCompatibilities-forge-annotations:9.+") // required to disable rei compat layer on jei plugin testCompileOnly("me.shedaniel:REIPluginCompatibilities-forge-annotations:9.+") // don't question this, it's required for compiling - modCompileOnly("mezz.jei:jei-$minecraftVersion-fabric:$jeiVersion") // required for common jei plugin and mixin + modCompileOnly("mezz.jei:jei-$minecraftVersion-fabric-api:$jeiVersion") // required for common jei plugin and mixin when (fabricRecipeViewer) { // runtime only "rei" -> modLocalRuntime("me.shedaniel:RoughlyEnoughItems-fabric:$reiVersion") "jei" -> modLocalRuntime("mezz.jei:jei-$minecraftVersion-fabric:$jeiVersion") diff --git a/Fabric/src/main/resources/fabric.mod.json b/Fabric/src/main/resources/fabric.mod.json index 377a068..a7cde67 100644 --- a/Fabric/src/main/resources/fabric.mod.json +++ b/Fabric/src/main/resources/fabric.mod.json @@ -32,5 +32,9 @@ "fabric": ">=${fabricApiVersion}", "minecraft": ">=${minecraftVersion}", "java": ">=17" + }, + "suggests": { + "jei": ">=${jeiVersion}", + "roughlyenoughitems": ">=${reiVersion}" } } diff --git a/Forge/build.gradle.kts b/Forge/build.gradle.kts index a31d141..b6ec1d9 100644 --- a/Forge/build.gradle.kts +++ b/Forge/build.gradle.kts @@ -44,7 +44,7 @@ dependencies { modCompileOnly("me.shedaniel:RoughlyEnoughItems-forge:$reiVersion") // required for common rei plugin | api does not work here compileOnly("me.shedaniel:REIPluginCompatibilities-forge-annotations:9.+") // required to disable rei compat layer on jei plugin testCompileOnly("me.shedaniel:REIPluginCompatibilities-forge-annotations:9.+") // don't question this, it's required for compiling - modCompileOnly("mezz.jei:jei-$minecraftVersion-forge:$jeiVersion") { + modCompileOnly("mezz.jei:jei-$minecraftVersion-forge-api:$jeiVersion") { isTransitive = false } // required for common jei plugin and mixin, transitivity is off because it breaks the forge runtime when (forgeRecipeViewer) { // runtime only diff --git a/Forge/src/main/java/com/almostreliable/unified/compat/MekanismRecipeUnifier.java b/Forge/src/main/java/com/almostreliable/unified/compat/MekanismRecipeUnifier.java index 067ffc6..ef41291 100644 --- a/Forge/src/main/java/com/almostreliable/unified/compat/MekanismRecipeUnifier.java +++ b/Forge/src/main/java/com/almostreliable/unified/compat/MekanismRecipeUnifier.java @@ -4,11 +4,15 @@ import com.almostreliable.unified.api.recipe.RecipeConstants; import com.almostreliable.unified.api.recipe.RecipeUnifier; import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder; +import java.util.List; + public class MekanismRecipeUnifier implements RecipeUnifier { @Override public void collectUnifier(RecipeUnifierBuilder builder) { - builder.put(RecipeConstants.MAIN_INPUT, (json, ctx) -> ctx.createIngredientReplacement(json)); - builder.put(RecipeConstants.MAIN_OUTPUT, (json, ctx) -> ctx.createResultReplacement(json)); - builder.put(RecipeConstants.SECONDARY_OUTPUT, (json, ctx) -> ctx.createResultReplacement(json)); + List.of(RecipeConstants.MAIN_INPUT, RecipeConstants.ITEM_INPUT) + .forEach(key -> builder.put(key, (json, ctx) -> ctx.createIngredientReplacement(json))); + + List.of(RecipeConstants.MAIN_OUTPUT, RecipeConstants.ITEM_OUTPUT, RecipeConstants.SECONDARY_OUTPUT) + .forEach(key -> builder.put(key, (json, ctx) -> ctx.createResultReplacement(json))); } } diff --git a/Forge/src/main/resources/META-INF/mods.toml b/Forge/src/main/resources/META-INF/mods.toml index 933d98f..5984d99 100644 --- a/Forge/src/main/resources/META-INF/mods.toml +++ b/Forge/src/main/resources/META-INF/mods.toml @@ -24,3 +24,24 @@ mandatory = true versionRange = "[${minecraftVersion},)" ordering = "NONE" side = "BOTH" + +[[dependencies.${modId}]] +modId = "jei" +mandatory = false +versionRange = "[${jeiVersion},)" +ordering = "BEFORE" +side = "BOTH" + +[[dependencies.${modId}]] +modId = "roughlyenoughitems" +mandatory = false +versionRange = "[${reiVersion},)" +ordering = "BEFORE" +side = "BOTH" + +[[dependencies.${modId}]] +modId = "rei_plugin_compatibilities" +mandatory = false +versionRange = "[9.0.43,)" +ordering = "BEFORE" +side = "BOTH" diff --git a/build.gradle.kts b/build.gradle.kts index d13ffb7..b17451c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,6 +18,7 @@ val modAuthor: String by project val githubRepo: String by project val githubUser: String by project val sharedRunDir: String by project +val jeiVersion: String by project val reiVersion: String by project plugins { @@ -156,6 +157,7 @@ subprojects { "fabricApiVersion" to fabricApiVersion, "forgeVersion" to forgeVersion, "forgeFMLVersion" to forgeVersion.substringBefore("."), // Only use major version as FML error message sucks. The error message for wrong Forge version is way better. + "jeiVersion" to jeiVersion, "reiVersion" to reiVersion, "githubUser" to githubUser, "githubRepo" to githubRepo diff --git a/gradle.properties b/gradle.properties index 2a983fa..8189746 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ junitVersion = 5.9.0 minecraftVersion = 1.19.4 # Mod -modVersion = 0.3.8 +modVersion = 0.4.0 modPackage = com.almostreliable.unified modId = almostunified modName = AlmostUnified @@ -20,7 +20,7 @@ modDescription = Unify all resources. # Mod Dependencies reiVersion = 11.0.597 -jeiVersion = 13.1.0.2 +jeiVersion = 13.1.0.3 # Fabric Settings fabricLoaderVersion = 0.14.18