From e63306e01528ffbd2d1e8217cbcf97285fe346fb Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Mon, 13 Feb 2023 10:30:18 +0100 Subject: [PATCH] Resource Conditions Additions (#2821) * Resource Conditions Additions - Add `registry_contains` condition. Closes #2548. - Make `fabric:load_conditions` appear first in generated JSON objects. - Uniformize implementation a bit. * Update fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataProviderMixin.java Co-authored-by: Juuz <6596629+Juuxel@users.noreply.github.com> * itemsLoaded -> itemsRegistered --------- Co-authored-by: Juuz <6596629+Juuxel@users.noreply.github.com> --- .../datagen/DataGeneratorTestEntrypoint.java | 8 ++ .../v1/DefaultResourceConditions.java | 70 ++++++++++ .../conditions/ResourceConditionsImpl.java | 120 ++++++++++++++---- .../conditions/DataPackContentsMixin.java | 6 +- .../conditions/DataProviderMixin.java | 40 ++++++ ...ric-resource-conditions-api-v1.mixins.json | 1 + 6 files changed, 215 insertions(+), 30 deletions(-) create mode 100644 fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataProviderMixin.java diff --git a/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java b/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java index ee906617b..1d0710713 100644 --- a/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java +++ b/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java @@ -37,6 +37,7 @@ import org.slf4j.LoggerFactory; import net.minecraft.advancement.Advancement; import net.minecraft.advancement.AdvancementFrame; import net.minecraft.advancement.criterion.OnKilledCriterion; +import net.minecraft.block.Blocks; import net.minecraft.registry.RegistryKeys; import net.minecraft.data.client.BlockStateModelGenerator; import net.minecraft.data.client.ItemModelGenerator; @@ -108,6 +109,13 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint { public void generate(Consumer<RecipeJsonProvider> exporter) { offerPlanksRecipe2(exporter, SIMPLE_BLOCK, ItemTags.ACACIA_LOGS, 1); + ShapelessRecipeJsonBuilder.create(RecipeCategory.MISC, Items.LEATHER, 4).input(Items.ITEM_FRAME) + .criterion("has_frame", conditionsFromItem(Items.ITEM_FRAME)) + .offerTo(withConditions(exporter, DefaultResourceConditions.itemsRegistered(Blocks.DIAMOND_BLOCK))); + ShapelessRecipeJsonBuilder.create(RecipeCategory.MISC, Items.LEATHER_BOOTS, 4).input(Items.ITEM_FRAME, 2) + .criterion("has_frame", conditionsFromItem(Items.ITEM_FRAME)) + .offerTo(withConditions(exporter, DefaultResourceConditions.registryContains(BiomeKeys.PLAINS, BiomeKeys.BADLANDS))); + ShapelessRecipeJsonBuilder.create(RecipeCategory.MISC, Items.GOLD_INGOT).input(Items.DIRT).criterion("has_dirt", conditionsFromItem(Items.DIRT)).offerTo(withConditions(exporter, NEVER_LOADED)); ShapelessRecipeJsonBuilder.create(RecipeCategory.MISC, Items.DIAMOND).input(Items.STICK).criterion("has_stick", conditionsFromItem(Items.STICK)).offerTo(withConditions(exporter, ALWAYS_LOADED)); diff --git a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/api/resource/conditions/v1/DefaultResourceConditions.java b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/api/resource/conditions/v1/DefaultResourceConditions.java index 8e5c029df..1a8886cfb 100644 --- a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/api/resource/conditions/v1/DefaultResourceConditions.java +++ b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/api/resource/conditions/v1/DefaultResourceConditions.java @@ -16,12 +16,20 @@ package net.fabricmc.fabric.api.resource.conditions.v1; +import java.util.Arrays; +import java.util.function.Function; + +import com.google.common.base.Preconditions; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import net.minecraft.block.Block; import net.minecraft.fluid.Fluid; import net.minecraft.item.Item; +import net.minecraft.item.ItemConvertible; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; import net.minecraft.registry.RegistryKeys; import net.minecraft.registry.tag.TagKey; import net.minecraft.resource.featuretoggle.FeatureFlag; @@ -44,6 +52,7 @@ public final class DefaultResourceConditions { private static final Identifier ITEM_TAGS_POPULATED = new Identifier("fabric:item_tags_populated"); private static final Identifier TAGS_POPULATED = new Identifier("fabric:tags_populated"); private static final Identifier FEATURES_ENABLED = new Identifier("fabric:features_enabled"); + private static final Identifier REGISTRY_CONTAINS = new Identifier("fabric:registry_contains"); /** * Creates a NOT condition that returns true if its child condition is false, and false if its child is true. @@ -159,6 +168,54 @@ public final class DefaultResourceConditions { return ResourceConditionsImpl.featuresEnabled(FEATURES_ENABLED, features); } + /** + * Creates a condition that returns true if all the passed items are registered (in {@link Registries#ITEM}). + * + * @see #registryContains(RegistryKey, Identifier...) + */ + public static ConditionJsonProvider itemsRegistered(ItemConvertible... items) { + return registryContains(Registries.ITEM, transform(items, ItemConvertible::asItem)); + } + + /** + * Creates a condition that returns true if the registry contains all the passed entries, + * i.e. if all the passed registry entries are loaded. + * + * @see #registryContains(RegistryKey, Identifier...) + */ + @SafeVarargs + public static <T> ConditionJsonProvider registryContains(Registry<T> registry, T... entries) { + return registryContains(transform(entries, e -> { + return registry.getKey(e).orElseThrow(() -> new IllegalArgumentException("Entry is not registered")); + })); + } + + /** + * Creates a condition that returns true if all the passed registry entries are loaded. + * + * @see #registryContains(RegistryKey, Identifier...) + */ + @SafeVarargs + public static <T> ConditionJsonProvider registryContains(RegistryKey<T>... entries) { + Preconditions.checkArgument(entries.length > 0, "Must register at least one entry."); + + return registryContains( + RegistryKey.ofRegistry(entries[0].getRegistry()), + transform(entries, RegistryKey::getValue)); + } + + /** + * Creates a condition that returns true if all the passed registry entries are loaded. + * Dynamic registries are supported for server resources. + * + * @apiNote This condition's ID is {@code fabric:registry_contains}, and takes up to two properties: + * {@code values}, which is an array of string registry entry IDs, and {@code registry}, which is the ID of + * the registry of the entries. If {@code registry} is not provided, it defaults to {@code minecraft:item}. + */ + public static <T> ConditionJsonProvider registryContains(RegistryKey<Registry<T>> registry, Identifier... entries) { + return ResourceConditionsImpl.registryContains(REGISTRY_CONTAINS, registry.getValue(), entries); + } + static void init() { // init static } @@ -183,6 +240,19 @@ public final class DefaultResourceConditions { ResourceConditions.register(ITEM_TAGS_POPULATED, object -> ResourceConditionsImpl.tagsPopulatedMatch(object, RegistryKeys.ITEM)); ResourceConditions.register(TAGS_POPULATED, ResourceConditionsImpl::tagsPopulatedMatch); ResourceConditions.register(FEATURES_ENABLED, ResourceConditionsImpl::featuresEnabledMatch); + ResourceConditions.register(REGISTRY_CONTAINS, ResourceConditionsImpl::registryContainsMatch); + } + + // Slightly gross - the empty outputType vararg is used to capture the correct type for B[] + @SafeVarargs + private static <A, B> B[] transform(A[] input, Function<A, B> mapper, B... outputType) { + B[] output = Arrays.copyOf(outputType, input.length); + + for (int i = 0; i < input.length; i++) { + output[i] = mapper.apply(input[i]); + } + + return output; } private DefaultResourceConditions() { diff --git a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/impl/resource/conditions/ResourceConditionsImpl.java b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/impl/resource/conditions/ResourceConditionsImpl.java index 0e345c289..315725e08 100644 --- a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/impl/resource/conditions/ResourceConditionsImpl.java +++ b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/impl/resource/conditions/ResourceConditionsImpl.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.TreeSet; @@ -32,6 +33,7 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.minecraft.registry.DynamicRegistryManager; import net.minecraft.registry.Registry; import net.minecraft.registry.RegistryKey; import net.minecraft.registry.RegistryKeys; @@ -126,6 +128,55 @@ public final class ResourceConditionsImpl { }; } + public static ConditionJsonProvider featuresEnabled(Identifier id, final FeatureFlag... features) { + final Set<Identifier> ids = new TreeSet<>(FeatureFlags.FEATURE_MANAGER.toId(FeatureFlags.FEATURE_MANAGER.featureSetOf(features))); + + return new ConditionJsonProvider() { + @Override + public Identifier getConditionId() { + return id; + } + + @Override + public void writeParameters(JsonObject object) { + JsonArray array = new JsonArray(); + + for (Identifier id : ids) { + array.add(id.toString()); + } + + object.add("features", array); + } + }; + } + + public static ConditionJsonProvider registryContains(Identifier id, Identifier registry, Identifier... entries) { + Preconditions.checkArgument(entries.length > 0, "Must register at least one entry."); + + return new ConditionJsonProvider() { + @Override + public Identifier getConditionId() { + return id; + } + + @Override + public void writeParameters(JsonObject object) { + JsonArray array = new JsonArray(); + + for (Identifier entry : entries) { + array.add(entry.toString()); + } + + object.add("values", array); + + if (!RegistryKeys.ITEM.getValue().equals(registry)) { + // Skip if this is the default (minecraft:item) + object.addProperty("registry", registry.toString()); + } + } + }; + } + // Condition implementations public static boolean modsLoadedMatch(JsonObject object, boolean and) { @@ -165,10 +216,6 @@ public final class ResourceConditionsImpl { LOADED_TAGS.set(tagMap); } - public static void clearTags() { - LOADED_TAGS.remove(); - } - public static boolean tagsPopulatedMatch(JsonObject object) { String key = JsonHelper.getString(object, "registry", "minecraft:item"); RegistryKey<? extends Registry<?>> registryRef = RegistryKey.ofRegistry(new Identifier(key)); @@ -208,29 +255,7 @@ public final class ResourceConditionsImpl { return true; } - public static ConditionJsonProvider featuresEnabled(Identifier id, final FeatureFlag... features) { - final Set<Identifier> ids = new TreeSet<>(FeatureFlags.FEATURE_MANAGER.toId(FeatureFlags.FEATURE_MANAGER.featureSetOf(features))); - - return new ConditionJsonProvider() { - @Override - public Identifier getConditionId() { - return id; - } - - @Override - public void writeParameters(JsonObject object) { - JsonArray array = new JsonArray(); - - for (Identifier id : ids) { - array.add(id.toString()); - } - - object.add("features", array); - } - }; - } - - public static ThreadLocal<FeatureSet> currentFeature = ThreadLocal.withInitial(() -> FeatureFlags.DEFAULT_ENABLED_FEATURES); + public static final ThreadLocal<FeatureSet> CURRENT_FEATURES = ThreadLocal.withInitial(() -> FeatureFlags.DEFAULT_ENABLED_FEATURES); public static boolean featuresEnabledMatch(JsonObject object) { List<Identifier> featureIds = JsonHelper.getArray(object, "features").asList().stream().map((element) -> new Identifier(element.getAsString())).toList(); @@ -238,6 +263,45 @@ public final class ResourceConditionsImpl { throw new JsonParseException("Unknown feature flag: " + id); }); - return set.isSubsetOf(currentFeature.get()); + return set.isSubsetOf(CURRENT_FEATURES.get()); + } + + public static final ThreadLocal<DynamicRegistryManager.Immutable> CURRENT_REGISTRIES = new ThreadLocal<>(); + + public static boolean registryContainsMatch(JsonObject object) { + String key = JsonHelper.getString(object, "registry", "minecraft:item"); + RegistryKey<? extends Registry<?>> registryRef = RegistryKey.ofRegistry(new Identifier(key)); + return registryContainsMatch(object, registryRef); + } + + private static <E> boolean registryContainsMatch(JsonObject object, RegistryKey<? extends Registry<? extends E>> registryRef) { + JsonArray array = JsonHelper.getArray(object, "values"); + DynamicRegistryManager.Immutable registries = CURRENT_REGISTRIES.get(); + + if (registries == null) { + LOGGER.warn("Can't retrieve current registries. Failing registry_contains resource condition check."); + return false; + } + + Optional<Registry<E>> registry = registries.getOptional(registryRef); + + if (registry.isEmpty()) { + // No such registry + return array.isEmpty(); + } + + for (JsonElement element : array) { + if (element.isJsonPrimitive()) { + Identifier id = new Identifier(element.getAsString()); + + if (!registry.get().containsId(id)) { + return false; + } + } else { + throw new JsonParseException("Invalid registry entry id: " + element); + } + } + + return true; } } diff --git a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataPackContentsMixin.java b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataPackContentsMixin.java index 1eb18b40a..dd8314341 100644 --- a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataPackContentsMixin.java +++ b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataPackContentsMixin.java @@ -44,7 +44,8 @@ public class DataPackContentsMixin { at = @At("HEAD") ) public void hookRefresh(DynamicRegistryManager dynamicRegistryManager, CallbackInfo ci) { - ResourceConditionsImpl.clearTags(); + ResourceConditionsImpl.LOADED_TAGS.remove(); + ResourceConditionsImpl.CURRENT_REGISTRIES.remove(); } @Inject( @@ -52,6 +53,7 @@ public class DataPackContentsMixin { at = @At("HEAD") ) private static void hookReload(ResourceManager manager, DynamicRegistryManager.Immutable dynamicRegistryManager, FeatureSet enabledFeatures, CommandManager.RegistrationEnvironment environment, int functionPermissionLevel, Executor prepareExecutor, Executor applyExecutor, CallbackInfoReturnable<CompletableFuture<DataPackContents>> cir) { - ResourceConditionsImpl.currentFeature.set(enabledFeatures); + ResourceConditionsImpl.CURRENT_FEATURES.set(enabledFeatures); + ResourceConditionsImpl.CURRENT_REGISTRIES.set(dynamicRegistryManager); } } diff --git a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataProviderMixin.java b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataProviderMixin.java new file mode 100644 index 000000000..c3c42292a --- /dev/null +++ b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataProviderMixin.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.resource.conditions; + +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.spongepowered.asm.mixin.Dynamic; +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.CallbackInfo; + +import net.minecraft.data.DataProvider; + +import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions; + +/** + * Make the {@value ResourceConditions#CONDITIONS_KEY} appear first in generated JSON objects. + */ +@Mixin(DataProvider.class) +public interface DataProviderMixin { + @Dynamic("lambda method passed to Util.make") + @Inject(method = "method_43808", at = @At("HEAD")) + private static void fabric_injectResourceConditionsSortOrder(Object2IntOpenHashMap<String> map, CallbackInfo ci) { + map.put(ResourceConditions.CONDITIONS_KEY, -100); + } +} diff --git a/fabric-resource-conditions-api-v1/src/main/resources/fabric-resource-conditions-api-v1.mixins.json b/fabric-resource-conditions-api-v1/src/main/resources/fabric-resource-conditions-api-v1.mixins.json index 1d03936d4..a77c8f7e2 100644 --- a/fabric-resource-conditions-api-v1/src/main/resources/fabric-resource-conditions-api-v1.mixins.json +++ b/fabric-resource-conditions-api-v1/src/main/resources/fabric-resource-conditions-api-v1.mixins.json @@ -4,6 +4,7 @@ "compatibilityLevel": "JAVA_16", "mixins": [ "DataPackContentsMixin", + "DataProviderMixin", "JsonDataLoaderMixin", "SinglePreparationResourceReloaderMixin", "TagManagerLoaderMixin"