[1.20.1] tag inheritance (#57)

This commit is contained in:
Relentless 2023-08-06 00:34:25 +02:00 committed by GitHub
parent 1562b0323d
commit 47b50f9bc8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 608 additions and 134 deletions

View file

@ -1,19 +1,16 @@
package com.almostreliable.unified; package com.almostreliable.unified;
import com.almostreliable.unified.api.StoneStrataHandler;
import com.almostreliable.unified.config.Config; import com.almostreliable.unified.config.Config;
import com.almostreliable.unified.config.ServerConfigs; import com.almostreliable.unified.config.ServerConfigs;
import com.almostreliable.unified.config.StartupConfig; import com.almostreliable.unified.config.StartupConfig;
import com.almostreliable.unified.config.UnifyConfig; import com.almostreliable.unified.config.UnifyConfig;
import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory; import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory;
import com.almostreliable.unified.utils.ReplacementMap;
import com.almostreliable.unified.utils.TagMap;
import com.almostreliable.unified.utils.TagOwnerships; import com.almostreliable.unified.utils.TagOwnerships;
import com.almostreliable.unified.utils.TagReloadHandler;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import net.minecraft.core.Holder; import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagManager;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -28,7 +25,6 @@ public final class AlmostUnified {
public static final Logger LOG = LogManager.getLogger(BuildConfig.MOD_NAME); public static final Logger LOG = LogManager.getLogger(BuildConfig.MOD_NAME);
@Nullable private static AlmostUnifiedRuntime RUNTIME; @Nullable private static AlmostUnifiedRuntime RUNTIME;
@Nullable private static TagManager TAG_MANAGER;
@Nullable private static StartupConfig STARTUP_CONFIG; @Nullable private static StartupConfig STARTUP_CONFIG;
public static boolean isRuntimeLoaded() { public static boolean isRuntimeLoaded() {
@ -49,40 +45,52 @@ public final class AlmostUnified {
return STARTUP_CONFIG; return STARTUP_CONFIG;
} }
public static void onTagManagerReload(TagManager tagManager) {
TAG_MANAGER = tagManager;
}
public static void onTagLoaderReload(Map<ResourceLocation, Collection<Holder<Item>>> tags) { public static void onTagLoaderReload(Map<ResourceLocation, Collection<Holder<Item>>> tags) {
Preconditions.checkNotNull(TAG_MANAGER, "TagManager was not loaded correctly"); RecipeHandlerFactory recipeHandlerFactory = new RecipeHandlerFactory();
AlmostUnifiedPlatform.INSTANCE.bindRecipeHandlers(recipeHandlerFactory);
ServerConfigs serverConfigs = ServerConfigs.load(); ServerConfigs serverConfigs = ServerConfigs.load();
UnifyConfig unifyConfig = serverConfigs.getUnifyConfig(); UnifyConfig unifyConfig = serverConfigs.getUnifyConfig();
var unifyTags = unifyConfig.bakeTags(); TagOwnerships tagOwnerships = new TagOwnerships(
unifyConfig.bakeAndValidateTags(tags),
TagOwnerships tagOwnerships = new TagOwnerships(unifyTags, unifyConfig.getTagOwnerships()); unifyConfig.getTagOwnerships()
);
tagOwnerships.applyOwnerships(tags); tagOwnerships.applyOwnerships(tags);
TagMap globalTagMap = TagMap.create(tags); ReplacementData replacementData = loadReplacementData(tags, unifyConfig, tagOwnerships);
TagMap filteredTagMap = globalTagMap.filtered(unifyTags::contains, unifyConfig::includeItem);
StoneStrataHandler stoneStrataHandler = StoneStrataHandler.create( RUNTIME = new AlmostUnifiedRuntimeImpl(
unifyConfig.getStoneStrata(), serverConfigs,
AlmostUnifiedPlatform.INSTANCE.getStoneStrataTags(unifyConfig.getStoneStrata()), replacementData.filteredTagMap(),
globalTagMap replacementData.replacementMap(),
recipeHandlerFactory
); );
ReplacementMap repMap = new ReplacementMap(unifyConfig, filteredTagMap, stoneStrataHandler, tagOwnerships);
RecipeHandlerFactory recipeHandlerFactory = new RecipeHandlerFactory();
AlmostUnifiedPlatform.INSTANCE.bindRecipeHandlers(recipeHandlerFactory);
RUNTIME = new AlmostUnifiedRuntimeImpl(serverConfigs, filteredTagMap, repMap, recipeHandlerFactory);
} }
public static void onRecipeManagerReload(Map<ResourceLocation, JsonElement> recipes) { public static void onRecipeManagerReload(Map<ResourceLocation, JsonElement> recipes) {
Preconditions.checkNotNull(RUNTIME, "AlmostUnifiedRuntime was not loaded correctly"); Preconditions.checkNotNull(RUNTIME, "AlmostUnifiedRuntime was not loaded correctly");
RUNTIME.run(recipes, getStartupConfig().isServerOnly()); RUNTIME.run(recipes, getStartupConfig().isServerOnly());
} }
/**
* Loads the required data for the replacement logic.
* <p>
* This method applies tag inheritance and rebuilds the replacement data if the
* inheritance mutates the tags.
*
* @param tags The vanilla tag map provided by the TagManager
* @param unifyConfig The mod config to use for unifying
* @param tagOwnerships The tag ownerships to apply
* @return The loaded data
*/
private static ReplacementData loadReplacementData(Map<ResourceLocation, Collection<Holder<Item>>> tags, UnifyConfig unifyConfig, TagOwnerships tagOwnerships) {
ReplacementData replacementData = ReplacementData.load(tags, unifyConfig, tagOwnerships);
var needsRebuild = TagReloadHandler.applyInheritance(unifyConfig, replacementData);
if (needsRebuild) {
return ReplacementData.load(tags, unifyConfig, tagOwnerships);
}
return replacementData;
}
} }

View file

@ -22,7 +22,7 @@ public class AlmostUnifiedFallbackRuntime implements AlmostUnifiedRuntime {
@Nullable private static AlmostUnifiedFallbackRuntime INSTANCE; @Nullable private static AlmostUnifiedFallbackRuntime INSTANCE;
@Nullable private UnifyConfig unifyConfig; @Nullable private UnifyConfig unifyConfig;
@Nullable private TagMap filteredTagMap; @Nullable private TagMap<Item> filteredTagMap;
@Nullable private ReplacementMap replacementMap; @Nullable private ReplacementMap replacementMap;
public static AlmostUnifiedFallbackRuntime getInstance() { public static AlmostUnifiedFallbackRuntime getInstance() {
@ -41,7 +41,7 @@ public class AlmostUnifiedFallbackRuntime implements AlmostUnifiedRuntime {
build(); build();
} }
public void build() { private void build() {
unifyConfig = Config.load(UnifyConfig.NAME, new UnifyConfig.Serializer()); unifyConfig = Config.load(UnifyConfig.NAME, new UnifyConfig.Serializer());
Set<UnifyTag<Item>> unifyTags = unifyConfig.bakeTags(); Set<UnifyTag<Item>> unifyTags = unifyConfig.bakeTags();
filteredTagMap = TagMap.create(unifyTags).filtered($ -> true, unifyConfig::includeItem); filteredTagMap = TagMap.create(unifyTags).filtered($ -> true, unifyConfig::includeItem);
@ -52,7 +52,7 @@ public class AlmostUnifiedFallbackRuntime implements AlmostUnifiedRuntime {
private static StoneStrataHandler createStoneStrataHandler(UnifyConfig config) { private static StoneStrataHandler createStoneStrataHandler(UnifyConfig config) {
Set<UnifyTag<Item>> stoneStrataTags = AlmostUnifiedPlatform.INSTANCE.getStoneStrataTags(config.getStoneStrata()); Set<UnifyTag<Item>> stoneStrataTags = AlmostUnifiedPlatform.INSTANCE.getStoneStrataTags(config.getStoneStrata());
TagMap stoneStrataTagMap = TagMap.create(stoneStrataTags); TagMap<Item> stoneStrataTagMap = TagMap.create(stoneStrataTags);
return StoneStrataHandler.create(config.getStoneStrata(), stoneStrataTags, stoneStrataTagMap); return StoneStrataHandler.create(config.getStoneStrata(), stoneStrataTags, stoneStrataTagMap);
} }
@ -62,7 +62,7 @@ public class AlmostUnifiedFallbackRuntime implements AlmostUnifiedRuntime {
} }
@Override @Override
public Optional<TagMap> getFilteredTagMap() { public Optional<TagMap<Item>> getFilteredTagMap() {
return Optional.ofNullable(filteredTagMap); return Optional.ofNullable(filteredTagMap);
} }

View file

@ -9,8 +9,8 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey; import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.level.ItemLike; import net.minecraft.world.level.ItemLike;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -65,7 +65,7 @@ public class AlmostUnifiedLookupImpl implements AlmostUnifiedLookup {
.getRuntime() .getRuntime()
.getFilteredTagMap() .getFilteredTagMap()
.map(tagMap -> tagMap .map(tagMap -> tagMap
.getItemsByTag(asUnifyTag) .getEntriesByTag(asUnifyTag)
.stream() .stream()
.flatMap(rl -> BuiltInRegistries.ITEM.getOptional(rl).stream()) .flatMap(rl -> BuiltInRegistries.ITEM.getOptional(rl).stream())
.collect(Collectors.toSet())) .collect(Collectors.toSet()))

View file

@ -5,6 +5,7 @@ import com.almostreliable.unified.utils.ReplacementMap;
import com.almostreliable.unified.utils.TagMap; import com.almostreliable.unified.utils.TagMap;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -13,7 +14,7 @@ public interface AlmostUnifiedRuntime {
void run(Map<ResourceLocation, JsonElement> recipes, boolean skipClientTracking); void run(Map<ResourceLocation, JsonElement> recipes, boolean skipClientTracking);
Optional<TagMap> getFilteredTagMap(); Optional<TagMap<Item>> getFilteredTagMap();
Optional<ReplacementMap> getReplacementMap(); Optional<ReplacementMap> getReplacementMap();

View file

@ -11,6 +11,7 @@ import com.almostreliable.unified.utils.ReplacementMap;
import com.almostreliable.unified.utils.TagMap; import com.almostreliable.unified.utils.TagMap;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -20,20 +21,20 @@ public final class AlmostUnifiedRuntimeImpl implements AlmostUnifiedRuntime {
private final UnifyConfig unifyConfig; private final UnifyConfig unifyConfig;
private final DuplicationConfig duplicationConfig; private final DuplicationConfig duplicationConfig;
private final DebugConfig debugConfig; private final DebugConfig debugConfig;
private final TagMap filteredTagMap; private final TagMap<Item> tagMap;
private final ReplacementMap replacementMap; private final ReplacementMap replacementMap;
private final RecipeHandlerFactory recipeHandlerFactory; private final RecipeHandlerFactory recipeHandlerFactory;
AlmostUnifiedRuntimeImpl( AlmostUnifiedRuntimeImpl(
ServerConfigs configs, ServerConfigs configs,
TagMap tagMap, TagMap<Item> tagMap,
ReplacementMap repMap, ReplacementMap repMap,
RecipeHandlerFactory recipeHandlerFactory RecipeHandlerFactory recipeHandlerFactory
) { ) {
this.unifyConfig = configs.getUnifyConfig(); this.unifyConfig = configs.getUnifyConfig();
this.duplicationConfig = configs.getDupConfig(); this.duplicationConfig = configs.getDupConfig();
this.debugConfig = configs.getDebugConfig(); this.debugConfig = configs.getDebugConfig();
this.filteredTagMap = tagMap; this.tagMap = tagMap;
this.replacementMap = repMap; this.replacementMap = repMap;
this.recipeHandlerFactory = recipeHandlerFactory; this.recipeHandlerFactory = recipeHandlerFactory;
} }
@ -41,7 +42,7 @@ public final class AlmostUnifiedRuntimeImpl implements AlmostUnifiedRuntime {
@Override @Override
public void run(Map<ResourceLocation, JsonElement> recipes, boolean skipClientTracking) { public void run(Map<ResourceLocation, JsonElement> recipes, boolean skipClientTracking) {
debugConfig.logRecipes(recipes, "recipes_before_unification.txt"); debugConfig.logRecipes(recipes, "recipes_before_unification.txt");
debugConfig.logUnifyTagDump(filteredTagMap); debugConfig.logUnifyTagDump(tagMap);
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
RecipeTransformer.Result result = new RecipeTransformer( RecipeTransformer.Result result = new RecipeTransformer(
@ -57,8 +58,8 @@ public final class AlmostUnifiedRuntimeImpl implements AlmostUnifiedRuntime {
} }
@Override @Override
public Optional<TagMap> getFilteredTagMap() { public Optional<TagMap<Item>> getFilteredTagMap() {
return Optional.of(filteredTagMap); return Optional.of(tagMap);
} }
@Override @Override

View file

@ -0,0 +1,42 @@
package com.almostreliable.unified;
import com.almostreliable.unified.api.StoneStrataHandler;
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 net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import java.util.Collection;
import java.util.Map;
/**
* Holder class for storing all the data needed for replacements in recipes.
*
* @param globalTagMap The global tag map, containing all tags.
* @param filteredTagMap The filtered tag map, containing only the tags that will be used for replacing. Determined by the unify config.
* @param stoneStrataHandler The stone strata handler, used for replacing stone strata.
* @param replacementMap The replacement map, used for replacing items.
*/
public record ReplacementData(TagMap<Item> globalTagMap, TagMap<Item> filteredTagMap,
StoneStrataHandler stoneStrataHandler,
ReplacementMap replacementMap) {
public static ReplacementData load(Map<ResourceLocation, Collection<Holder<Item>>> tags, UnifyConfig unifyConfig, TagOwnerships tagOwnerships) {
var globalTagMap = TagMap.createFromItemTags(tags);
var unifyTags = unifyConfig.bakeAndValidateTags(tags);
var filteredTagMap = globalTagMap.filtered(unifyTags::contains, unifyConfig::includeItem);
var stoneStrataHandler = StoneStrataHandler.create(
unifyConfig.getStoneStrata(),
AlmostUnifiedPlatform.INSTANCE.getStoneStrataTags(unifyConfig.getStoneStrata()),
globalTagMap
);
var replacementMap = new ReplacementMap(unifyConfig, filteredTagMap, stoneStrataHandler, tagOwnerships);
return new ReplacementData(globalTagMap, filteredTagMap, stoneStrataHandler, replacementMap);
}
}

View file

@ -9,18 +9,18 @@ import net.minecraft.world.item.Item;
import java.util.*; import java.util.*;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class StoneStrataHandler { public final class StoneStrataHandler {
private final List<String> stoneStrata; private final List<String> stoneStrata;
private final Pattern tagMatcher; private final Pattern tagMatcher;
private final TagMap stoneStrataTagMap; private final TagMap<Item> stoneStrataTagMap;
// don't clear the caches, so they are available for the runtime and KubeJS binding // 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 // the runtime holding this handler is automatically yeeted on reload
private final Map<UnifyTag<?>, Boolean> stoneStrataTagCache; private final Map<UnifyTag<?>, Boolean> stoneStrataTagCache;
private final Map<ResourceLocation, String> stoneStrataCache; private final Map<ResourceLocation, String> stoneStrataCache;
private StoneStrataHandler(List<String> stoneStrata, Pattern tagMatcher, TagMap stoneStrataTagMap) { private StoneStrataHandler(List<String> stoneStrata, Pattern tagMatcher, TagMap<Item> stoneStrataTagMap) {
this.stoneStrata = createSortedStoneStrata(stoneStrata); this.stoneStrata = createSortedStoneStrata(stoneStrata);
this.tagMatcher = tagMatcher; this.tagMatcher = tagMatcher;
this.stoneStrataTagMap = stoneStrataTagMap; this.stoneStrataTagMap = stoneStrataTagMap;
@ -41,8 +41,8 @@ public class StoneStrataHandler {
return stoneStrata.stream().sorted(Comparator.comparingInt(String::length).reversed()).toList(); return stoneStrata.stream().sorted(Comparator.comparingInt(String::length).reversed()).toList();
} }
public static StoneStrataHandler create(List<String> stoneStrataIds, Set<UnifyTag<Item>> stoneStrataTags, TagMap tagMap) { public static StoneStrataHandler create(List<String> stoneStrataIds, Set<UnifyTag<Item>> stoneStrataTags, TagMap<Item> tagMap) {
TagMap stoneStrataTagMap = tagMap.filtered(stoneStrataTags::contains, item -> true); var stoneStrataTagMap = tagMap.filtered(stoneStrataTags::contains, item -> true);
Pattern tagMatcher = Pattern.compile(switch (AlmostUnifiedPlatform.INSTANCE.getPlatform()) { Pattern tagMatcher = Pattern.compile(switch (AlmostUnifiedPlatform.INSTANCE.getPlatform()) {
case FORGE -> "forge:ores/.+"; case FORGE -> "forge:ores/.+";
case FABRIC -> "(c:ores/.+|c:.+_ores)"; case FABRIC -> "(c:ores/.+|c:.+_ores)";
@ -71,7 +71,7 @@ public class StoneStrataHandler {
*/ */
private String computeStoneStrata(ResourceLocation item) { private String computeStoneStrata(ResourceLocation item) {
String strata = stoneStrataTagMap String strata = stoneStrataTagMap
.getTagsByItem(item) .getTagsByEntry(item)
.stream() .stream()
.findFirst() .findFirst()
.map(UnifyTag::location) .map(UnifyTag::location)

View file

@ -3,8 +3,8 @@ package com.almostreliable.unified.compat;
import com.almostreliable.unified.AlmostUnified; import com.almostreliable.unified.AlmostUnified;
import com.almostreliable.unified.AlmostUnifiedRuntime; import com.almostreliable.unified.AlmostUnifiedRuntime;
import com.almostreliable.unified.utils.ReplacementMap; import com.almostreliable.unified.utils.ReplacementMap;
import com.almostreliable.unified.utils.TagMap;
import com.almostreliable.unified.utils.TagOwnerships; import com.almostreliable.unified.utils.TagOwnerships;
import com.almostreliable.unified.utils.Utils;
import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries; import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
@ -18,22 +18,23 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class HideHelper { public final class HideHelper {
private HideHelper() {}
public static Collection<ItemStack> createHidingList(AlmostUnifiedRuntime runtime) { public static Collection<ItemStack> createHidingList(AlmostUnifiedRuntime runtime) {
ReplacementMap repMap = runtime.getReplacementMap().orElse(null); ReplacementMap repMap = runtime.getReplacementMap().orElse(null);
TagMap tagMap = runtime.getFilteredTagMap().orElse(null); var tagMap = runtime.getFilteredTagMap().orElse(null);
if (repMap == null || tagMap == null) return new ArrayList<>(); if (repMap == null || tagMap == null) return new ArrayList<>();
Set<ResourceLocation> hidingList = new HashSet<>(); Set<ResourceLocation> hidingList = new HashSet<>();
for (var unifyTag : tagMap.getTags()) { for (var unifyTag : tagMap.getTags()) {
var itemsByTag = tagMap.getItemsByTag(unifyTag); var itemsByTag = tagMap.getEntriesByTag(unifyTag);
// avoid hiding single entries and tags that only contain the same namespace for all items // avoid handling single entries and tags that only contain the same namespace for all items
long namespaces = itemsByTag.stream().map(ResourceLocation::getNamespace).distinct().count(); if (Utils.allSameNamespace(itemsByTag)) continue;
if (namespaces <= 1) continue;
Set<ResourceLocation> replacements = new HashSet<>(); Set<ResourceLocation> replacements = new HashSet<>();
for (ResourceLocation item : itemsByTag) { for (ResourceLocation item : itemsByTag) {

View file

@ -6,6 +6,7 @@ import com.almostreliable.unified.utils.TagMap;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.util.Comparator; import java.util.Comparator;
@ -29,7 +30,7 @@ public class DebugConfig extends Config {
this.dumpRecipes = dumpRecipes; this.dumpRecipes = dumpRecipes;
} }
public void logUnifyTagDump(TagMap tagMap) { public void logUnifyTagDump(TagMap<Item> tagMap) {
if (!dumpTagMap) { if (!dumpTagMap) {
return; return;
} }
@ -40,7 +41,7 @@ public class DebugConfig extends Config {
.stream() .stream()
.sorted(Comparator.comparing(t -> t.location().toString())) .sorted(Comparator.comparing(t -> t.location().toString()))
.map(t -> StringUtils.rightPad(t.location().toString(), 40) + " => " + tagMap .map(t -> StringUtils.rightPad(t.location().toString(), 40) + " => " + tagMap
.getItemsByTag(t) .getEntriesByTag(t)
.stream() .stream()
.map(ResourceLocation::toString) .map(ResourceLocation::toString)
.sorted() .sorted()

View file

@ -5,11 +5,15 @@ import com.almostreliable.unified.AlmostUnifiedPlatform;
import com.almostreliable.unified.utils.JsonUtils; import com.almostreliable.unified.utils.JsonUtils;
import com.almostreliable.unified.utils.UnifyTag; import com.almostreliable.unified.utils.UnifyTag;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.*; import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -22,6 +26,10 @@ public class UnifyConfig extends Config {
private final List<String> materials; private final List<String> materials;
private final Map<ResourceLocation, String> priorityOverrides; private final Map<ResourceLocation, String> priorityOverrides;
private final Map<ResourceLocation, Set<ResourceLocation>> tagOwnerships; private final Map<ResourceLocation, Set<ResourceLocation>> tagOwnerships;
private final Enum<TagInheritanceMode> itemTagInheritanceMode;
private final Map<ResourceLocation, Set<Pattern>> itemTagInheritance;
private final Enum<TagInheritanceMode> blockTagInheritanceMode;
private final Map<ResourceLocation, Set<Pattern>> blockTagInheritance;
private final Set<UnifyTag<Item>> ignoredTags; private final Set<UnifyTag<Item>> ignoredTags;
private final Set<Pattern> ignoredItems; private final Set<Pattern> ignoredItems;
private final Set<Pattern> ignoredRecipeTypes; private final Set<Pattern> ignoredRecipeTypes;
@ -38,6 +46,10 @@ public class UnifyConfig extends Config {
List<String> materials, List<String> materials,
Map<ResourceLocation, String> priorityOverrides, Map<ResourceLocation, String> priorityOverrides,
Map<ResourceLocation, Set<ResourceLocation>> tagOwnerships, Map<ResourceLocation, Set<ResourceLocation>> tagOwnerships,
Enum<TagInheritanceMode> itemTagInheritanceMode,
Map<ResourceLocation, Set<Pattern>> itemTagInheritance,
Enum<TagInheritanceMode> blockTagInheritanceMode,
Map<ResourceLocation, Set<Pattern>> blockTagInheritance,
Set<UnifyTag<Item>> ignoredTags, Set<UnifyTag<Item>> ignoredTags,
Set<Pattern> ignoredItems, Set<Pattern> ignoredItems,
Set<Pattern> ignoredRecipeTypes, Set<Pattern> ignoredRecipeTypes,
@ -50,6 +62,10 @@ public class UnifyConfig extends Config {
this.materials = materials; this.materials = materials;
this.priorityOverrides = priorityOverrides; this.priorityOverrides = priorityOverrides;
this.tagOwnerships = tagOwnerships; this.tagOwnerships = tagOwnerships;
this.itemTagInheritanceMode = itemTagInheritanceMode;
this.itemTagInheritance = itemTagInheritance;
this.blockTagInheritanceMode = blockTagInheritanceMode;
this.blockTagInheritance = blockTagInheritance;
this.ignoredTags = ignoredTags; this.ignoredTags = ignoredTags;
this.ignoredItems = ignoredItems; this.ignoredItems = ignoredItems;
this.ignoredRecipeTypes = ignoredRecipeTypes; this.ignoredRecipeTypes = ignoredRecipeTypes;
@ -67,11 +83,20 @@ public class UnifyConfig extends Config {
} }
public Set<UnifyTag<Item>> bakeTags() { public Set<UnifyTag<Item>> bakeTags() {
return bakeTags($ -> true);
}
public Set<UnifyTag<Item>> bakeAndValidateTags(Map<ResourceLocation, Collection<Holder<Item>>> tags) {
return bakeTags(tags::containsKey);
}
private Set<UnifyTag<Item>> bakeTags(Predicate<ResourceLocation> tagValidator) {
if (bakedTagsCache != null) { if (bakedTagsCache != null) {
return bakedTagsCache; return bakedTagsCache;
} }
Set<UnifyTag<Item>> result = new HashSet<>(); Set<UnifyTag<Item>> result = new HashSet<>();
Set<UnifyTag<Item>> wrongTags = new HashSet<>();
for (String tag : unbakedTags) { for (String tag : unbakedTags) {
for (String material : materials) { for (String material : materials) {
@ -83,12 +108,24 @@ public class UnifyConfig extends Config {
} }
UnifyTag<Item> t = UnifyTag.item(asRL); UnifyTag<Item> t = UnifyTag.item(asRL);
if (!ignoredTags.contains(t)) { if (ignoredTags.contains(t)) continue;
result.add(t);
if (!tagValidator.test(asRL)) {
wrongTags.add(t);
continue;
} }
result.add(t);
} }
} }
if (!wrongTags.isEmpty()) {
AlmostUnified.LOG.warn(
"The following tags are invalid and will be ignored: {}",
wrongTags.stream().map(UnifyTag::location).collect(Collectors.toList())
);
}
bakedTagsCache = result; bakedTagsCache = result;
return result; return result;
} }
@ -107,6 +144,45 @@ public class UnifyConfig extends Config {
return Collections.unmodifiableMap(tagOwnerships); return Collections.unmodifiableMap(tagOwnerships);
} }
public boolean shouldInheritItemTag(UnifyTag<Item> itemTag, Set<UnifyTag<Item>> dominantTags) {
var patterns = itemTagInheritance.get(itemTag.location());
boolean result = checkPatterns(dominantTags, patterns);
// noinspection SimplifiableConditionalExpression
return itemTagInheritanceMode == TagInheritanceMode.ALLOW ? result : !result;
}
public boolean shouldInheritBlockTag(UnifyTag<Block> itemTag, Set<UnifyTag<Item>> dominantTags) {
var patterns = blockTagInheritance.get(itemTag.location());
boolean result = checkPatterns(dominantTags, patterns);
// noinspection SimplifiableConditionalExpression
return blockTagInheritanceMode == TagInheritanceMode.ALLOW ? result : !result;
}
/**
* Checks all patterns against all dominant tags.
* <p>
* This implementation works based on the assumption that the mode is {@link TagInheritanceMode#ALLOW}.
* Flip the result if the mode is {@link TagInheritanceMode#DENY}.
*
* @param dominantTags The tags of the dominant item to check.
* @param patterns The patterns to check against.
* @param <T> The type of the dominant tags.
* @return Whether the dominant tags match any of the patterns.
*/
private static <T> boolean checkPatterns(Set<UnifyTag<T>> dominantTags, @Nullable Set<Pattern> patterns) {
if (patterns == null) return false;
for (var pattern : patterns) {
for (var dominantTag : dominantTags) {
if (pattern.matcher(dominantTag.location().toString()).matches()) {
return true;
}
}
}
return false;
}
public boolean includeItem(ResourceLocation item) { public boolean includeItem(ResourceLocation item) {
for (Pattern pattern : ignoredItems) { for (Pattern pattern : ignoredItems) {
if (pattern.matcher(item.toString()).matches()) { if (pattern.matcher(item.toString()).matches()) {
@ -152,6 +228,10 @@ public class UnifyConfig extends Config {
public static final String MATERIALS = "materials"; public static final String MATERIALS = "materials";
public static final String PRIORITY_OVERRIDES = "priorityOverrides"; public static final String PRIORITY_OVERRIDES = "priorityOverrides";
public static final String TAG_OWNERSHIPS = "tagOwnerships"; public static final String TAG_OWNERSHIPS = "tagOwnerships";
public static final String ITEM_TAG_INHERITANCE_MODE = "itemTagInheritanceMode";
public static final String ITEM_TAG_INHERITANCE = "itemTagInheritance";
public static final String BLOCK_TAG_INHERITANCE_MODE = "blockTagInheritanceMode";
public static final String BLOCK_TAG_INHERITANCE = "blockTagInheritance";
public static final String IGNORED_TAGS = "ignoredTags"; public static final String IGNORED_TAGS = "ignoredTags";
public static final String IGNORED_ITEMS = "ignoredItems"; public static final String IGNORED_ITEMS = "ignoredItems";
public static final String IGNORED_RECIPE_TYPES = "ignoredRecipeTypes"; public static final String IGNORED_RECIPE_TYPES = "ignoredRecipeTypes";
@ -168,6 +248,7 @@ public class UnifyConfig extends Config {
List<String> tags = safeGet(() -> JsonUtils.toList(json.getAsJsonArray(TAGS)), Defaults.getTags(platform)); List<String> tags = safeGet(() -> JsonUtils.toList(json.getAsJsonArray(TAGS)), Defaults.getTags(platform));
List<String> materials = safeGet(() -> JsonUtils.toList(json.getAsJsonArray(MATERIALS)), List<String> materials = safeGet(() -> JsonUtils.toList(json.getAsJsonArray(MATERIALS)),
Defaults.MATERIALS); Defaults.MATERIALS);
Map<ResourceLocation, String> priorityOverrides = safeGet(() -> json.getAsJsonObject(PRIORITY_OVERRIDES) Map<ResourceLocation, String> priorityOverrides = safeGet(() -> json.getAsJsonObject(PRIORITY_OVERRIDES)
.entrySet() .entrySet()
.stream() .stream()
@ -176,6 +257,7 @@ public class UnifyConfig extends Config {
entry -> entry.getValue().getAsString(), entry -> entry.getValue().getAsString(),
(a, b) -> b, (a, b) -> b,
HashMap::new)), new HashMap<>()); HashMap::new)), new HashMap<>());
Map<ResourceLocation, Set<ResourceLocation>> tagOwnerships = safeGet(() -> json Map<ResourceLocation, Set<ResourceLocation>> tagOwnerships = safeGet(() -> json
.getAsJsonObject(TAG_OWNERSHIPS) .getAsJsonObject(TAG_OWNERSHIPS)
.entrySet() .entrySet()
@ -188,6 +270,17 @@ public class UnifyConfig extends Config {
.collect(Collectors.toSet()), .collect(Collectors.toSet()),
(a, b) -> b, (a, b) -> b,
HashMap::new)), new HashMap<>()); HashMap::new)), new HashMap<>());
Enum<TagInheritanceMode> itemTagInheritanceMode = deserializeTagInheritanceMode(json,
ITEM_TAG_INHERITANCE_MODE);
Map<ResourceLocation, Set<Pattern>> itemTagInheritance = deserializePatternsForLocations(json,
ITEM_TAG_INHERITANCE);
Enum<TagInheritanceMode> blockTagInheritanceMode = deserializeTagInheritanceMode(json,
BLOCK_TAG_INHERITANCE_MODE);
Map<ResourceLocation, Set<Pattern>> blockTagInheritance = deserializePatternsForLocations(json,
BLOCK_TAG_INHERITANCE);
Set<UnifyTag<Item>> ignoredTags = safeGet(() -> JsonUtils Set<UnifyTag<Item>> ignoredTags = safeGet(() -> JsonUtils
.toList(json.getAsJsonArray(IGNORED_TAGS)) .toList(json.getAsJsonArray(IGNORED_TAGS))
.stream() .stream()
@ -206,6 +299,10 @@ public class UnifyConfig extends Config {
materials, materials,
priorityOverrides, priorityOverrides,
tagOwnerships, tagOwnerships,
itemTagInheritanceMode,
itemTagInheritance,
blockTagInheritanceMode,
blockTagInheritance,
ignoredTags, ignoredTags,
ignoredItems, ignoredItems,
ignoredRecipeTypes, ignoredRecipeTypes,
@ -214,6 +311,43 @@ public class UnifyConfig extends Config {
); );
} }
private TagInheritanceMode deserializeTagInheritanceMode(JsonObject json, String key) {
return safeGet(() -> TagInheritanceMode.valueOf(json
.getAsJsonPrimitive(key)
.getAsString().toUpperCase()), TagInheritanceMode.ALLOW);
}
/**
* Deserializes a list of patterns from a json object with a base key. Example json:
* <pre>
* {
* "baseKey": {
* "location1": [ pattern1, pattern2 ],
* "location2": [ pattern3, pattern4 ]
* }
* }
* </pre>
*
* @param rawConfigJson The raw config json
* @param baseKey The base key
* @return The deserialized patterns separated by location
*/
private Map<ResourceLocation, Set<Pattern>> unsafeDeserializePatternsForLocations(JsonObject rawConfigJson, String baseKey) {
JsonObject json = rawConfigJson.getAsJsonObject(baseKey);
return json
.keySet()
.stream()
.collect(Collectors.toMap(
ResourceLocation::new,
key -> deserializePatterns(json, key, List.of()),
(a, b) -> b,
HashMap::new));
}
private Map<ResourceLocation, Set<Pattern>> deserializePatternsForLocations(JsonObject rawConfigJson, String baseKey) {
return safeGet(() -> unsafeDeserializePatternsForLocations(rawConfigJson, baseKey), new HashMap<>());
}
@Override @Override
public JsonObject serialize(UnifyConfig config) { public JsonObject serialize(UnifyConfig config) {
JsonObject json = new JsonObject(); JsonObject json = new JsonObject();
@ -232,6 +366,20 @@ public class UnifyConfig extends Config {
JsonUtils.toArray(child.stream().map(ResourceLocation::toString).toList())); JsonUtils.toArray(child.stream().map(ResourceLocation::toString).toList()));
}); });
json.add(TAG_OWNERSHIPS, tagOwnerships); json.add(TAG_OWNERSHIPS, tagOwnerships);
JsonObject itemTagInheritance = new JsonObject();
config.itemTagInheritance.forEach((tag, patterns) -> {
itemTagInheritance.add(tag.toString(),
JsonUtils.toArray(patterns.stream().map(Pattern::toString).toList()));
});
json.add(ITEM_TAG_INHERITANCE_MODE, new JsonPrimitive(config.itemTagInheritanceMode.toString()));
json.add(ITEM_TAG_INHERITANCE, itemTagInheritance);
JsonObject blockTagInheritance = new JsonObject();
config.blockTagInheritance.forEach((tag, patterns) -> {
blockTagInheritance.add(tag.toString(),
JsonUtils.toArray(patterns.stream().map(Pattern::toString).toList()));
});
json.add(BLOCK_TAG_INHERITANCE_MODE, new JsonPrimitive(config.blockTagInheritanceMode.toString()));
json.add(BLOCK_TAG_INHERITANCE, blockTagInheritance);
json.add(IGNORED_TAGS, json.add(IGNORED_TAGS,
JsonUtils.toArray(config.ignoredTags JsonUtils.toArray(config.ignoredTags
.stream() .stream()
@ -245,4 +393,9 @@ public class UnifyConfig extends Config {
return json; return json;
} }
} }
public enum TagInheritanceMode {
ALLOW,
DENY
}
} }

View file

@ -1,11 +1,13 @@
package com.almostreliable.unified.mixin.runtime; package com.almostreliable.unified.mixin.runtime;
import com.almostreliable.unified.AlmostUnified; import com.almostreliable.unified.AlmostUnified;
import com.almostreliable.unified.utils.TagReloadHandler;
import com.almostreliable.unified.utils.Utils; import com.almostreliable.unified.utils.Utils;
import net.minecraft.core.Holder; import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagLoader; import net.minecraft.tags.TagLoader;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
@ -27,7 +29,17 @@ public class TagLoaderMixin {
if (directory.equals("tags/items")) { if (directory.equals("tags/items")) {
try { try {
Map<ResourceLocation, Collection<Holder<Item>>> tags = Utils.cast(cir.getReturnValue()); Map<ResourceLocation, Collection<Holder<Item>>> tags = Utils.cast(cir.getReturnValue());
AlmostUnified.onTagLoaderReload(tags); TagReloadHandler.initItemTags(tags);
TagReloadHandler.run();
} catch (Exception e) {
AlmostUnified.LOG.error(e.getMessage(), e);
}
}
if (directory.equals("tags/blocks")) {
try {
Map<ResourceLocation, Collection<Holder<Block>>> tags = Utils.cast(cir.getReturnValue());
TagReloadHandler.initBlockTags(tags);
TagReloadHandler.run();
} catch (Exception e) { } catch (Exception e) {
AlmostUnified.LOG.error(e.getMessage(), e); AlmostUnified.LOG.error(e.getMessage(), e);
} }

View file

@ -1,23 +0,0 @@
package com.almostreliable.unified.mixin.runtime;
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<CompletableFuture<Void>> cir) {
AlmostUnified.onTagManagerReload((TagManager) (Object) this);
}
}

View file

@ -18,12 +18,12 @@ import java.util.function.Predicate;
public class ReplacementMap { public class ReplacementMap {
private final UnifyConfig unifyConfig; private final UnifyConfig unifyConfig;
private final TagMap tagMap; private final TagMap<Item> tagMap;
private final StoneStrataHandler stoneStrataHandler; private final StoneStrataHandler stoneStrataHandler;
private final TagOwnerships tagOwnerships; private final TagOwnerships tagOwnerships;
private final Set<ResourceLocation> warnings; private final Set<ResourceLocation> warnings;
public ReplacementMap(UnifyConfig unifyConfig, TagMap tagMap, StoneStrataHandler stoneStrataHandler, TagOwnerships tagOwnerships) { public ReplacementMap(UnifyConfig unifyConfig, TagMap<Item> tagMap, StoneStrataHandler stoneStrataHandler, TagOwnerships tagOwnerships) {
this.tagMap = tagMap; this.tagMap = tagMap;
this.unifyConfig = unifyConfig; this.unifyConfig = unifyConfig;
this.stoneStrataHandler = stoneStrataHandler; this.stoneStrataHandler = stoneStrataHandler;
@ -33,7 +33,7 @@ public class ReplacementMap {
@Nullable @Nullable
public UnifyTag<Item> getPreferredTagForItem(ResourceLocation item) { public UnifyTag<Item> getPreferredTagForItem(ResourceLocation item) {
Collection<UnifyTag<Item>> tags = tagMap.getTagsByItem(item); Collection<UnifyTag<Item>> tags = tagMap.getTagsByEntry(item);
if (tags.isEmpty()) { if (tags.isEmpty()) {
return null; return null;
@ -72,7 +72,7 @@ public class ReplacementMap {
if (tagToLookup == null) tagToLookup = tag; if (tagToLookup == null) tagToLookup = tag;
List<ResourceLocation> items = tagMap List<ResourceLocation> items = tagMap
.getItemsByTag(tagToLookup) .getEntriesByTag(tagToLookup)
.stream() .stream()
.filter(itemFilter) .filter(itemFilter)
// Helps us to get the clean stone variant first in case of a stone strata tag // Helps us to get the clean stone variant first in case of a stone strata tag

View file

@ -1,6 +1,7 @@
package com.almostreliable.unified.utils; package com.almostreliable.unified.utils;
import net.minecraft.core.Holder; import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries; import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceKey;
@ -8,31 +9,32 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey; import net.minecraft.tags.TagKey;
import net.minecraft.tags.TagLoader; import net.minecraft.tags.TagLoader;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import java.util.*; import java.util.*;
import java.util.function.Predicate; import java.util.function.Predicate;
public class TagMap { public class TagMap<T> {
private final Map<UnifyTag<Item>, Set<ResourceLocation>> tagsToItems = new HashMap<>(); private final Map<UnifyTag<T>, Set<ResourceLocation>> tagsToEntries = new HashMap<>();
private final Map<ResourceLocation, Set<UnifyTag<Item>>> itemsToTags = new HashMap<>(); private final Map<ResourceLocation, Set<UnifyTag<T>>> entriesToTags = new HashMap<>();
protected TagMap() {} protected TagMap() {}
/** /**
* Creates a tag map from a set of unify tags. * Creates an item tag map from a set of item unify tags.
* <p> * <p>
* This should only be used for client-side tag maps or for tests.<br> * This should only be used for client-side tag maps or for tests.<br>
* It requires the registry to be loaded in order to validate the tags * It requires the registry to be loaded in order to validate the tags
* and fetch the holder from it. * and fetch the holder from it.
* <p> * <p>
* For the server, use {@link #create(Map)} instead. * For the server, use {@link #createFromItemTags(Map)} instead.
* *
* @param unifyTags The unify tags. * @param unifyTags The unify tags.
* @return A new tag map. * @return A new tag map.
*/ */
public static TagMap create(Set<UnifyTag<Item>> unifyTags) { public static TagMap<Item> create(Set<UnifyTag<Item>> unifyTags) {
TagMap tagMap = new TagMap(); TagMap<Item> tagMap = new TagMap<>();
unifyTags.forEach(ut -> { unifyTags.forEach(ut -> {
TagKey<Item> asTagKey = TagKey.create(Registries.ITEM, ut.location()); TagKey<Item> asTagKey = TagKey.create(Registries.ITEM, ut.location());
@ -46,84 +48,115 @@ public class TagMap {
} }
/** /**
* Creates a tag map from the vanilla tag collection passed by the {@link TagLoader}. * Creates an item tag map from the vanilla item tag collection passed by the {@link TagLoader}.
* <p> * <p>
* This should only be used on the server.<br> * This should only be used on the server.<br>
* This tag map should later be filtered by using {@link #filtered(Predicate, Predicate)}. * This tag map should later be filtered by using {@link #filtered(Predicate, Predicate)}.
* <p> * <p>
* For the client, use {@link #create(Set)} instead. * For the client, use {@link #create(Set)} instead.
* *
* @param tags The vanilla tag collection. * @param tags The vanilla item tag collection.
* @return A new tag map. * @return A new item tag map.
*/ */
public static TagMap create(Map<ResourceLocation, Collection<Holder<Item>>> tags) { public static TagMap<Item> createFromItemTags(Map<ResourceLocation, Collection<Holder<Item>>> tags) {
TagMap tagMap = new TagMap(); TagMap<Item> tagMap = new TagMap<>();
for (var entry : tags.entrySet()) { for (var entry : tags.entrySet()) {
UnifyTag<Item> unifyTag = UnifyTag.item(entry.getKey()); UnifyTag<Item> unifyTag = UnifyTag.item(entry.getKey());
for (Holder<?> holder : entry.getValue()) { fillEntries(tagMap, entry.getValue(), unifyTag, BuiltInRegistries.ITEM);
holder
.unwrapKey()
.map(ResourceKey::location)
.filter(BuiltInRegistries.ITEM::containsKey)
.ifPresent(itemId -> tagMap.put(unifyTag, itemId));
}
} }
return tagMap; return tagMap;
} }
/**
* Creates a block tag map from the vanilla block tag collection passed by the {@link TagLoader}.
* <p>
* This should only be used on the server.
*
* @param tags The vanilla block tag collection.
* @return A new block tag map.
*/
public static TagMap<Block> createFromBlockTags(Map<ResourceLocation, Collection<Holder<Block>>> tags) {
TagMap<Block> tagMap = new TagMap<>();
for (var entry : tags.entrySet()) {
UnifyTag<Block> unifyTag = UnifyTag.block(entry.getKey());
fillEntries(tagMap, entry.getValue(), unifyTag, BuiltInRegistries.BLOCK);
}
return tagMap;
}
/**
* Unwrap all holders, verify them and put them into the tag map.
*
* @param tagMap The tag map to fill.
* @param holders The holders to unwrap.
* @param unifyTag The unify tag to use.
* @param registry The registry to use.
*/
private static <T> void fillEntries(TagMap<T> tagMap, Collection<Holder<T>> holders, UnifyTag<T> unifyTag, Registry<T> registry) {
for (var holder : holders) {
holder
.unwrapKey()
.map(ResourceKey::location)
.filter(registry::containsKey)
.ifPresent(id -> tagMap.put(unifyTag, id));
}
}
/** /**
* Creates a filtered tag map copy. * Creates a filtered tag map copy.
* *
* @param tagFilter A filter to determine which tags to include. * @param tagFilter A filter to determine which tags to include.
* @param itemFilter A filter to determine which items to include. * @param entryFilter A filter to determine which entries to include.
* @return A filtered copy of this tag map. * @return A filtered copy of this tag map.
*/ */
public TagMap filtered(Predicate<UnifyTag<Item>> tagFilter, Predicate<ResourceLocation> itemFilter) { public TagMap<T> filtered(Predicate<UnifyTag<T>> tagFilter, Predicate<ResourceLocation> entryFilter) {
TagMap tagMap = new TagMap(); TagMap<T> tagMap = new TagMap<>();
tagsToItems.forEach((tag, items) -> { tagsToEntries.forEach((tag, items) -> {
if (!tagFilter.test(tag)) { if (!tagFilter.test(tag)) {
return; return;
} }
items.stream().filter(itemFilter).forEach(item -> tagMap.put(tag, item)); items.stream().filter(entryFilter).forEach(item -> tagMap.put(tag, item));
}); });
return tagMap; return tagMap;
} }
public int tagSize() { public int tagSize() {
return tagsToItems.size(); return tagsToEntries.size();
} }
public int itemSize() { public int itemSize() {
return itemsToTags.size(); return entriesToTags.size();
} }
public Set<ResourceLocation> getItemsByTag(UnifyTag<Item> tag) { public Set<ResourceLocation> getEntriesByTag(UnifyTag<T> tag) {
return Collections.unmodifiableSet(tagsToItems.getOrDefault(tag, Collections.emptySet())); return Collections.unmodifiableSet(tagsToEntries.getOrDefault(tag, Collections.emptySet()));
} }
public Set<UnifyTag<Item>> getTagsByItem(ResourceLocation items) { public Set<UnifyTag<T>> getTagsByEntry(ResourceLocation entry) {
return Collections.unmodifiableSet(itemsToTags.getOrDefault(items, Collections.emptySet())); return Collections.unmodifiableSet(entriesToTags.getOrDefault(entry, Collections.emptySet()));
} }
public Set<UnifyTag<Item>> getTags() { public Set<UnifyTag<T>> getTags() {
return Collections.unmodifiableSet(tagsToItems.keySet()); return Collections.unmodifiableSet(tagsToEntries.keySet());
} }
/** /**
* Helper function to build a relationship between a tag and an item. * Helper function to build a relationship between a tag and an entry.
* <p> * <p>
* If the entries don't exist in the internal maps yet, they will be created. That means * 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. * it needs to be checked whether the tag or entry is valid before calling this method.
* *
* @param tag The tag. * @param tag The tag.
* @param item The item. * @param entry The entry.
*/ */
protected void put(UnifyTag<Item> tag, ResourceLocation item) { protected void put(UnifyTag<T> tag, ResourceLocation entry) {
tagsToItems.computeIfAbsent(tag, k -> new HashSet<>()).add(item); tagsToEntries.computeIfAbsent(tag, k -> new HashSet<>()).add(entry);
itemsToTags.computeIfAbsent(item, k -> new HashSet<>()).add(tag); entriesToTags.computeIfAbsent(entry, k -> new HashSet<>()).add(tag);
} }
} }

View file

@ -0,0 +1,217 @@
package com.almostreliable.unified.utils;
import com.almostreliable.unified.AlmostUnified;
import com.almostreliable.unified.ReplacementData;
import com.almostreliable.unified.config.UnifyConfig;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public final class TagReloadHandler {
private static final Object LOCK = new Object();
private static Map<ResourceLocation, Collection<Holder<Item>>> RAW_ITEM_TAGS;
private static Map<ResourceLocation, Collection<Holder<Block>>> RAW_BLOCK_TAGS;
private TagReloadHandler() {}
public static void initItemTags(Map<ResourceLocation, Collection<Holder<Item>>> rawItemTags) {
synchronized (LOCK) {
RAW_ITEM_TAGS = rawItemTags;
}
}
public static void initBlockTags(Map<ResourceLocation, Collection<Holder<Block>>> rawBlockTags) {
synchronized (LOCK) {
RAW_BLOCK_TAGS = rawBlockTags;
}
}
public static void run() {
if (RAW_ITEM_TAGS == null || RAW_BLOCK_TAGS == null) {
return;
}
AlmostUnified.onTagLoaderReload(RAW_ITEM_TAGS);
RAW_ITEM_TAGS = null;
RAW_BLOCK_TAGS = null;
}
public static boolean applyInheritance(UnifyConfig unifyConfig, ReplacementData replacementData) {
Preconditions.checkNotNull(RAW_ITEM_TAGS, "Item tags were not loaded correctly");
Preconditions.checkNotNull(RAW_BLOCK_TAGS, "Block tags were not loaded correctly");
Multimap<ResourceLocation, ResourceLocation> changedItemTags = HashMultimap.create();
Multimap<ResourceLocation, ResourceLocation> changedBlockTags = HashMultimap.create();
var relations = resolveRelations(replacementData.filteredTagMap(), replacementData.replacementMap());
if (relations.isEmpty()) return false;
var blockTagMap = TagMap.createFromBlockTags(RAW_BLOCK_TAGS);
var globalTagMap = replacementData.globalTagMap();
for (TagRelation relation : relations) {
var dominant = relation.dominant;
var dominantItemHolder = findDominantItemHolder(relation);
var dominantBlockHolder = findDominantBlockHolder(blockTagMap, dominant);
var dominantItemTags = globalTagMap.getTagsByEntry(dominant);
for (var item : relation.items) {
if (dominantItemHolder != null) {
var changed = applyItemTags(unifyConfig, globalTagMap, dominantItemHolder, dominantItemTags, item);
changedItemTags.putAll(dominant, changed);
}
if (dominantBlockHolder != null) {
var changed = applyBlockTags(unifyConfig, blockTagMap, dominantBlockHolder, dominantItemTags, item);
changedBlockTags.putAll(dominant, changed);
}
}
}
if (!changedBlockTags.isEmpty()) {
changedBlockTags.asMap().forEach((dominant, tags) -> {
AlmostUnified.LOG.info("[TagInheritance] Added '{}' to block tags {}", dominant, tags);
});
}
if (!changedItemTags.isEmpty()) {
changedItemTags.asMap().forEach((dominant, tags) -> {
AlmostUnified.LOG.info("[TagInheritance] Added '{}' to item tags {}", dominant, tags);
});
return true;
}
return false;
}
private static Set<TagRelation> resolveRelations(TagMap<Item> filteredTagMap, ReplacementMap repMap) {
Set<TagRelation> relations = new HashSet<>();
for (var unifyTag : filteredTagMap.getTags()) {
var itemsByTag = filteredTagMap.getEntriesByTag(unifyTag);
// avoid handling single entries and tags that only contain the same namespace for all items
if (Utils.allSameNamespace(itemsByTag)) continue;
ResourceLocation dominant = repMap.getPreferredItemForTag(unifyTag, $ -> true);
if (dominant == null || !BuiltInRegistries.ITEM.containsKey(dominant)) continue;
Set<ResourceLocation> items = getValidatedItems(itemsByTag, dominant);
if (items.isEmpty()) continue;
relations.add(new TagRelation(unifyTag.location(), dominant, items));
}
return relations;
}
/**
* Returns a set of all items that are not the dominant item and are valid by checking if they are registered.
*
* @param itemIds The set of all items that are in the tag
* @param dominant The dominant item
* @return A set of all items that are not the dominant item and are valid
*/
private static Set<ResourceLocation> getValidatedItems(Set<ResourceLocation> itemIds, ResourceLocation dominant) {
Set<ResourceLocation> result = new HashSet<>(itemIds.size());
for (ResourceLocation id : itemIds) {
if (!id.equals(dominant) && BuiltInRegistries.ITEM.containsKey(id)) {
result.add(id);
}
}
return result;
}
@SuppressWarnings("StaticVariableUsedBeforeInitialization")
@Nullable
private static Holder<Item> findDominantItemHolder(TagRelation relation) {
var tagHolders = RAW_ITEM_TAGS.get(relation.tag);
if (tagHolders == null) return null;
return findDominantHolder(tagHolders, relation.dominant);
}
@SuppressWarnings("StaticVariableUsedBeforeInitialization")
@Nullable
private static Holder<Block> findDominantBlockHolder(TagMap<Block> tagMap, ResourceLocation dominant) {
var blockTags = tagMap.getTagsByEntry(dominant);
if (blockTags.isEmpty()) return null;
var tagHolders = RAW_BLOCK_TAGS.get(blockTags.iterator().next().location());
if (tagHolders == null) return null;
return findDominantHolder(tagHolders, dominant);
}
@Nullable
private static <T> Holder<T> findDominantHolder(Collection<Holder<T>> holders, ResourceLocation dominant) {
for (var tagHolder : holders) {
var holderKey = tagHolder.unwrapKey();
if (holderKey.isPresent() && holderKey.get().location().equals(dominant)) {
return tagHolder;
}
}
return null;
}
private static Set<ResourceLocation> applyItemTags(UnifyConfig unifyConfig, TagMap<Item> globalTagMap, Holder<Item> dominantItemHolder, Set<UnifyTag<Item>> dominantItemTags, ResourceLocation item) {
var itemTags = globalTagMap.getTagsByEntry(item);
Set<ResourceLocation> changed = new HashSet<>();
for (var itemTag : itemTags) {
if (!unifyConfig.shouldInheritItemTag(itemTag, dominantItemTags)) continue;
if (tryUpdatingRawTags(dominantItemHolder, itemTag, RAW_ITEM_TAGS)) {
changed.add(itemTag.location());
}
}
return changed;
}
private static Set<ResourceLocation> applyBlockTags(UnifyConfig unifyConfig, TagMap<Block> blockTagMap, Holder<Block> dominantBlockHolder, Set<UnifyTag<Item>> dominantItemTags, ResourceLocation item) {
var blockTags = blockTagMap.getTagsByEntry(item);
Set<ResourceLocation> changed = new HashSet<>();
for (var blockTag : blockTags) {
if (!unifyConfig.shouldInheritBlockTag(blockTag, dominantItemTags)) continue;
if (tryUpdatingRawTags(dominantBlockHolder, blockTag, RAW_BLOCK_TAGS)) {
changed.add(blockTag.location());
}
}
return changed;
}
private static <T> boolean tryUpdatingRawTags(Holder<T> dominantHolder, UnifyTag<T> tag, Map<ResourceLocation, Collection<Holder<T>>> rawTags) {
var tagHolders = rawTags.get(tag.location());
if (tagHolders == null) return false;
if (tagHolders.contains(dominantHolder)) return false; // already present, no need to add it again
ImmutableSet.Builder<Holder<T>> newHolders = ImmutableSet.builder();
newHolders.addAll(tagHolders);
newHolders.add(dominantHolder);
rawTags.put(tag.location(), newHolders.build());
return true;
}
private record TagRelation(ResourceLocation tag, ResourceLocation dominant, Set<ResourceLocation> items) {}
}

View file

@ -2,12 +2,17 @@ package com.almostreliable.unified.utils;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
public record UnifyTag<T>(Class<T> boundType, ResourceLocation location) { public record UnifyTag<T>(Class<T> boundType, ResourceLocation location) {
public static UnifyTag<Item> item(ResourceLocation location) { public static UnifyTag<Item> item(ResourceLocation location) {
return new UnifyTag<>(Item.class, location); return new UnifyTag<>(Item.class, location);
} }
public static UnifyTag<Block> block(ResourceLocation location) {
return new UnifyTag<>(Block.class, location);
}
@Override @Override
public String toString() { public String toString() {
return "UnifyTag[" + boundType.getSimpleName().toLowerCase() + " / " + location + "]"; return "UnifyTag[" + boundType.getSimpleName().toLowerCase() + " / " + location + "]";

View file

@ -5,6 +5,7 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Set;
public final class Utils { public final class Utils {
public static final ResourceLocation UNUSED_ID = new ResourceLocation(BuildConfig.MOD_ID, "unused_id"); public static final ResourceLocation UNUSED_ID = new ResourceLocation(BuildConfig.MOD_ID, "unused_id");
@ -37,4 +38,23 @@ public final class Utils {
public static String prefix(String path) { public static String prefix(String path) {
return BuildConfig.MOD_ID + "." + path; return BuildConfig.MOD_ID + "." + path;
} }
/**
* Checks if all ids have the same namespace
*
* @param ids set of ids
* @return true if all ids have the same namespace
*/
public static boolean allSameNamespace(Set<ResourceLocation> ids) {
if (ids.size() <= 1) return true;
var it = ids.iterator();
var namespace = it.next().getNamespace();
while (it.hasNext()) {
if (!it.next().getNamespace().equals(namespace)) return false;
}
return true;
}
} }

View file

@ -7,7 +7,6 @@
"mixins": [ "mixins": [
"runtime.RecipeManagerMixin", "runtime.RecipeManagerMixin",
"runtime.TagLoaderMixin", "runtime.TagLoaderMixin",
"runtime.TagManagerMixin",
"unifier.ArmorItemMixin", "unifier.ArmorItemMixin",
"unifier.TieredItemMixin" "unifier.TieredItemMixin"
], ],

View file

@ -42,6 +42,10 @@ public final class TestUtils {
TEST_MOD_PRIORITIES, TEST_MOD_PRIORITIES,
new HashMap<>(), new HashMap<>(),
new HashMap<>(), new HashMap<>(),
UnifyConfig.TagInheritanceMode.ALLOW,
new HashMap<>(),
UnifyConfig.TagInheritanceMode.ALLOW,
new HashMap<>(),
new HashSet<>(), new HashSet<>(),
new HashSet<>(), new HashSet<>(),
new HashSet<>(), new HashSet<>(),

View file

@ -9,8 +9,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
public class TagMapTests { public class TagMapTests {
public static TagMap testTagMap() { public static TagMap<Item> testTagMap() {
TagMap tagMap = new TagMap(); TagMap<Item> tagMap = new TagMap<>();
UnifyTag<Item> bronzeOreTag = UnifyTag.item(new ResourceLocation("forge:ores/bronze")); UnifyTag<Item> bronzeOreTag = UnifyTag.item(new ResourceLocation("forge:ores/bronze"));
UnifyTag<Item> invarOreTag = UnifyTag.item(new ResourceLocation("forge:ores/invar")); UnifyTag<Item> invarOreTag = UnifyTag.item(new ResourceLocation("forge:ores/invar"));
UnifyTag<Item> tinOreTag = UnifyTag.item(new ResourceLocation("forge:ores/tin")); UnifyTag<Item> tinOreTag = UnifyTag.item(new ResourceLocation("forge:ores/tin"));
@ -36,7 +36,7 @@ public class TagMapTests {
@Test @Test
public void simpleCheck() { public void simpleCheck() {
TagMap tagMap = new TagMap(); TagMap<Item> tagMap = new TagMap<>();
UnifyTag<Item> bronzeOreTag = UnifyTag.item(new ResourceLocation("forge:ores/bronze")); UnifyTag<Item> bronzeOreTag = UnifyTag.item(new ResourceLocation("forge:ores/bronze"));
tagMap.put(bronzeOreTag, TestUtils.mod1RL("bronze_ore")); tagMap.put(bronzeOreTag, TestUtils.mod1RL("bronze_ore"));
tagMap.put(bronzeOreTag, TestUtils.mod2RL("bronze_ore")); tagMap.put(bronzeOreTag, TestUtils.mod2RL("bronze_ore"));
@ -44,12 +44,12 @@ public class TagMapTests {
tagMap.put(bronzeOreTag, TestUtils.mod4RL("bronze_ore")); tagMap.put(bronzeOreTag, TestUtils.mod4RL("bronze_ore"));
tagMap.put(bronzeOreTag, TestUtils.mod5RL("bronze_ore")); tagMap.put(bronzeOreTag, TestUtils.mod5RL("bronze_ore"));
assertEquals(tagMap.getItemsByTag(bronzeOreTag).size(), 5); assertEquals(tagMap.getEntriesByTag(bronzeOreTag).size(), 5);
assertEquals(tagMap.getTagsByItem(TestUtils.mod1RL("bronze_ore")).size(), 1); assertEquals(tagMap.getTagsByEntry(TestUtils.mod1RL("bronze_ore")).size(), 1);
assertEquals(tagMap.getTagsByItem(TestUtils.mod2RL("bronze_ore")).size(), 1); assertEquals(tagMap.getTagsByEntry(TestUtils.mod2RL("bronze_ore")).size(), 1);
assertEquals(tagMap.getTagsByItem(TestUtils.mod3RL("bronze_ore")).size(), 1); assertEquals(tagMap.getTagsByEntry(TestUtils.mod3RL("bronze_ore")).size(), 1);
assertEquals(tagMap.getTagsByItem(TestUtils.mod4RL("bronze_ore")).size(), 1); assertEquals(tagMap.getTagsByEntry(TestUtils.mod4RL("bronze_ore")).size(), 1);
assertEquals(tagMap.getTagsByItem(TestUtils.mod5RL("bronze_ore")).size(), 1); assertEquals(tagMap.getTagsByEntry(TestUtils.mod5RL("bronze_ore")).size(), 1);
tagMap.put(UnifyTag.item(new ResourceLocation("forge:ores/invar")), TestUtils.mod1RL("invar_ore")); tagMap.put(UnifyTag.item(new ResourceLocation("forge:ores/invar")), TestUtils.mod1RL("invar_ore"));