Resource Conditions Additions ()

* Resource Conditions Additions

- Add `registry_contains` condition. Closes .
- 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>
This commit is contained in:
Technici4n 2023-02-13 10:30:18 +01:00 committed by GitHub
parent 2ff127f537
commit e63306e015
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 215 additions and 30 deletions
fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen
fabric-resource-conditions-api-v1/src/main
java/net/fabricmc/fabric
api/resource/conditions/v1
impl/resource/conditions
mixin/resource/conditions
resources

View file

@ -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));

View file

@ -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() {

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -4,6 +4,7 @@
"compatibilityLevel": "JAVA_16",
"mixins": [
"DataPackContentsMixin",
"DataProviderMixin",
"JsonDataLoaderMixin",
"SinglePreparationResourceReloaderMixin",
"TagManagerLoaderMixin"