mirror of
https://github.com/FabricMC/fabric.git
synced 2025-04-21 03:10:54 -04:00
[1.20.5] Codec-based Resource Conditions and refactors (#3690)
* Initial move to codec-based resource conditions * Move default condition types to DefaultResourceConditionTypes * Move built-in condition types to impl * Run spotlessApply * Move default condition registration to entrypoint in imp * Formatting fixes * Add back package-info.java * Make it compatible with new DFU * checkstyle * Refactor FeaturesEnabledResourceCondition * Pass RegistryWrapper and reduce reliance on statics * Some more improvements * Add back some tests * Add more tests * Add back tags_populated support, clean up code * Some more improvements * add javadocs (wip) * Fix unfixed merge conflict * Javadocs * Fix null condition in addConditions * Allow empty arrays in certain codecs * Move addConditions to datagen impl * Address reviews * add debug log * Store features in normal static field * Fix TagsPopulatedResourceCondition#getType --------- Co-authored-by: Apollo <102649729+Apollounknowndev@users.noreply.github.com>
This commit is contained in:
parent
9d8968f5a3
commit
3b6dc5deb2
39 changed files with 1330 additions and 757 deletions
fabric-data-generation-api-v1/src
main/java/net/fabricmc/fabric
api/datagen/v1
loot
provider
impl/datagen
testmod/java/net/fabricmc/fabric/test/datagen
fabric-resource-conditions-api-v1
build.gradle
src
main
java/net/fabricmc/fabric
api/resource/conditions/v1
ConditionJsonProvider.javaDefaultResourceConditions.javaResourceCondition.javaResourceConditionType.javaResourceConditions.javapackage-info.java
impl/resource/conditions
mixin/resource/conditions
resources
testmod
java/net/fabricmc/fabric/test/resource/conditions
resources
|
@ -20,7 +20,7 @@ import com.google.common.base.Preconditions;
|
|||
|
||||
import net.minecraft.data.server.loottable.BlockLootTableGenerator;
|
||||
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ConditionJsonProvider;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.impl.datagen.loot.ConditionBlockLootTableGenerator;
|
||||
|
||||
/**
|
||||
|
@ -33,7 +33,7 @@ public interface FabricBlockLootTableGenerator {
|
|||
* Return a new generator that applies the specified conditions to any loot table it receives,
|
||||
* and then forwards the loot tables to this generator.
|
||||
*/
|
||||
default BlockLootTableGenerator withConditions(ConditionJsonProvider... conditions) {
|
||||
default BlockLootTableGenerator withConditions(ResourceCondition... conditions) {
|
||||
Preconditions.checkArgument(conditions.length > 0, "Must add at least one condition.");
|
||||
|
||||
return new ConditionBlockLootTableGenerator((BlockLootTableGenerator) this, conditions);
|
||||
|
|
|
@ -40,7 +40,7 @@ import net.minecraft.util.Identifier;
|
|||
|
||||
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
|
||||
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ConditionJsonProvider;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.impl.datagen.FabricDataGenHelper;
|
||||
|
||||
/**
|
||||
|
@ -69,7 +69,7 @@ public abstract class FabricAdvancementProvider implements DataProvider {
|
|||
/**
|
||||
* Return a new exporter that applies the specified conditions to any advancement it receives.
|
||||
*/
|
||||
protected Consumer<AdvancementEntry> withConditions(Consumer<AdvancementEntry> exporter, ConditionJsonProvider... conditions) {
|
||||
protected Consumer<AdvancementEntry> withConditions(Consumer<AdvancementEntry> exporter, ResourceCondition... conditions) {
|
||||
Preconditions.checkArgument(conditions.length > 0, "Must add at least one condition.");
|
||||
return advancement -> {
|
||||
FabricDataGenHelper.addConditions(advancement, conditions);
|
||||
|
@ -94,7 +94,7 @@ public abstract class FabricAdvancementProvider implements DataProvider {
|
|||
}
|
||||
|
||||
JsonObject advancementJson = Advancement.CODEC.encodeStart(ops, advancement.value()).getOrThrow(IllegalStateException::new).getAsJsonObject();
|
||||
ConditionJsonProvider.write(advancementJson, FabricDataGenHelper.consumeConditions(advancement));
|
||||
FabricDataGenHelper.addConditions(advancementJson, FabricDataGenHelper.consumeConditions(advancement));
|
||||
futures.add(DataProvider.writeToPath(writer, advancementJson, getOutputPath(advancement)));
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import net.minecraft.loot.LootTable;
|
|||
import net.minecraft.registry.RegistryKey;
|
||||
|
||||
import net.fabricmc.fabric.api.datagen.v1.loot.FabricBlockLootTableGenerator;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ConditionJsonProvider;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.impl.datagen.FabricDataGenHelper;
|
||||
|
||||
/**
|
||||
|
@ -44,7 +44,7 @@ public interface FabricLootTableProvider extends LootTableGenerator, DataProvide
|
|||
*
|
||||
* <p>For block loot tables, use {@link FabricBlockLootTableGenerator#withConditions} instead.
|
||||
*/
|
||||
default BiConsumer<RegistryKey<LootTable>, LootTable.Builder> withConditions(BiConsumer<RegistryKey<LootTable>, LootTable.Builder> exporter, ConditionJsonProvider... conditions) {
|
||||
default BiConsumer<RegistryKey<LootTable>, LootTable.Builder> withConditions(BiConsumer<RegistryKey<LootTable>, LootTable.Builder> exporter, ResourceCondition... conditions) {
|
||||
Preconditions.checkArgument(conditions.length > 0, "Must add at least one condition.");
|
||||
return (id, table) -> {
|
||||
FabricDataGenHelper.addConditions(table, conditions);
|
||||
|
|
|
@ -44,7 +44,7 @@ import net.minecraft.util.Identifier;
|
|||
|
||||
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
|
||||
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ConditionJsonProvider;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.impl.datagen.FabricDataGenHelper;
|
||||
|
||||
/**
|
||||
|
@ -69,7 +69,7 @@ public abstract class FabricRecipeProvider extends RecipeProvider {
|
|||
/**
|
||||
* Return a new exporter that applies the specified conditions to any recipe json provider it receives.
|
||||
*/
|
||||
protected RecipeExporter withConditions(RecipeExporter exporter, ConditionJsonProvider... conditions) {
|
||||
protected RecipeExporter withConditions(RecipeExporter exporter, ResourceCondition... conditions) {
|
||||
Preconditions.checkArgument(conditions.length > 0, "Must add at least one condition.");
|
||||
return new RecipeExporter() {
|
||||
@Override
|
||||
|
@ -100,14 +100,14 @@ public abstract class FabricRecipeProvider extends RecipeProvider {
|
|||
|
||||
RegistryOps<JsonElement> registryOps = wrapperLookup.getOps(JsonOps.INSTANCE);
|
||||
JsonObject recipeJson = Recipe.CODEC.encodeStart(registryOps, recipe).getOrThrow(IllegalStateException::new).getAsJsonObject();
|
||||
ConditionJsonProvider[] conditions = FabricDataGenHelper.consumeConditions(recipe);
|
||||
ConditionJsonProvider.write(recipeJson, conditions);
|
||||
ResourceCondition[] conditions = FabricDataGenHelper.consumeConditions(recipe);
|
||||
FabricDataGenHelper.addConditions(recipeJson, conditions);
|
||||
|
||||
list.add(DataProvider.writeToPath(writer, recipeJson, recipesPathResolver.resolveJson(identifier)));
|
||||
|
||||
if (advancement != null) {
|
||||
JsonObject advancementJson = Advancement.CODEC.encodeStart(registryOps, advancement.value()).getOrThrow(IllegalStateException::new).getAsJsonObject();
|
||||
ConditionJsonProvider.write(advancementJson, conditions);
|
||||
FabricDataGenHelper.addConditions(advancementJson, conditions);
|
||||
list.add(DataProvider.writeToPath(writer, advancementJson, advancementsPathResolver.resolveJson(getRecipeIdentifier(advancement.id()))));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package net.fabricmc.fabric.impl.datagen;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
|
@ -27,7 +28,9 @@ import java.util.Map;
|
|||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
@ -47,7 +50,8 @@ import net.minecraft.util.Util;
|
|||
|
||||
import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint;
|
||||
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ConditionJsonProvider;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;
|
||||
|
@ -215,14 +219,31 @@ public final class FabricDataGenHelper {
|
|||
/**
|
||||
* Used to keep track of conditions associated to generated objects.
|
||||
*/
|
||||
private static final Map<Object, ConditionJsonProvider[]> CONDITIONS_MAP = new IdentityHashMap<>();
|
||||
private static final Map<Object, ResourceCondition[]> CONDITIONS_MAP = new IdentityHashMap<>();
|
||||
|
||||
public static void addConditions(Object object, ConditionJsonProvider[] conditions) {
|
||||
public static void addConditions(Object object, ResourceCondition[] conditions) {
|
||||
CONDITIONS_MAP.merge(object, conditions, ArrayUtils::addAll);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ConditionJsonProvider[] consumeConditions(Object object) {
|
||||
public static ResourceCondition[] consumeConditions(Object object) {
|
||||
return CONDITIONS_MAP.remove(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds {@code conditions} to {@code baseObject}.
|
||||
* @param baseObject the base JSON object to which the conditions are inserted
|
||||
* @param conditions the conditions to insert
|
||||
* @throws IllegalArgumentException if the object already has conditions
|
||||
*/
|
||||
public static void addConditions(JsonObject baseObject, ResourceCondition... conditions) {
|
||||
if (baseObject.has(ResourceConditions.CONDITIONS_KEY)) {
|
||||
throw new IllegalArgumentException("Object already has a condition entry: " + baseObject);
|
||||
} else if (conditions == null || conditions.length == 0) {
|
||||
// Datagen might pass null conditions.
|
||||
return;
|
||||
}
|
||||
|
||||
baseObject.add(ResourceConditions.CONDITIONS_KEY, ResourceCondition.LIST_CODEC.encodeStart(JsonOps.INSTANCE, Arrays.asList(conditions)).getOrThrow());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,14 +23,14 @@ import net.minecraft.data.server.loottable.BlockLootTableGenerator;
|
|||
import net.minecraft.loot.LootTable;
|
||||
import net.minecraft.resource.featuretoggle.FeatureFlags;
|
||||
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ConditionJsonProvider;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.impl.datagen.FabricDataGenHelper;
|
||||
|
||||
public class ConditionBlockLootTableGenerator extends BlockLootTableGenerator {
|
||||
private final BlockLootTableGenerator parent;
|
||||
private final ConditionJsonProvider[] conditions;
|
||||
private final ResourceCondition[] conditions;
|
||||
|
||||
public ConditionBlockLootTableGenerator(BlockLootTableGenerator parent, ConditionJsonProvider[] conditions) {
|
||||
public ConditionBlockLootTableGenerator(BlockLootTableGenerator parent, ResourceCondition[] conditions) {
|
||||
super(Collections.emptySet(), FeatureFlags.FEATURE_MANAGER.getFeatureSet());
|
||||
|
||||
this.parent = parent;
|
||||
|
|
|
@ -41,7 +41,7 @@ import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
|
|||
import net.fabricmc.fabric.api.datagen.v1.provider.FabricBlockLootTableProvider;
|
||||
import net.fabricmc.fabric.api.datagen.v1.provider.FabricLootTableProvider;
|
||||
import net.fabricmc.fabric.api.datagen.v1.provider.SimpleFabricLootTableProvider;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ConditionJsonProvider;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.impl.datagen.FabricDataGenHelper;
|
||||
|
||||
public final class FabricLootTableProviderImpl {
|
||||
|
@ -55,11 +55,11 @@ public final class FabricLootTableProviderImpl {
|
|||
FabricDataOutput fabricDataOutput,
|
||||
CompletableFuture<RegistryWrapper.WrapperLookup> registryLookup) {
|
||||
HashMap<Identifier, LootTable> builders = Maps.newHashMap();
|
||||
HashMap<Identifier, ConditionJsonProvider[]> conditionMap = new HashMap<>();
|
||||
HashMap<Identifier, ResourceCondition[]> conditionMap = new HashMap<>();
|
||||
|
||||
return registryLookup.thenCompose(lookup -> {
|
||||
provider.accept(lookup, (registryKey, builder) -> {
|
||||
ConditionJsonProvider[] conditions = FabricDataGenHelper.consumeConditions(builder);
|
||||
ResourceCondition[] conditions = FabricDataGenHelper.consumeConditions(builder);
|
||||
conditionMap.put(registryKey.getValue(), conditions);
|
||||
|
||||
if (builders.put(registryKey.getValue(), builder.type(lootContextType).build()) != null) {
|
||||
|
@ -72,7 +72,7 @@ public final class FabricLootTableProviderImpl {
|
|||
|
||||
for (Map.Entry<Identifier, LootTable> entry : builders.entrySet()) {
|
||||
JsonObject tableJson = (JsonObject) LootTable.CODEC.encodeStart(ops, entry.getValue()).getOrThrow(IllegalStateException::new);
|
||||
ConditionJsonProvider.write(tableJson, conditionMap.remove(entry.getKey()));
|
||||
FabricDataGenHelper.addConditions(tableJson, conditionMap.remove(entry.getKey()));
|
||||
futures.add(DataProvider.writeToPath(writer, tableJson, getOutputPath(fabricDataOutput, entry.getKey())));
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,6 @@ import net.minecraft.advancement.AdvancementFrame;
|
|||
import net.minecraft.advancement.criterion.OnKilledCriterion;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockKeys;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.component.ComponentChanges;
|
||||
import net.minecraft.component.DataComponentTypes;
|
||||
import net.minecraft.data.DataOutput;
|
||||
|
@ -66,6 +65,7 @@ import net.minecraft.loot.provider.number.ConstantLootNumberProvider;
|
|||
import net.minecraft.recipe.Ingredient;
|
||||
import net.minecraft.recipe.book.RecipeCategory;
|
||||
import net.minecraft.registry.Registerable;
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.registry.RegistryBuilder;
|
||||
import net.minecraft.registry.RegistryEntryLookup;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
|
@ -97,13 +97,13 @@ import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
|
|||
import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider;
|
||||
import net.fabricmc.fabric.api.datagen.v1.provider.SimpleFabricLootTableProvider;
|
||||
import net.fabricmc.fabric.api.recipe.v1.ingredient.DefaultCustomIngredients;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ConditionJsonProvider;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.DefaultResourceConditions;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
|
||||
private static final ConditionJsonProvider NEVER_LOADED = DefaultResourceConditions.allModsLoaded("a");
|
||||
private static final ConditionJsonProvider ALWAYS_LOADED = DefaultResourceConditions.not(NEVER_LOADED);
|
||||
private static final ResourceCondition ALWAYS_LOADED = ResourceConditions.alwaysTrue();
|
||||
private static final ResourceCondition NEVER_LOADED = ResourceConditions.not(ALWAYS_LOADED);
|
||||
|
||||
@Override
|
||||
public void addJsonKeySortOrders(JsonKeySortOrderCallback callback) {
|
||||
|
@ -166,10 +166,10 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
|
|||
|
||||
ShapelessRecipeJsonBuilder.create(RecipeCategory.MISC, Items.DIAMOND_ORE, 4).input(Items.ITEM_FRAME)
|
||||
.criterion("has_frame", conditionsFromItem(Items.ITEM_FRAME))
|
||||
.offerTo(withConditions(exporter, DefaultResourceConditions.itemsRegistered(Blocks.DIAMOND_BLOCK)));
|
||||
.offerTo(withConditions(exporter, ResourceConditions.registryContains(RegistryKeys.ITEM, Registries.ITEM.getId(Items.DIAMOND_BLOCK))));
|
||||
ShapelessRecipeJsonBuilder.create(RecipeCategory.MISC, Items.EMERALD, 4).input(Items.ITEM_FRAME, 2)
|
||||
.criterion("has_frame", conditionsFromItem(Items.ITEM_FRAME))
|
||||
.offerTo(withConditions(exporter, DefaultResourceConditions.registryContains(BiomeKeys.PLAINS, BiomeKeys.BADLANDS)));
|
||||
.offerTo(withConditions(exporter, ResourceConditions.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));
|
||||
|
@ -390,7 +390,7 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
|
|||
@Override
|
||||
public void generate() {
|
||||
// Same condition twice to test recursive condition adding
|
||||
withConditions(ALWAYS_LOADED).withConditions(DefaultResourceConditions.not(NEVER_LOADED)).addDrop(SIMPLE_BLOCK);
|
||||
withConditions(ALWAYS_LOADED).withConditions(ResourceConditions.not(NEVER_LOADED)).addDrop(SIMPLE_BLOCK);
|
||||
addDrop(BLOCK_WITHOUT_ITEM, drops(SIMPLE_BLOCK));
|
||||
|
||||
excludeFromStrictValidation(BLOCK_WITHOUT_LOOT_TABLE);
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
version = getSubprojectVersion(project)
|
||||
|
||||
loom {
|
||||
accessWidenerPath = file("src/main/resources/fabric-resource-conditions-api-v1.accesswidener")
|
||||
}
|
||||
|
||||
testDependencies(project, [':fabric-gametest-api-v1'])
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* 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.api.resource.conditions.v1;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
/**
|
||||
* A resource condition and its parameters that can be serialized to JSON, meant for use in data generators.
|
||||
*/
|
||||
public interface ConditionJsonProvider {
|
||||
/**
|
||||
* Write the passed conditions to a JSON object in the {@value ResourceConditions#CONDITIONS_KEY} array.
|
||||
*
|
||||
* @throws IllegalArgumentException if the JSON object already contains that array
|
||||
*/
|
||||
static void write(JsonObject conditionalObject, ConditionJsonProvider @Nullable... conditions) {
|
||||
if (conditions == null) { // no condition -> skip
|
||||
return;
|
||||
}
|
||||
|
||||
Preconditions.checkArgument(conditions.length > 0, "Must write at least one condition."); // probably a programmer error
|
||||
if (conditionalObject.has(ResourceConditions.CONDITIONS_KEY)) throw new IllegalArgumentException("Object already has a condition entry: " + conditionalObject);
|
||||
|
||||
JsonArray array = new JsonArray();
|
||||
|
||||
for (ConditionJsonProvider condition : conditions) {
|
||||
Objects.requireNonNull(condition, "condition cannot be null");
|
||||
array.add(condition.toJson());
|
||||
}
|
||||
|
||||
conditionalObject.add(ResourceConditions.CONDITIONS_KEY, array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize this condition and its parameters to a new JSON object.
|
||||
*/
|
||||
default JsonObject toJson() {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.addProperty(ResourceConditions.CONDITION_ID_KEY, getConditionId().toString());
|
||||
this.writeParameters(jsonObject);
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the identifier of this condition} This is only for use by {@link #toJson()} to write it.
|
||||
*/
|
||||
Identifier getConditionId();
|
||||
|
||||
/**
|
||||
* Write the condition parameters (everything except the {@code "condition": ...} entry). This is only for use by {@link #toJson()}.
|
||||
*/
|
||||
void writeParameters(JsonObject object);
|
||||
}
|
|
@ -1,260 +0,0 @@
|
|||
/*
|
||||
* 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.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;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.JsonHelper;
|
||||
|
||||
import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl;
|
||||
|
||||
/**
|
||||
* Contains {@link ConditionJsonProvider}s for resource conditions provided by fabric itself.
|
||||
*/
|
||||
public final class DefaultResourceConditions {
|
||||
private static final Identifier NOT = new Identifier("fabric:not");
|
||||
private static final Identifier AND = new Identifier("fabric:and");
|
||||
private static final Identifier OR = new Identifier("fabric:or");
|
||||
private static final Identifier ALL_MODS_LOADED = new Identifier("fabric:all_mods_loaded");
|
||||
private static final Identifier ANY_MOD_LOADED = new Identifier("fabric:any_mod_loaded");
|
||||
private static final Identifier BLOCK_TAGS_POPULATED = new Identifier("fabric:block_tags_populated");
|
||||
private static final Identifier FLUID_TAGS_POPULATED = new Identifier("fabric:fluid_tags_populated");
|
||||
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.
|
||||
*
|
||||
* @apiNote This condition's ID is {@code fabric:not}, and takes one property, {@code value},
|
||||
* which is a condition.
|
||||
*/
|
||||
public static ConditionJsonProvider not(ConditionJsonProvider value) {
|
||||
return new ConditionJsonProvider() {
|
||||
@Override
|
||||
public void writeParameters(JsonObject object) {
|
||||
object.add("value", value.toJson());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier getConditionId() {
|
||||
return NOT;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that returns true if all of its child conditions are true.
|
||||
*
|
||||
* @apiNote This condition's ID is {@code fabric:and}, and takes one property, {@code values},
|
||||
* which is an array of conditions.
|
||||
*/
|
||||
public static ConditionJsonProvider and(ConditionJsonProvider... values) {
|
||||
return ResourceConditionsImpl.array(AND, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that returns true if any of its child conditions are true.
|
||||
*
|
||||
* @apiNote This condition's ID is {@code fabric:or}, and takes one property, {@code values},
|
||||
* which is an array of conditions.
|
||||
*/
|
||||
public static ConditionJsonProvider or(ConditionJsonProvider... values) {
|
||||
return ResourceConditionsImpl.array(OR, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that returns true if all the passed mod ids correspond to a loaded mod.
|
||||
*
|
||||
* @apiNote This condition's ID is {@code fabric:all_mods_loaded}, and takes one property,
|
||||
* {@code values}, which is an array of string mod IDs.
|
||||
*/
|
||||
public static ConditionJsonProvider allModsLoaded(String... modIds) {
|
||||
return ResourceConditionsImpl.mods(ALL_MODS_LOADED, modIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that returns true if at least one of the passed mod ids corresponds to a loaded mod.
|
||||
*
|
||||
* @apiNote This condition's ID is {@code fabric:any_mod_loaded}, and takes one property,
|
||||
* {@code values}, which is an array of string mod IDs.
|
||||
*/
|
||||
public static ConditionJsonProvider anyModLoaded(String... modIds) {
|
||||
return ResourceConditionsImpl.mods(ANY_MOD_LOADED, modIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a condition that returns true if each of the passed block tags exists and has at least one element.
|
||||
* @deprecated Use {@link #tagsPopulated} instead.
|
||||
*/
|
||||
@SafeVarargs
|
||||
@Deprecated
|
||||
public static ConditionJsonProvider blockTagsPopulated(TagKey<Block>... tags) {
|
||||
return ResourceConditionsImpl.tagsPopulated(BLOCK_TAGS_POPULATED, false, tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a condition that returns true if each of the passed fluid tags exists and has at least one element.
|
||||
* @deprecated Use {@link #tagsPopulated} instead.
|
||||
*/
|
||||
@SafeVarargs
|
||||
@Deprecated
|
||||
public static ConditionJsonProvider fluidTagsPopulated(TagKey<Fluid>... tags) {
|
||||
return ResourceConditionsImpl.tagsPopulated(FLUID_TAGS_POPULATED, false, tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a condition that returns true if each of the passed item tags exists and has at least one element.
|
||||
* @deprecated Use {@link #tagsPopulated} instead.
|
||||
*/
|
||||
@SafeVarargs
|
||||
@Deprecated
|
||||
public static ConditionJsonProvider itemTagsPopulated(TagKey<Item>... tags) {
|
||||
return ResourceConditionsImpl.tagsPopulated(ITEM_TAGS_POPULATED, false, tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that returns true if each of the passed tags exists and has at least one element.
|
||||
* This works for any registries, and the registry ID of the tags is serialized to JSON as well as the tags.
|
||||
*
|
||||
* @apiNote This condition's ID is {@code fabric:tags_populated}, and takes up to two properties:
|
||||
* {@code values}, which is an array of string tag IDs, and {@code registry}, which is the ID of
|
||||
* the registry of the tags. If {@code registry} is not provided, it defaults to {@code minecraft:item}.
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> ConditionJsonProvider tagsPopulated(TagKey<T>... tags) {
|
||||
return ResourceConditionsImpl.tagsPopulated(TAGS_POPULATED, true, tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that returns true if all the passed features are enabled.
|
||||
* @param features the features to check for
|
||||
*
|
||||
* @apiNote This condition's ID is {@code fabric:features_enabled}, and takes one property:
|
||||
* {@code features}, which is the array of the IDs of the feature flag to check.
|
||||
*/
|
||||
public static ConditionJsonProvider featuresEnabled(FeatureFlag... features) {
|
||||
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
|
||||
}
|
||||
|
||||
static {
|
||||
ResourceConditions.register(NOT, object -> {
|
||||
JsonObject condition = JsonHelper.getObject(object, "value");
|
||||
return !ResourceConditions.conditionMatches(condition);
|
||||
});
|
||||
ResourceConditions.register(AND, object -> {
|
||||
JsonArray array = JsonHelper.getArray(object, "values");
|
||||
return ResourceConditions.conditionsMatch(array, true);
|
||||
});
|
||||
ResourceConditions.register(OR, object -> {
|
||||
JsonArray array = JsonHelper.getArray(object, "values");
|
||||
return ResourceConditions.conditionsMatch(array, false);
|
||||
});
|
||||
ResourceConditions.register(ALL_MODS_LOADED, object -> ResourceConditionsImpl.modsLoadedMatch(object, true));
|
||||
ResourceConditions.register(ANY_MOD_LOADED, object -> ResourceConditionsImpl.modsLoadedMatch(object, false));
|
||||
ResourceConditions.register(BLOCK_TAGS_POPULATED, object -> ResourceConditionsImpl.tagsPopulatedMatch(object, RegistryKeys.BLOCK));
|
||||
ResourceConditions.register(FLUID_TAGS_POPULATED, object -> ResourceConditionsImpl.tagsPopulatedMatch(object, RegistryKeys.FLUID));
|
||||
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() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.api.resource.conditions.v1;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.registry.RegistryWrapper;
|
||||
|
||||
/**
|
||||
* A resource condition. To create a custom condition type, implement this interface,
|
||||
* call {@link ResourceConditionType#create} and create the type, then register
|
||||
* via {@link ResourceConditions#register}.
|
||||
*/
|
||||
public interface ResourceCondition {
|
||||
/**
|
||||
* A codec for a resource condition. It is a map with a {@code condition} key denoting the ID
|
||||
* of the condition, and any additional values required by the specific condition.
|
||||
*/
|
||||
Codec<ResourceCondition> CODEC = ResourceConditionType.TYPE_CODEC.dispatch("condition", ResourceCondition::getType, ResourceConditionType::codec);
|
||||
/**
|
||||
* A codec for a list of conditions.
|
||||
*/
|
||||
Codec<List<ResourceCondition>> LIST_CODEC = CODEC.listOf();
|
||||
|
||||
/**
|
||||
* @return the type of the condition
|
||||
*/
|
||||
ResourceConditionType<?> getType();
|
||||
|
||||
/**
|
||||
* Tests the condition. The passed registry lookup, if any, includes all static and dynamic entries.
|
||||
* However, the tags are not loaded yet.
|
||||
*
|
||||
* @implNote {@code registryLookup} should never be {@code null} for supported use cases
|
||||
* (such as recipes or advancements). However, it may be {@code null} in client-side
|
||||
* resources, or for non-vanilla resource types.
|
||||
*
|
||||
* @param registryLookup the registry lookup, or {@code null} in case registry is unavailable
|
||||
* @return whether the condition was successful
|
||||
*/
|
||||
boolean test(@Nullable RegistryWrapper.WrapperLookup registryLookup);
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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.api.resource.conditions.v1;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.Nullables;
|
||||
|
||||
/**
|
||||
* A type of resource conditions.
|
||||
* @param <T> the type of {@link ResourceCondition}
|
||||
*/
|
||||
public interface ResourceConditionType<T extends ResourceCondition> {
|
||||
/**
|
||||
* A codec used to serialize the condition type.
|
||||
*/
|
||||
Codec<ResourceConditionType<?>> TYPE_CODEC = Identifier.CODEC.comapFlatMap(id ->
|
||||
Nullables.mapOrElseGet(ResourceConditions.getConditionType(id), DataResult::success, () -> DataResult.error(() -> "Unknown resource condition key: "+ id)),
|
||||
ResourceConditionType::id
|
||||
);
|
||||
|
||||
/**
|
||||
* @return the condition's ID
|
||||
*/
|
||||
Identifier id();
|
||||
|
||||
/**
|
||||
* @return the condition's codec
|
||||
*/
|
||||
MapCodec<T> codec();
|
||||
|
||||
/**
|
||||
* Creates a resource condition type. The returned value needs to be registered with {@link ResourceConditions#register}.
|
||||
* @param id the ID of the condition
|
||||
* @param codec the codec used to serialize the condition
|
||||
* @param <T> the type of the resource condition
|
||||
* @return the condition type to register
|
||||
*/
|
||||
static <T extends ResourceCondition> ResourceConditionType<T> create(Identifier id, MapCodec<T> codec) {
|
||||
Objects.requireNonNull(id, "id cannot be null");
|
||||
Objects.requireNonNull(codec, "codec cannot be null");
|
||||
|
||||
return new ResourceConditionType<>() {
|
||||
@Override
|
||||
public Identifier id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapCodec<T> codec() {
|
||||
return codec;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -16,167 +16,172 @@
|
|||
|
||||
package net.fabricmc.fabric.api.resource.conditions.v1;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.resource.JsonDataLoader;
|
||||
import net.minecraft.registry.Registry;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.registry.tag.TagKey;
|
||||
import net.minecraft.resource.featuretoggle.FeatureFlag;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.JsonHelper;
|
||||
|
||||
import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.AllModsLoadedResourceCondition;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.AndResourceCondition;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.AnyModsLoadedResourceCondition;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.FeaturesEnabledResourceCondition;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.NotResourceCondition;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.OrResourceCondition;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.RegistryContainsResourceCondition;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.TagsPopulatedResourceCondition;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.TrueResourceCondition;
|
||||
|
||||
/**
|
||||
* Registration and access to resource loading conditions.
|
||||
* A resource condition is an identified {@code Predicate<JsonObject>} that can decide whether a resource should be loaded or not.
|
||||
* <ul>
|
||||
* <li>A JSON object that may contain a condition can be parsed with {@link #objectMatchesConditions}.
|
||||
* This is the preferred way of implementing conditional objects, as it handles the details of the format (see below) and catches and logs thrown exceptions.
|
||||
* This function should only be called from the "apply" phase of a {@link net.minecraft.resource.ResourceReloader},
|
||||
* otherwise some conditions might behave in unexpected ways.
|
||||
* </li>
|
||||
* <li>The lower-level {@link #conditionsMatch} and {@link #conditionMatches} may be useful when implementing conditions.</li>
|
||||
* <li>Conditions are registered with {@link #register} and queried with {@link #get}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>At the moment, Fabric only recognizes conditions for resources loaded by subclasses of {@link JsonDataLoader}.
|
||||
* This means: recipes, advancements, loot tables, loot functions and loot conditions.
|
||||
*
|
||||
* <p>Fabric provides some conditions, which can be generated using the helper methods in {@link DefaultResourceConditions}.
|
||||
*
|
||||
* <h3>Details of the format</h3>
|
||||
*
|
||||
* <p>A conditional JSON object must have a {@link #CONDITIONS_KEY} entry, containing an array of condition objects.
|
||||
* The conditions in the array must be satisfied to load the resource.
|
||||
* Each condition object must contain a {@link #CONDITION_ID_KEY} entry with the identifier of the condition,
|
||||
* and it may also contain additional data for the condition.
|
||||
* Here is an example of a resource that is only loaded if no mod with id {@code a} is loaded:
|
||||
* <pre>{@code
|
||||
* {
|
||||
* ... // normal contents of the resource
|
||||
* "fabric:load_conditions": [ // array of condition objects
|
||||
* { // a condition object
|
||||
* // the identifier of the condition... the "fabric:not" condition inverts the condition in its "value" field
|
||||
* "condition": "fabric:not",
|
||||
* // additional data, for "fabric:not", the "value" field is required to be another condition object
|
||||
* "value": {
|
||||
* // the identifier of the condition
|
||||
* "condition": "fabric:all_mods_loaded",
|
||||
* // additional data, for "fabric:all_mods_loaded"
|
||||
* "values": [
|
||||
* "a"
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }</pre>
|
||||
* Contains default resource conditions and the condition registry.
|
||||
*/
|
||||
public final class ResourceConditions {
|
||||
private static final Map<Identifier, Predicate<JsonObject>> REGISTERED_CONDITIONS = new ConcurrentHashMap<>();
|
||||
private static final Map<Identifier, ResourceConditionType<?>> REGISTERED_CONDITIONS = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* The key ({@value}) Fabric uses to identify resource conditions in a JSON object.
|
||||
* The JSON key for resource conditions, {@value #CONDITIONS_KEY}.
|
||||
*/
|
||||
public static final String CONDITIONS_KEY = "fabric:load_conditions";
|
||||
/**
|
||||
* The key ({@value}) identifying the resource condition's identifier inside a condition object.
|
||||
*/
|
||||
public static final String CONDITION_ID_KEY = "condition";
|
||||
|
||||
/**
|
||||
* Register a new resource condition.
|
||||
*
|
||||
* @throws IllegalArgumentException If a resource condition is already registered with the same name.
|
||||
*/
|
||||
public static void register(Identifier identifier, Predicate<JsonObject> condition) {
|
||||
Objects.requireNonNull(identifier, "Identifier may not be null.");
|
||||
Objects.requireNonNull(condition, "Condition may not be null.");
|
||||
|
||||
if (REGISTERED_CONDITIONS.put(identifier, condition) != null) {
|
||||
throw new IllegalArgumentException("Duplicate JSON condition registration with id " + identifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource condition with the passed name, or {@code null} if none is registered (yet).
|
||||
*/
|
||||
@Nullable
|
||||
public static Predicate<JsonObject> get(Identifier identifier) {
|
||||
return REGISTERED_CONDITIONS.get(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the passed JSON object either has no {@code fabric:conditions} tag, or all of its conditions match.
|
||||
* This should be called for objects that may contain a conditions entry.
|
||||
*
|
||||
* <p>This function should only be called from the "apply" phase of a {@link net.minecraft.resource.ResourceReloader},
|
||||
* otherwise some conditions might behave in unexpected ways.
|
||||
*
|
||||
* <p>If an exception is thrown during condition testing, it will be caught and logged, and false will be returned.
|
||||
*/
|
||||
public static boolean objectMatchesConditions(JsonObject object) {
|
||||
try {
|
||||
JsonArray conditions = JsonHelper.getArray(object, CONDITIONS_KEY, null);
|
||||
|
||||
if (conditions == null) {
|
||||
return true; // no conditions
|
||||
} else {
|
||||
return conditionsMatch(conditions, true);
|
||||
}
|
||||
} catch (RuntimeException exception) {
|
||||
ResourceConditionsImpl.LOGGER.warn("Skipping object %s. Failed to parse resource conditions".formatted(object), exception);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@code and} is true, check if all the passed conditions match.
|
||||
* If it is false, check if at least one of the passed conditions matches.
|
||||
*
|
||||
* @throws RuntimeException If some condition failed to parse.
|
||||
*/
|
||||
public static boolean conditionsMatch(JsonArray conditions, boolean and) throws RuntimeException {
|
||||
for (JsonElement element : conditions) {
|
||||
if (element.isJsonObject()) {
|
||||
if (conditionMatches(element.getAsJsonObject()) != and) {
|
||||
return !and;
|
||||
}
|
||||
} else {
|
||||
throw new JsonParseException("Invalid condition entry: " + element);
|
||||
}
|
||||
}
|
||||
|
||||
return and;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the passed condition object matches.
|
||||
*
|
||||
* @throws RuntimeException If some condition failed to parse.
|
||||
*/
|
||||
public static boolean conditionMatches(JsonObject condition) throws RuntimeException {
|
||||
Identifier conditionId = new Identifier(JsonHelper.getString(condition, CONDITION_ID_KEY));
|
||||
Predicate<JsonObject> jrc = get(conditionId);
|
||||
|
||||
if (jrc == null) {
|
||||
throw new JsonParseException("Unknown recipe condition: " + conditionId);
|
||||
} else {
|
||||
return jrc.test(condition);
|
||||
}
|
||||
}
|
||||
|
||||
private ResourceConditions() {
|
||||
}
|
||||
|
||||
static {
|
||||
// Load Fabric-provided conditions.
|
||||
DefaultResourceConditions.init();
|
||||
/**
|
||||
* Registers {@code condition}.
|
||||
* @param condition the condition to register
|
||||
* @throws IllegalArgumentException if {@code condition} is already registered
|
||||
*/
|
||||
public static void register(ResourceConditionType<?> condition) {
|
||||
Objects.requireNonNull(condition, "Condition may not be null.");
|
||||
|
||||
if (REGISTERED_CONDITIONS.put(condition.id(), condition) != null) {
|
||||
throw new IllegalArgumentException("Duplicate resource condition registered with id " + condition.id());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the condition with ID {@code id}, or {@code null} if there is no such condition
|
||||
*/
|
||||
public static ResourceConditionType<?> getConditionType(Identifier id) {
|
||||
return REGISTERED_CONDITIONS.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* A condition that always passes. Has ID {@code fabric:true}.
|
||||
*/
|
||||
public static ResourceCondition alwaysTrue() {
|
||||
return new TrueResourceCondition();
|
||||
}
|
||||
|
||||
/**
|
||||
* A condition that passes if {@code condition} does not pass. Has ID {@code fabric:not} and
|
||||
* takes one field, {@code value}, which is a resource condition.
|
||||
*/
|
||||
public static ResourceCondition not(ResourceCondition condition) {
|
||||
return new NotResourceCondition(condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* A condition that passes if each of the {@code conditions} passes. Has ID {@code fabric:and}
|
||||
* and takes one field, {@code values}, which is a list of resource conditions.
|
||||
* If there are no conditions to check, it always passes.
|
||||
*/
|
||||
public static ResourceCondition and(ResourceCondition... conditions) {
|
||||
return new AndResourceCondition(List.of(conditions));
|
||||
}
|
||||
|
||||
/**
|
||||
* A condition that passes if any of the {@code conditions} passes. Has ID {@code fabric:or}
|
||||
* and takes one field, {@code values}, which is a list of resource conditions.
|
||||
* If there are no conditions to check, it always fails.
|
||||
*/
|
||||
public static ResourceCondition or(ResourceCondition... conditions) {
|
||||
return new OrResourceCondition(List.of(conditions));
|
||||
}
|
||||
|
||||
/**
|
||||
* A condition that passes if each of the specified {@code modIds} are loaded. Has ID
|
||||
* {@code all_mods_loaded} and takes one field, {@code values}, which is a list of strings indicating
|
||||
* mod IDs. If there are no IDs to check, it always passes.
|
||||
*/
|
||||
public static ResourceCondition allModsLoaded(String... modIds) {
|
||||
return new AllModsLoadedResourceCondition(List.of(modIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* A condition that passes if any of the specified {@code modIds} are loaded. Has ID
|
||||
* {@code any_mods_loaded} and takes one field, {@code values}, which is a list of strings indicating
|
||||
* mod IDs. If there are no IDs to check, it always fails.
|
||||
*/
|
||||
public static ResourceCondition anyModsLoaded(String... modIds) {
|
||||
return new AnyModsLoadedResourceCondition(List.of(modIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* A condition that passes if each of the {@code tags} exist. This does not check if those tags have
|
||||
* any entries. Has ID {@code tags_populated} and takes two fields: {@code registry}, which is the ID
|
||||
* of the registry the tag is for, and {@code values}, which is a list of the tag IDs to check.
|
||||
* If there are no IDs to check, it always passes, including in cases where a nonexistent registry is
|
||||
* specified.
|
||||
*
|
||||
* @implNote Because tags are loaded after loot tables (and predicates/item modifiers), these resources
|
||||
* do not support this condition. In those cases, this condition logs a warning and always fails.
|
||||
* @param <T> the type of the tag values
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> ResourceCondition tagsPopulated(TagKey<T>... tags) {
|
||||
return new TagsPopulatedResourceCondition(tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #tagsPopulated(TagKey[])
|
||||
* @param <T> the type of the tag values
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> ResourceCondition tagsPopulated(RegistryKey<? extends Registry<T>> registry, TagKey<T>... tags) {
|
||||
return new TagsPopulatedResourceCondition(registry.getValue(), tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* A condition that passes if each of the {@code features} are enabled. Has ID {@code features_enabled}
|
||||
* and takes one field, {@code features}, which is a list of the feature IDs. If there are no IDs to
|
||||
* check, it always passes. If an unknown feature is specified, it always fails.
|
||||
*/
|
||||
public static ResourceCondition featuresEnabled(Identifier... features) {
|
||||
return new FeaturesEnabledResourceCondition(features);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #featuresEnabled(Identifier...)
|
||||
*/
|
||||
public static ResourceCondition featuresEnabled(FeatureFlag... features) {
|
||||
return new FeaturesEnabledResourceCondition(features);
|
||||
}
|
||||
|
||||
/**
|
||||
* A condition that passes if each of the {@code entries} exist. The entries may be from static or
|
||||
* dynamic registries. Has ID {@code registry_contains} and takes two fields: {@code registry}, which
|
||||
* is the ID of the registry, and {@code values}, which is a list of IDs to check.
|
||||
* If there are no IDs to check, it always passes, including in cases where a nonexistent registry is
|
||||
* specified.
|
||||
*
|
||||
* @param <T> the type of the tag values
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> ResourceCondition registryContains(RegistryKey<T>... entries) {
|
||||
return new RegistryContainsResourceCondition(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #registryContains(RegistryKey[])
|
||||
* @param <T> the type of the tag values
|
||||
*/
|
||||
public static <T> ResourceCondition registryContains(RegistryKey<? extends Registry<T>> registry, Identifier... entries) {
|
||||
return new RegistryContainsResourceCondition(registry.getValue(), entries);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
/**
|
||||
* Provides a way of conditionally loading JSON-based resources. By default, this can
|
||||
* be used with recipes, loot tables, advancements, predicates, and item modifiers.
|
||||
* be used with recipes, advancements, loot tables, predicates, and item modifiers.
|
||||
* Conditions are identified by an identifier and registered at {@link
|
||||
* net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions}.
|
||||
*
|
||||
|
@ -43,13 +43,12 @@
|
|||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <p>See {@link net.fabricmc.fabric.api.resource.conditions.v1.DefaultResourceConditions} for
|
||||
* the list of built-in conditions. It is also possible to register a custom condition.
|
||||
* <p>Unknown/invalid conditions will be skipped and considered successful.
|
||||
*
|
||||
* <h2>Data generation integration</h2>
|
||||
*
|
||||
* <p>Fabric Data Generation API supports adding a {@link
|
||||
* net.fabricmc.fabric.api.resource.conditions.v1.ConditionJsonProvider} to a generated file.
|
||||
* net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition} to a generated file.
|
||||
* Please check the documentation of the Data Generation API.
|
||||
*/
|
||||
package net.fabricmc.fabric.api.resource.conditions.v1;
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.impl.resource.conditions;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditionType;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.AllModsLoadedResourceCondition;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.AndResourceCondition;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.AnyModsLoadedResourceCondition;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.FeaturesEnabledResourceCondition;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.NotResourceCondition;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.OrResourceCondition;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.RegistryContainsResourceCondition;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.TagsPopulatedResourceCondition;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.conditions.TrueResourceCondition;
|
||||
|
||||
public class DefaultResourceConditionTypes {
|
||||
public static final ResourceConditionType<TrueResourceCondition> TRUE = createResourceConditionType("true", TrueResourceCondition.CODEC);
|
||||
public static final ResourceConditionType<NotResourceCondition> NOT = createResourceConditionType("not", NotResourceCondition.CODEC);
|
||||
public static final ResourceConditionType<OrResourceCondition> OR = createResourceConditionType("or", OrResourceCondition.CODEC);
|
||||
public static final ResourceConditionType<AndResourceCondition> AND = createResourceConditionType("and", AndResourceCondition.CODEC);
|
||||
public static final ResourceConditionType<AllModsLoadedResourceCondition> ALL_MODS_LOADED = createResourceConditionType("all_mods_loaded", AllModsLoadedResourceCondition.CODEC);
|
||||
public static final ResourceConditionType<AnyModsLoadedResourceCondition> ANY_MODS_LOADED = createResourceConditionType("any_mods_loaded", AnyModsLoadedResourceCondition.CODEC);
|
||||
public static final ResourceConditionType<TagsPopulatedResourceCondition> TAGS_POPULATED = createResourceConditionType("tags_populated", TagsPopulatedResourceCondition.CODEC);
|
||||
public static final ResourceConditionType<FeaturesEnabledResourceCondition> FEATURES_ENABLED = createResourceConditionType("features_enabled", FeaturesEnabledResourceCondition.CODEC);
|
||||
public static final ResourceConditionType<RegistryContainsResourceCondition> REGISTRY_CONTAINS = createResourceConditionType("registry_contains", RegistryContainsResourceCondition.CODEC);
|
||||
|
||||
private static <T extends ResourceCondition> ResourceConditionType<T> createResourceConditionType(String name, MapCodec<T> codec) {
|
||||
return ResourceConditionType.create(new Identifier("fabric", name), codec);
|
||||
}
|
||||
}
|
|
@ -17,178 +17,89 @@
|
|||
package net.fabricmc.fabric.impl.resource.conditions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import org.apache.commons.lang3.mutable.MutableBoolean;
|
||||
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;
|
||||
import net.minecraft.registry.entry.RegistryEntry;
|
||||
import net.minecraft.registry.tag.TagKey;
|
||||
import net.minecraft.registry.RegistryWrapper;
|
||||
import net.minecraft.registry.tag.TagManagerLoader;
|
||||
import net.minecraft.resource.featuretoggle.FeatureFlag;
|
||||
import net.minecraft.resource.featuretoggle.FeatureFlags;
|
||||
import net.minecraft.resource.featuretoggle.FeatureSet;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.JsonHelper;
|
||||
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ConditionJsonProvider;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
public final class ResourceConditionsImpl {
|
||||
public final class ResourceConditionsImpl implements ModInitializer {
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger("Fabric Resource Conditions");
|
||||
public static FeatureSet currentFeatures = null;
|
||||
|
||||
// Providers
|
||||
|
||||
public static ConditionJsonProvider array(Identifier id, ConditionJsonProvider... values) {
|
||||
Preconditions.checkArgument(values.length > 0, "Must register at least one value.");
|
||||
|
||||
return new ConditionJsonProvider() {
|
||||
@Override
|
||||
public Identifier getConditionId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeParameters(JsonObject object) {
|
||||
JsonArray array = new JsonArray();
|
||||
|
||||
for (ConditionJsonProvider provider : values) {
|
||||
array.add(provider.toJson());
|
||||
}
|
||||
|
||||
object.add("values", array);
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
ResourceConditions.register(DefaultResourceConditionTypes.TRUE);
|
||||
ResourceConditions.register(DefaultResourceConditionTypes.NOT);
|
||||
ResourceConditions.register(DefaultResourceConditionTypes.AND);
|
||||
ResourceConditions.register(DefaultResourceConditionTypes.OR);
|
||||
ResourceConditions.register(DefaultResourceConditionTypes.ALL_MODS_LOADED);
|
||||
ResourceConditions.register(DefaultResourceConditionTypes.ANY_MODS_LOADED);
|
||||
ResourceConditions.register(DefaultResourceConditionTypes.TAGS_POPULATED);
|
||||
ResourceConditions.register(DefaultResourceConditionTypes.FEATURES_ENABLED);
|
||||
ResourceConditions.register(DefaultResourceConditionTypes.REGISTRY_CONTAINS);
|
||||
}
|
||||
|
||||
public static ConditionJsonProvider mods(Identifier id, String... modIds) {
|
||||
Preconditions.checkArgument(modIds.length > 0, "Must register at least one mod id.");
|
||||
public static boolean applyResourceConditions(JsonObject obj, String dataType, Identifier key, @Nullable RegistryWrapper.WrapperLookup registryLookup) {
|
||||
boolean debugLogEnabled = ResourceConditionsImpl.LOGGER.isDebugEnabled();
|
||||
|
||||
return new ConditionJsonProvider() {
|
||||
@Override
|
||||
public Identifier getConditionId() {
|
||||
return id;
|
||||
}
|
||||
if (obj.has(ResourceConditions.CONDITIONS_KEY)) {
|
||||
DataResult<List<ResourceCondition>> conditions = ResourceCondition.LIST_CODEC.parse(JsonOps.INSTANCE, obj.get(ResourceConditions.CONDITIONS_KEY));
|
||||
|
||||
@Override
|
||||
public void writeParameters(JsonObject object) {
|
||||
JsonArray array = new JsonArray();
|
||||
if (conditions.isSuccess()) {
|
||||
boolean matched = ResourceConditionsImpl.conditionsMet(conditions.getOrThrow(), registryLookup, true);
|
||||
|
||||
for (String modId : modIds) {
|
||||
array.add(modId);
|
||||
if (debugLogEnabled) {
|
||||
String verdict = matched ? "Allowed" : "Rejected";
|
||||
ResourceConditionsImpl.LOGGER.debug("{} resource of type {} with id {}", verdict, dataType, key);
|
||||
}
|
||||
|
||||
object.add("values", array);
|
||||
return matched;
|
||||
} else {
|
||||
ResourceConditionsImpl.LOGGER.error("Failed to parse resource conditions for file of type {} with id {}, skipping: {}", dataType, key, conditions.error().get().message());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> ConditionJsonProvider tagsPopulated(Identifier id, boolean includeRegistry, TagKey<T>... tags) {
|
||||
Preconditions.checkArgument(tags.length > 0, "Must register at least one tag.");
|
||||
final RegistryKey<? extends Registry<?>> registryRef = tags[0].registry();
|
||||
|
||||
return new ConditionJsonProvider() {
|
||||
@Override
|
||||
public Identifier getConditionId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeParameters(JsonObject object) {
|
||||
JsonArray array = new JsonArray();
|
||||
|
||||
for (TagKey<T> tag : tags) {
|
||||
array.add(tag.id().toString());
|
||||
}
|
||||
|
||||
object.add("values", array);
|
||||
|
||||
if (includeRegistry && registryRef != RegistryKeys.ITEM) {
|
||||
// tags[0] is guaranteed to exist.
|
||||
// Skip if this is the default (minecraft:item)
|
||||
object.addProperty("registry", registryRef.getValue().toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
// Condition implementations
|
||||
|
||||
public static boolean modsLoadedMatch(JsonObject object, boolean and) {
|
||||
JsonArray array = JsonHelper.getArray(object, "values");
|
||||
public static boolean conditionsMet(List<ResourceCondition> conditions, @Nullable RegistryWrapper.WrapperLookup registryLookup, boolean and) {
|
||||
for (ResourceCondition condition : conditions) {
|
||||
if (condition.test(registryLookup) != and) {
|
||||
return !and;
|
||||
}
|
||||
}
|
||||
|
||||
for (JsonElement element : array) {
|
||||
if (element.isJsonPrimitive()) {
|
||||
if (FabricLoader.getInstance().isModLoaded(element.getAsString()) != and) {
|
||||
return !and;
|
||||
}
|
||||
} else {
|
||||
throw new JsonParseException("Invalid mod id entry: " + element);
|
||||
return and;
|
||||
}
|
||||
|
||||
public static boolean modsLoaded(List<String> modIds, boolean and) {
|
||||
for (String modId : modIds) {
|
||||
if (FabricLoader.getInstance().isModLoaded(modId) != and) {
|
||||
return !and;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,108 +111,78 @@ public final class ResourceConditionsImpl {
|
|||
* The tags are set at the end of the "apply" phase in {@link TagManagerLoader}, and cleared in {@link net.minecraft.server.DataPackContents#refresh}.
|
||||
* If the resource reload fails, the thread local is not cleared and:
|
||||
* - the map will remain in memory until the next reload;
|
||||
* - any call to {@link #tagsPopulatedMatch} will check the tags from the failed reload instead of failing directly.
|
||||
* - any call to {@link #tagsPopulated} will check the tags from the failed reload instead of failing directly.
|
||||
* This is probably acceptable.
|
||||
*/
|
||||
public static final ThreadLocal<Map<RegistryKey<?>, Map<Identifier, Collection<RegistryEntry<?>>>>> LOADED_TAGS = new ThreadLocal<>();
|
||||
public static final ThreadLocal<Map<RegistryKey<?>, Set<Identifier>>> LOADED_TAGS = new ThreadLocal<>();
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public static void setTags(List<TagManagerLoader.RegistryTags<?>> tags) {
|
||||
Map<RegistryKey<?>, Map<Identifier, Collection<RegistryEntry<?>>>> tagMap = new HashMap<>();
|
||||
Map<RegistryKey<?>, Set<Identifier>> tagMap = new IdentityHashMap<>();
|
||||
|
||||
for (TagManagerLoader.RegistryTags<?> registryTags : tags) {
|
||||
tagMap.put(registryTags.key(), (Map) registryTags.tags());
|
||||
tagMap.put(registryTags.key(), registryTags.tags().keySet());
|
||||
}
|
||||
|
||||
LOADED_TAGS.set(tagMap);
|
||||
}
|
||||
|
||||
public static boolean tagsPopulatedMatch(JsonObject object) {
|
||||
String key = JsonHelper.getString(object, "registry", "minecraft:item");
|
||||
RegistryKey<? extends Registry<?>> registryRef = RegistryKey.ofRegistry(new Identifier(key));
|
||||
return tagsPopulatedMatch(object, registryRef);
|
||||
}
|
||||
// Cannot use registry because tags are not loaded to the registry at this stage yet.
|
||||
public static boolean tagsPopulated(Identifier registryId, List<Identifier> tags) {
|
||||
Map<RegistryKey<?>, Set<Identifier>> tagMap = LOADED_TAGS.get();
|
||||
|
||||
public static boolean tagsPopulatedMatch(JsonObject object, RegistryKey<? extends Registry<?>> registryKey) {
|
||||
JsonArray array = JsonHelper.getArray(object, "values");
|
||||
@Nullable
|
||||
Map<RegistryKey<?>, Map<Identifier, Collection<RegistryEntry<?>>>> allTags = LOADED_TAGS.get();
|
||||
|
||||
if (allTags == null) {
|
||||
LOGGER.warn("Can't retrieve deserialized tags. Failing tags_populated resource condition check.");
|
||||
if (tagMap == null) {
|
||||
LOGGER.warn("Can't retrieve registry {}, failing tags_populated resource condition check", registryId);
|
||||
return false;
|
||||
}
|
||||
|
||||
Map<Identifier, Collection<RegistryEntry<?>>> registryTags = allTags.get(registryKey);
|
||||
Set<Identifier> tagSet = tagMap.get(RegistryKey.ofRegistry(registryId));
|
||||
|
||||
if (registryTags == null) {
|
||||
// No tag for this registry
|
||||
return array.isEmpty();
|
||||
if (tagSet == null) {
|
||||
return tags.isEmpty();
|
||||
} else {
|
||||
return tagSet.containsAll(tags);
|
||||
}
|
||||
|
||||
for (JsonElement element : array) {
|
||||
if (element.isJsonPrimitive()) {
|
||||
Identifier id = new Identifier(element.getAsString());
|
||||
Collection<RegistryEntry<?>> tags = registryTags.get(id);
|
||||
|
||||
if (tags == null || tags.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
throw new JsonParseException("Invalid tag id entry: " + element);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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();
|
||||
FeatureSet set = FeatureFlags.FEATURE_MANAGER.featureSetOf(featureIds, (id) -> {
|
||||
throw new JsonParseException("Unknown feature flag: " + id);
|
||||
public static boolean featuresEnabled(Collection<Identifier> features) {
|
||||
MutableBoolean foundUnknown = new MutableBoolean();
|
||||
FeatureSet set = FeatureFlags.FEATURE_MANAGER.featureSetOf(features, (id) -> {
|
||||
LOGGER.info("Found unknown feature {}, treating it as failure", id);
|
||||
foundUnknown.setTrue();
|
||||
});
|
||||
|
||||
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.");
|
||||
if (foundUnknown.booleanValue()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Optional<Registry<E>> registry = registries.getOptional(registryRef);
|
||||
|
||||
if (registry.isEmpty()) {
|
||||
// No such registry
|
||||
return array.isEmpty();
|
||||
if (currentFeatures == null) {
|
||||
LOGGER.warn("Can't retrieve current features, failing features_enabled resource condition check.");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (JsonElement element : array) {
|
||||
if (element.isJsonPrimitive()) {
|
||||
Identifier id = new Identifier(element.getAsString());
|
||||
return set.isSubsetOf(currentFeatures);
|
||||
}
|
||||
|
||||
if (!registry.get().containsId(id)) {
|
||||
public static boolean registryContains(@Nullable RegistryWrapper.WrapperLookup registryLookup, Identifier registryId, List<Identifier> entries) {
|
||||
RegistryKey<? extends Registry<Object>> registryKey = RegistryKey.ofRegistry(registryId);
|
||||
|
||||
if (registryLookup == null) {
|
||||
LOGGER.warn("Can't retrieve registry {}, failing registry_contains resource condition check", registryId);
|
||||
return false;
|
||||
}
|
||||
|
||||
Optional<RegistryWrapper.Impl<Object>> wrapper = registryLookup.getOptionalWrapper(registryKey);
|
||||
|
||||
if (wrapper.isPresent()) {
|
||||
for (Identifier id : entries) {
|
||||
if (wrapper.get().getOptional(RegistryKey.of(registryKey, id)).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
throw new JsonParseException("Invalid registry entry id: " + element);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
} else {
|
||||
return entries.isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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.impl.resource.conditions.conditions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.registry.RegistryWrapper;
|
||||
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditionType;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.DefaultResourceConditionTypes;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl;
|
||||
|
||||
public record AllModsLoadedResourceCondition(List<String> modIds) implements ResourceCondition {
|
||||
public static final MapCodec<AllModsLoadedResourceCondition> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||
Codec.STRING.listOf().fieldOf("values").forGetter(AllModsLoadedResourceCondition::modIds)
|
||||
).apply(instance, AllModsLoadedResourceCondition::new));
|
||||
|
||||
@Override
|
||||
public ResourceConditionType<?> getType() {
|
||||
return DefaultResourceConditionTypes.ALL_MODS_LOADED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(@Nullable RegistryWrapper.WrapperLookup registryLookup) {
|
||||
return ResourceConditionsImpl.modsLoaded(this.modIds(), true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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.impl.resource.conditions.conditions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.registry.RegistryWrapper;
|
||||
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditionType;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.DefaultResourceConditionTypes;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl;
|
||||
|
||||
public record AndResourceCondition(List<ResourceCondition> conditions) implements ResourceCondition {
|
||||
public static final MapCodec<AndResourceCondition> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||
ResourceCondition.CODEC.listOf().fieldOf("values").forGetter(AndResourceCondition::conditions)
|
||||
).apply(instance, AndResourceCondition::new));
|
||||
|
||||
@Override
|
||||
public ResourceConditionType<?> getType() {
|
||||
return DefaultResourceConditionTypes.AND;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(@Nullable RegistryWrapper.WrapperLookup registryLookup) {
|
||||
return ResourceConditionsImpl.conditionsMet(this.conditions(), registryLookup, true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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.impl.resource.conditions.conditions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.registry.RegistryWrapper;
|
||||
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditionType;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.DefaultResourceConditionTypes;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl;
|
||||
|
||||
public record AnyModsLoadedResourceCondition(List<String> modIds) implements ResourceCondition {
|
||||
public static final MapCodec<AnyModsLoadedResourceCondition> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||
Codec.STRING.listOf().fieldOf("values").forGetter(AnyModsLoadedResourceCondition::modIds)
|
||||
).apply(instance, AnyModsLoadedResourceCondition::new));
|
||||
|
||||
@Override
|
||||
public ResourceConditionType<?> getType() {
|
||||
return DefaultResourceConditionTypes.ANY_MODS_LOADED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(@Nullable RegistryWrapper.WrapperLookup registryLookup) {
|
||||
return ResourceConditionsImpl.modsLoaded(this.modIds(), false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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.impl.resource.conditions.conditions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.registry.RegistryWrapper;
|
||||
import net.minecraft.resource.featuretoggle.FeatureFlag;
|
||||
import net.minecraft.resource.featuretoggle.FeatureFlags;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditionType;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.DefaultResourceConditionTypes;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl;
|
||||
|
||||
public record FeaturesEnabledResourceCondition(Collection<Identifier> features) implements ResourceCondition {
|
||||
public static final MapCodec<FeaturesEnabledResourceCondition> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||
Identifier.CODEC.listOf().fieldOf("features").forGetter(condition -> List.copyOf(condition.features))
|
||||
).apply(instance, FeaturesEnabledResourceCondition::new));
|
||||
|
||||
public FeaturesEnabledResourceCondition(Identifier... features) {
|
||||
this(List.of(features));
|
||||
}
|
||||
|
||||
public FeaturesEnabledResourceCondition(FeatureFlag... flags) {
|
||||
this(FeatureFlags.FEATURE_MANAGER.toId(FeatureFlags.FEATURE_MANAGER.featureSetOf(flags)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceConditionType<?> getType() {
|
||||
return DefaultResourceConditionTypes.FEATURES_ENABLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(@Nullable RegistryWrapper.WrapperLookup registryLookup) {
|
||||
return ResourceConditionsImpl.featuresEnabled(this.features());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.impl.resource.conditions.conditions;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.registry.RegistryWrapper;
|
||||
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditionType;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.DefaultResourceConditionTypes;
|
||||
|
||||
public record NotResourceCondition(ResourceCondition condition) implements ResourceCondition {
|
||||
public static final MapCodec<NotResourceCondition> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||
ResourceCondition.CODEC.fieldOf("value").forGetter(NotResourceCondition::condition)
|
||||
).apply(instance, NotResourceCondition::new));
|
||||
|
||||
@Override
|
||||
public ResourceConditionType<?> getType() {
|
||||
return DefaultResourceConditionTypes.NOT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(@Nullable RegistryWrapper.WrapperLookup registryLookup) {
|
||||
return !this.condition().test(registryLookup);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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.impl.resource.conditions.conditions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.registry.RegistryWrapper;
|
||||
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditionType;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.DefaultResourceConditionTypes;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl;
|
||||
|
||||
public record OrResourceCondition(List<ResourceCondition> conditions) implements ResourceCondition {
|
||||
public static final MapCodec<OrResourceCondition> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||
ResourceCondition.CODEC.listOf().fieldOf("values").forGetter(OrResourceCondition::conditions)
|
||||
).apply(instance, OrResourceCondition::new));
|
||||
|
||||
@Override
|
||||
public ResourceConditionType<?> getType() {
|
||||
return DefaultResourceConditionTypes.OR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(@Nullable RegistryWrapper.WrapperLookup registryLookup) {
|
||||
return ResourceConditionsImpl.conditionsMet(this.conditions(), registryLookup, false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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.impl.resource.conditions.conditions;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.registry.RegistryKeys;
|
||||
import net.minecraft.registry.RegistryWrapper;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditionType;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.DefaultResourceConditionTypes;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl;
|
||||
|
||||
public record RegistryContainsResourceCondition(Identifier registry, List<Identifier> entries) implements ResourceCondition {
|
||||
// Cannot use registry-bound codec because they fail parsing if nonexistent,
|
||||
// and resource conditions themselves should not fail to parse on condition failure
|
||||
public static final MapCodec<RegistryContainsResourceCondition> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||
Identifier.CODEC.fieldOf("registry").orElse(RegistryKeys.ITEM.getValue()).forGetter(RegistryContainsResourceCondition::registry),
|
||||
Identifier.CODEC.listOf().fieldOf("values").forGetter(RegistryContainsResourceCondition::entries)
|
||||
).apply(instance, RegistryContainsResourceCondition::new));
|
||||
|
||||
public RegistryContainsResourceCondition(Identifier registry, Identifier... entries) {
|
||||
this(registry, List.of(entries));
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public <T> RegistryContainsResourceCondition(RegistryKey<T>... entries) {
|
||||
this(entries[0].getRegistry(), Arrays.stream(entries).map(RegistryKey::getValue).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceConditionType<?> getType() {
|
||||
return DefaultResourceConditionTypes.REGISTRY_CONTAINS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(@Nullable RegistryWrapper.WrapperLookup registryLookup) {
|
||||
return ResourceConditionsImpl.registryContains(registryLookup, this.registry(), this.entries());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.impl.resource.conditions.conditions;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.registry.RegistryKeys;
|
||||
import net.minecraft.registry.RegistryWrapper;
|
||||
import net.minecraft.registry.tag.TagKey;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditionType;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.DefaultResourceConditionTypes;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl;
|
||||
|
||||
public record TagsPopulatedResourceCondition(Identifier registry, List<Identifier> tags) implements ResourceCondition {
|
||||
// Cannot use registry-bound codec because they fail parsing if nonexistent,
|
||||
// and resource conditions themselves should not fail to parse on condition failure
|
||||
public static final MapCodec<TagsPopulatedResourceCondition> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||
Identifier.CODEC.fieldOf("registry").orElse(RegistryKeys.ITEM.getValue()).forGetter(TagsPopulatedResourceCondition::registry),
|
||||
Identifier.CODEC.listOf().fieldOf("values").forGetter(TagsPopulatedResourceCondition::tags)
|
||||
).apply(instance, TagsPopulatedResourceCondition::new));
|
||||
|
||||
@SafeVarargs
|
||||
public <T> TagsPopulatedResourceCondition(Identifier registry, TagKey<T>... tags) {
|
||||
this(registry, Arrays.stream(tags).map(TagKey::id).toList());
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public <T> TagsPopulatedResourceCondition(TagKey<T>... tags) {
|
||||
this(tags[0].registry().getValue(), Arrays.stream(tags).map(TagKey::id).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceConditionType<?> getType() {
|
||||
return DefaultResourceConditionTypes.TAGS_POPULATED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(@Nullable RegistryWrapper.WrapperLookup registryLookup) {
|
||||
return ResourceConditionsImpl.tagsPopulated(this.registry(), this.tags());
|
||||
}
|
||||
}
|
|
@ -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.impl.resource.conditions.conditions;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.registry.RegistryWrapper;
|
||||
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditionType;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.DefaultResourceConditionTypes;
|
||||
|
||||
public class TrueResourceCondition implements ResourceCondition {
|
||||
public static final MapCodec<TrueResourceCondition> CODEC = MapCodec.unit(TrueResourceCondition::new);
|
||||
|
||||
@Override
|
||||
public ResourceConditionType<?> getType() {
|
||||
return DefaultResourceConditionTypes.TRUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(@Nullable RegistryWrapper.WrapperLookup registryLookup) {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
|||
import net.minecraft.registry.CombinedDynamicRegistries;
|
||||
import net.minecraft.registry.ServerDynamicRegistryType;
|
||||
import net.minecraft.resource.ResourceManager;
|
||||
import net.minecraft.resource.featuretoggle.FeatureFlags;
|
||||
import net.minecraft.resource.featuretoggle.FeatureSet;
|
||||
import net.minecraft.server.DataPackContents;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
|
@ -36,17 +37,12 @@ import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl;
|
|||
|
||||
@Mixin(DataPackContents.class)
|
||||
public class DataPackContentsMixin {
|
||||
/**
|
||||
* Clear the tags captured by {@link DataPackContentsMixin}.
|
||||
* This must happen after the resource reload is complete, to ensure that the tags remain available throughout the entire "apply" phase.
|
||||
*/
|
||||
@Inject(
|
||||
method = "refresh",
|
||||
at = @At("HEAD")
|
||||
)
|
||||
public void hookRefresh(CallbackInfo ci) {
|
||||
private void hookRefresh(CallbackInfo ci) {
|
||||
ResourceConditionsImpl.LOADED_TAGS.remove();
|
||||
ResourceConditionsImpl.CURRENT_REGISTRIES.remove();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
|
@ -54,7 +50,7 @@ public class DataPackContentsMixin {
|
|||
at = @At("HEAD")
|
||||
)
|
||||
private static void hookReload(ResourceManager manager, CombinedDynamicRegistries<ServerDynamicRegistryType> combinedDynamicRegistries, FeatureSet enabledFeatures, CommandManager.RegistrationEnvironment environment, int functionPermissionLevel, Executor prepareExecutor, Executor applyExecutor, CallbackInfoReturnable<CompletableFuture<DataPackContents>> cir) {
|
||||
ResourceConditionsImpl.CURRENT_FEATURES.set(enabledFeatures);
|
||||
ResourceConditionsImpl.CURRENT_REGISTRIES.set(combinedDynamicRegistries.getCombinedRegistryManager());
|
||||
System.out.println("Enabling " + FeatureFlags.FEATURE_MANAGER.toId(enabledFeatures));
|
||||
ResourceConditionsImpl.currentFeatures = enabledFeatures;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,16 +21,17 @@ import java.util.Map;
|
|||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import net.minecraft.registry.RegistryWrapper;
|
||||
import net.minecraft.resource.JsonDataLoader;
|
||||
import net.minecraft.resource.ResourceManager;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.profiler.Profiler;
|
||||
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl;
|
||||
|
||||
/**
|
||||
|
@ -44,11 +45,10 @@ public class JsonDataLoaderMixin extends SinglePreparationResourceReloaderMixin
|
|||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void fabric_applyResourceConditions(ResourceManager resourceManager, Profiler profiler, Object object) {
|
||||
protected void fabric_applyResourceConditions(ResourceManager resourceManager, Profiler profiler, Object object, @Nullable RegistryWrapper.WrapperLookup registryLookup) {
|
||||
profiler.push("Fabric resource conditions: %s".formatted(dataType));
|
||||
|
||||
Iterator<Map.Entry<Identifier, JsonElement>> it = ((Map<Identifier, JsonElement>) object).entrySet().iterator();
|
||||
boolean debugLogEnabled = ResourceConditionsImpl.LOGGER.isDebugEnabled();
|
||||
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<Identifier, JsonElement> entry = it.next();
|
||||
|
@ -57,17 +57,8 @@ public class JsonDataLoaderMixin extends SinglePreparationResourceReloaderMixin
|
|||
if (resourceData.isJsonObject()) {
|
||||
JsonObject obj = resourceData.getAsJsonObject();
|
||||
|
||||
if (obj.has(ResourceConditions.CONDITIONS_KEY)) {
|
||||
boolean matched = ResourceConditions.objectMatchesConditions(obj);
|
||||
|
||||
if (!matched) {
|
||||
it.remove();
|
||||
}
|
||||
|
||||
if (debugLogEnabled) {
|
||||
String verdict = matched ? "Allowed" : "Rejected";
|
||||
ResourceConditionsImpl.LOGGER.debug("{} resource of type {} with id {}", verdict, dataType, entry.getKey());
|
||||
}
|
||||
if (!ResourceConditionsImpl.applyResourceConditions(obj, dataType, entry.getKey(), fabric_getRegistryLookup())) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import net.minecraft.recipe.RecipeManager;
|
||||
import net.minecraft.registry.RegistryWrapper;
|
||||
|
||||
@Mixin(RecipeManager.class)
|
||||
public class RecipeManagerMixin extends SinglePreparationResourceReloaderMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private RegistryWrapper.WrapperLookup registryLookup;
|
||||
|
||||
@Override
|
||||
protected @Nullable RegistryWrapper.WrapperLookup fabric_getRegistryLookup() {
|
||||
return this.registryLookup;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 java.util.WeakHashMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import net.minecraft.loot.LootDataType;
|
||||
import net.minecraft.registry.CombinedDynamicRegistries;
|
||||
import net.minecraft.registry.DynamicRegistryManager;
|
||||
import net.minecraft.registry.MutableRegistry;
|
||||
import net.minecraft.registry.RegistryOps;
|
||||
import net.minecraft.registry.RegistryWrapper;
|
||||
import net.minecraft.registry.ReloadableRegistries;
|
||||
import net.minecraft.registry.ServerDynamicRegistryType;
|
||||
import net.minecraft.resource.ResourceManager;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl;
|
||||
|
||||
// Should apply before Loot API.
|
||||
@Mixin(value = ReloadableRegistries.class, priority = 900)
|
||||
public class ReloadableRegistriesMixin {
|
||||
// The cross-thread nature of the stuff makes this necessary. It is technically possible to query the wrapper from
|
||||
// the ops, but it requires more mixins.
|
||||
// Key refers to value, but value does not refer to key, so WeakHashMap is fine.
|
||||
@Unique
|
||||
private static final WeakHashMap<RegistryOps<?>, RegistryWrapper.WrapperLookup> REGISTRY_LOOKUPS = new WeakHashMap<>();
|
||||
|
||||
@WrapOperation(method = "reload", at = @At(value = "NEW", target = "net/minecraft/registry/ReloadableRegistries$ReloadableWrapperLookup"))
|
||||
private static ReloadableRegistries.ReloadableWrapperLookup storeWrapperLookup(DynamicRegistryManager registryManager, Operation<ReloadableRegistries.ReloadableWrapperLookup> original, @Share("wrapper") LocalRef<RegistryWrapper.WrapperLookup> share) {
|
||||
ReloadableRegistries.ReloadableWrapperLookup lookup = original.call(registryManager);
|
||||
share.set(lookup);
|
||||
return lookup;
|
||||
}
|
||||
|
||||
@Inject(method = "reload", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/registry/ReloadableRegistries$ReloadableWrapperLookup;getOps(Lcom/mojang/serialization/DynamicOps;)Lnet/minecraft/registry/RegistryOps;", shift = At.Shift.AFTER))
|
||||
private static void storeWrapperLookup(CombinedDynamicRegistries<ServerDynamicRegistryType> dynamicRegistries, ResourceManager resourceManager, Executor prepareExecutor, CallbackInfoReturnable<CompletableFuture<CombinedDynamicRegistries<ServerDynamicRegistryType>>> cir, @Local RegistryOps ops, @Share("wrapper") LocalRef<RegistryWrapper.WrapperLookup> share) {
|
||||
REGISTRY_LOOKUPS.put(ops, share.get());
|
||||
}
|
||||
|
||||
@Inject(method = "method_58278", at = @At("HEAD"), cancellable = true)
|
||||
private static void applyConditions(LootDataType lootDataType, RegistryOps ops, MutableRegistry mutableRegistry, Identifier id, JsonElement json, CallbackInfo ci) {
|
||||
if (json.isJsonObject() && !ResourceConditionsImpl.applyResourceConditions(json.getAsJsonObject(), lootDataType.directory(), id, REGISTRY_LOOKUPS.get(ops))) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import net.minecraft.registry.RegistryWrapper;
|
||||
import net.minecraft.server.ServerAdvancementLoader;
|
||||
|
||||
@Mixin(ServerAdvancementLoader.class)
|
||||
public class ServerAdvancementLoaderMixin extends SinglePreparationResourceReloaderMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private RegistryWrapper.WrapperLookup registryLookup;
|
||||
|
||||
@Override
|
||||
protected @Nullable RegistryWrapper.WrapperLookup fabric_getRegistryLookup() {
|
||||
return this.registryLookup;
|
||||
}
|
||||
}
|
|
@ -16,11 +16,13 @@
|
|||
|
||||
package net.fabricmc.fabric.mixin.resource.conditions;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
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.registry.RegistryWrapper;
|
||||
import net.minecraft.resource.ResourceManager;
|
||||
import net.minecraft.resource.SinglePreparationResourceReloader;
|
||||
import net.minecraft.util.profiler.Profiler;
|
||||
|
@ -34,9 +36,14 @@ public class SinglePreparationResourceReloaderMixin {
|
|||
// thenAcceptAsync in reload
|
||||
@Inject(at = @At("HEAD"), method = "method_18790")
|
||||
private void applyResourceConditions(ResourceManager resourceManager, Profiler profiler, Object object, CallbackInfo ci) {
|
||||
fabric_applyResourceConditions(resourceManager, profiler, object);
|
||||
fabric_applyResourceConditions(resourceManager, profiler, object, fabric_getRegistryLookup());
|
||||
}
|
||||
|
||||
protected void fabric_applyResourceConditions(ResourceManager resourceManager, Profiler profiler, Object object) {
|
||||
protected void fabric_applyResourceConditions(ResourceManager resourceManager, Profiler profiler, Object object, @Nullable RegistryWrapper.WrapperLookup registryLookup) {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected RegistryWrapper.WrapperLookup fabric_getRegistryLookup() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
accessWidener v2 named
|
||||
|
||||
accessible class net/minecraft/registry/ReloadableRegistries$ReloadableWrapperLookup
|
|
@ -6,6 +6,9 @@
|
|||
"DataPackContentsMixin",
|
||||
"DataProviderMixin",
|
||||
"JsonDataLoaderMixin",
|
||||
"RecipeManagerMixin",
|
||||
"ReloadableRegistriesMixin",
|
||||
"ServerAdvancementLoaderMixin",
|
||||
"SinglePreparationResourceReloaderMixin",
|
||||
"TagManagerLoaderMixin"
|
||||
],
|
||||
|
|
|
@ -19,9 +19,15 @@
|
|||
"fabricloader": ">=0.15.6"
|
||||
},
|
||||
"description": "Allows conditionally loading resources.",
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
"fabric-resource-conditions-api-v1.mixins.json"
|
||||
],
|
||||
"accessWidener": "fabric-resource-conditions-api-v1.accesswidener",
|
||||
"custom": {
|
||||
"fabric-api:module-lifecycle": "stable"
|
||||
}
|
||||
|
|
|
@ -16,7 +16,11 @@
|
|||
|
||||
package net.fabricmc.fabric.test.resource.conditions;
|
||||
|
||||
import net.minecraft.loot.LootTable;
|
||||
import net.minecraft.recipe.RecipeManager;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.registry.RegistryKeys;
|
||||
import net.minecraft.registry.ReloadableRegistries;
|
||||
import net.minecraft.test.GameTest;
|
||||
import net.minecraft.test.TestContext;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
@ -68,19 +72,18 @@ public class ConditionalResourcesTest {
|
|||
context.complete();
|
||||
}
|
||||
|
||||
/* TODO 1.20.5
|
||||
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
|
||||
public void conditionalPredicates(TestContext context) {
|
||||
// Predicates are internally handled as a kind of loot data,
|
||||
// hence the yarn name "loot condition".
|
||||
|
||||
LootManager manager = context.getWorld().getServer().getLootManager();
|
||||
ReloadableRegistries.Lookup registries = context.getWorld().getServer().getReloadableRegistries();
|
||||
|
||||
if (manager.getElementOptional(LootDataType.PREDICATES, id("loaded")).isEmpty()) {
|
||||
if (!registries.getRegistryManager().get(RegistryKeys.PREDICATE).containsId(id("loaded"))) {
|
||||
throw new AssertionError("loaded predicate should have been loaded.");
|
||||
}
|
||||
|
||||
if (manager.getElementOptional(LootDataType.PREDICATES, id("not_loaded")).isPresent()) {
|
||||
if (registries.getRegistryManager().get(RegistryKeys.PREDICATE).containsId(id("not_loaded"))) {
|
||||
throw new AssertionError("not_loaded predicate should not have been loaded.");
|
||||
}
|
||||
|
||||
|
@ -89,16 +92,16 @@ public class ConditionalResourcesTest {
|
|||
|
||||
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
|
||||
public void conditionalLootTables(TestContext context) {
|
||||
LootManager manager = context.getWorld().getServer().getLootManager();
|
||||
ReloadableRegistries.Lookup registries = context.getWorld().getServer().getReloadableRegistries();
|
||||
|
||||
if (manager.getElementOptional(LootDataType.LOOT_TABLES, id("blocks/loaded")).isEmpty()) {
|
||||
if (registries.getLootTable(RegistryKey.of(RegistryKeys.LOOT_TABLE, id("blocks/loaded"))) == LootTable.EMPTY) {
|
||||
throw new AssertionError("loaded loot table should have been loaded.");
|
||||
}
|
||||
|
||||
if (manager.getElementOptional(LootDataType.LOOT_TABLES, id("blocks/not_loaded")).isPresent()) {
|
||||
if (registries.getLootTable(RegistryKey.of(RegistryKeys.LOOT_TABLE, id("blocks/not_loaded"))) != LootTable.EMPTY) {
|
||||
throw new AssertionError("not_loaded loot table should not have been loaded.");
|
||||
}
|
||||
|
||||
context.complete();
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* 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.test.resource.conditions;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.mojang.serialization.JsonOps;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.registry.DynamicRegistryManager;
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.registry.Registry;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.registry.RegistryKeys;
|
||||
import net.minecraft.registry.RegistryWrapper;
|
||||
import net.minecraft.registry.tag.BiomeTags;
|
||||
import net.minecraft.registry.tag.BlockTags;
|
||||
import net.minecraft.registry.tag.TagKey;
|
||||
import net.minecraft.resource.featuretoggle.FeatureFlag;
|
||||
import net.minecraft.resource.featuretoggle.FeatureFlags;
|
||||
import net.minecraft.test.GameTest;
|
||||
import net.minecraft.test.TestContext;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.world.biome.BiomeKeys;
|
||||
|
||||
import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
|
||||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions;
|
||||
import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl;
|
||||
|
||||
public class DefaultResourceConditionsTest {
|
||||
private static final String TESTMOD_ID = "fabric-resource-conditions-api-v1-testmod";
|
||||
private static final String API_MOD_ID = "fabric-resource-conditions-api-v1";
|
||||
private static final String UNKNOWN_MOD_ID = "fabric-tiny-potato-api-v1";
|
||||
private static final RegistryKey<? extends Registry<Object>> UNKNOWN_REGISTRY_KEY = RegistryKey.ofRegistry(new Identifier(TESTMOD_ID, "unknown_registry"));
|
||||
private static final Identifier UNKNOWN_ENTRY_ID = new Identifier(TESTMOD_ID, "tiny_potato");
|
||||
|
||||
private void expectCondition(TestContext context, String name, ResourceCondition condition, boolean expected) {
|
||||
RegistryWrapper.WrapperLookup registryLookup = context.getWorld().getRegistryManager();
|
||||
boolean actual = condition.test(registryLookup);
|
||||
|
||||
if (actual != expected) {
|
||||
throw new AssertionError("Test \"%s\" for condition %s failed; expected %s, got %s".formatted(name, condition.getType().id(), expected, actual));
|
||||
}
|
||||
|
||||
// Test serialization
|
||||
ResourceCondition.CODEC.encodeStart(JsonOps.INSTANCE, condition).getOrThrow(message -> new AssertionError("Could not serialize \"%s\": %s".formatted(name, message)));
|
||||
}
|
||||
|
||||
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
|
||||
public void logics(TestContext context) {
|
||||
ResourceCondition alwaysTrue = ResourceConditions.alwaysTrue();
|
||||
ResourceCondition alwaysFalse = ResourceConditions.not(alwaysTrue);
|
||||
ResourceCondition trueAndTrue = ResourceConditions.and(alwaysTrue, alwaysTrue);
|
||||
ResourceCondition trueAndFalse = ResourceConditions.and(alwaysTrue, alwaysFalse);
|
||||
ResourceCondition emptyAnd = ResourceConditions.and();
|
||||
ResourceCondition trueOrFalse = ResourceConditions.or(alwaysTrue, alwaysFalse);
|
||||
ResourceCondition falseOrFalse = ResourceConditions.or(alwaysFalse, alwaysFalse);
|
||||
ResourceCondition emptyOr = ResourceConditions.or();
|
||||
|
||||
expectCondition(context, "always true", alwaysTrue, true);
|
||||
expectCondition(context, "always false", alwaysFalse, false);
|
||||
expectCondition(context, "true and true", trueAndTrue, true);
|
||||
expectCondition(context, "true and false", trueAndFalse, false);
|
||||
expectCondition(context, "vacuous truth", emptyAnd, true);
|
||||
expectCondition(context, "true or false", trueOrFalse, true);
|
||||
expectCondition(context, "false or false", falseOrFalse, false);
|
||||
expectCondition(context, "empty OR is always false", emptyOr, false);
|
||||
|
||||
context.complete();
|
||||
}
|
||||
|
||||
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
|
||||
public void allModsLoaded(TestContext context) {
|
||||
ResourceCondition testmod = ResourceConditions.allModsLoaded(TESTMOD_ID);
|
||||
ResourceCondition testmodAndApi = ResourceConditions.allModsLoaded(TESTMOD_ID, API_MOD_ID);
|
||||
ResourceCondition unknownMod = ResourceConditions.allModsLoaded(UNKNOWN_MOD_ID);
|
||||
ResourceCondition unknownAndTestmod = ResourceConditions.allModsLoaded(UNKNOWN_MOD_ID, TESTMOD_ID);
|
||||
ResourceCondition noMod = ResourceConditions.allModsLoaded();
|
||||
|
||||
expectCondition(context, "one loaded mod", testmod, true);
|
||||
expectCondition(context, "two loaded mods", testmodAndApi, true);
|
||||
expectCondition(context, "one unloaded mod", unknownMod, false);
|
||||
expectCondition(context, "both loaded and unloaded mods", unknownAndTestmod, false);
|
||||
expectCondition(context, "no mod", noMod, true);
|
||||
|
||||
context.complete();
|
||||
}
|
||||
|
||||
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
|
||||
public void anyModsLoaded(TestContext context) {
|
||||
ResourceCondition testmod = ResourceConditions.anyModsLoaded(TESTMOD_ID);
|
||||
ResourceCondition testmodAndApi = ResourceConditions.anyModsLoaded(TESTMOD_ID, API_MOD_ID);
|
||||
ResourceCondition unknownMod = ResourceConditions.anyModsLoaded(UNKNOWN_MOD_ID);
|
||||
ResourceCondition unknownAndTestmod = ResourceConditions.anyModsLoaded(UNKNOWN_MOD_ID, TESTMOD_ID);
|
||||
ResourceCondition noMod = ResourceConditions.anyModsLoaded();
|
||||
|
||||
expectCondition(context, "one loaded mod", testmod, true);
|
||||
expectCondition(context, "two loaded mods", testmodAndApi, true);
|
||||
expectCondition(context, "one unloaded mod", unknownMod, false);
|
||||
expectCondition(context, "both loaded and unloaded mods", unknownAndTestmod, true);
|
||||
expectCondition(context, "no mod", noMod, false);
|
||||
|
||||
context.complete();
|
||||
}
|
||||
|
||||
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
|
||||
public void featuresEnabled(TestContext context) {
|
||||
ResourceCondition vanilla = ResourceConditions.featuresEnabled(FeatureFlags.VANILLA);
|
||||
// Reminder: GameTest enables all features by default
|
||||
ResourceCondition vanillaAndBundle = ResourceConditions.featuresEnabled(FeatureFlags.VANILLA, FeatureFlags.BUNDLE);
|
||||
Identifier unknownId = new Identifier(TESTMOD_ID, "unknown_feature_to_test_condition");
|
||||
ResourceCondition unknown = ResourceConditions.featuresEnabled(unknownId);
|
||||
// Passing an array to avoid type ambiguity
|
||||
ResourceCondition empty = ResourceConditions.featuresEnabled(new FeatureFlag[]{});
|
||||
|
||||
expectCondition(context, "vanilla only", vanilla, true);
|
||||
expectCondition(context, "vanilla and bundle", vanillaAndBundle, true);
|
||||
expectCondition(context, "unknown feature ID", unknown, false);
|
||||
expectCondition(context, "no feature", empty, true);
|
||||
|
||||
context.complete();
|
||||
}
|
||||
|
||||
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
|
||||
public void registryContains(TestContext context) {
|
||||
// Static registry
|
||||
RegistryKey<Block> dirtKey = Registries.BLOCK.getKey(Blocks.DIRT).orElseThrow();
|
||||
ResourceCondition dirt = ResourceConditions.registryContains(dirtKey);
|
||||
ResourceCondition dirtAndUnknownBlock = ResourceConditions.registryContains(dirtKey, RegistryKey.of(RegistryKeys.BLOCK, UNKNOWN_ENTRY_ID));
|
||||
ResourceCondition emptyBlock = ResourceConditions.registryContains(RegistryKeys.BLOCK, new Identifier[]{});
|
||||
ResourceCondition unknownRegistry = ResourceConditions.registryContains(UNKNOWN_REGISTRY_KEY, UNKNOWN_ENTRY_ID);
|
||||
ResourceCondition emptyUnknown = ResourceConditions.registryContains(UNKNOWN_REGISTRY_KEY, new Identifier[]{});
|
||||
|
||||
expectCondition(context, "dirt", dirt, true);
|
||||
expectCondition(context, "dirt and unknown block", dirtAndUnknownBlock, false);
|
||||
expectCondition(context, "block registry, empty check", emptyBlock, true);
|
||||
expectCondition(context, "unknown registry, non-empty", unknownRegistry, false);
|
||||
expectCondition(context, "unknown registry, empty", emptyUnknown, true);
|
||||
|
||||
// Dynamic registry (in vitro; separate testmod needs to determine if this actually functions while loading)
|
||||
ResourceCondition plains = ResourceConditions.registryContains(BiomeKeys.PLAINS);
|
||||
ResourceCondition unknownBiome = ResourceConditions.registryContains(RegistryKey.of(RegistryKeys.BIOME, UNKNOWN_ENTRY_ID));
|
||||
ResourceCondition emptyDynamic = ResourceConditions.registryContains(RegistryKeys.BIOME, new Identifier[]{});
|
||||
|
||||
expectCondition(context, "plains", plains, true);
|
||||
expectCondition(context, "unknown biome", unknownBiome, false);
|
||||
expectCondition(context, "biome registry, empty check", emptyDynamic, true);
|
||||
|
||||
context.complete();
|
||||
}
|
||||
|
||||
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
|
||||
public void tagsPopulated(TestContext context) {
|
||||
// We need to set the tags ourselves as it is cleared outside the resource loading context.
|
||||
ResourceConditionsImpl.LOADED_TAGS.set(
|
||||
context.getWorld().getRegistryManager().streamAllRegistries().collect(Collectors.toMap(
|
||||
DynamicRegistryManager.Entry::key,
|
||||
e -> e.value().streamTags().map(TagKey::id).collect(Collectors.toUnmodifiableSet())
|
||||
))
|
||||
);
|
||||
|
||||
// Static registry
|
||||
ResourceCondition dirt = ResourceConditions.tagsPopulated(RegistryKeys.BLOCK, BlockTags.DIRT);
|
||||
ResourceCondition dirtAndUnknownBlock = ResourceConditions.tagsPopulated(RegistryKeys.BLOCK, BlockTags.DIRT, TagKey.of(RegistryKeys.BLOCK, UNKNOWN_ENTRY_ID));
|
||||
ResourceCondition emptyBlock = ResourceConditions.tagsPopulated(RegistryKeys.BLOCK);
|
||||
ResourceCondition unknownRegistry = ResourceConditions.tagsPopulated(UNKNOWN_REGISTRY_KEY, TagKey.of(UNKNOWN_REGISTRY_KEY, UNKNOWN_ENTRY_ID));
|
||||
ResourceCondition emptyUnknown = ResourceConditions.tagsPopulated(UNKNOWN_REGISTRY_KEY);
|
||||
|
||||
expectCondition(context, "dirt tag", dirt, true);
|
||||
expectCondition(context, "dirt tag and unknown tag", dirtAndUnknownBlock, false);
|
||||
expectCondition(context, "block registry, empty tag checks", emptyBlock, true);
|
||||
expectCondition(context, "unknown registry, non-empty tag checks", unknownRegistry, false);
|
||||
expectCondition(context, "unknown registry, empty tag checks", emptyUnknown, true);
|
||||
|
||||
// Dynamic registry (in vitro; separate testmod needs to determine if this actually functions while loading)
|
||||
ResourceCondition forest = ResourceConditions.tagsPopulated(RegistryKeys.BIOME, BiomeTags.IS_FOREST);
|
||||
ResourceCondition unknownBiome = ResourceConditions.tagsPopulated(RegistryKeys.BIOME, TagKey.of(RegistryKeys.BIOME, UNKNOWN_ENTRY_ID));
|
||||
ResourceCondition emptyDynamic = ResourceConditions.tagsPopulated(RegistryKeys.BIOME);
|
||||
|
||||
expectCondition(context, "forest tag", forest, true);
|
||||
expectCondition(context, "unknown biome tag", unknownBiome, false);
|
||||
expectCondition(context, "biome registry, empty tag check", emptyDynamic, true);
|
||||
|
||||
context.complete();
|
||||
}
|
||||
}
|
|
@ -10,7 +10,8 @@
|
|||
},
|
||||
"fabric:load_conditions": [
|
||||
{
|
||||
"condition": "fabric:item_tags_populated",
|
||||
"condition": "fabric:tags_populated",
|
||||
"registry": "minecraft:item",
|
||||
"values": [
|
||||
"fabric-resource-conditions-api-v1-testmod:test_condition"
|
||||
]
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
},
|
||||
"entrypoints": {
|
||||
"fabric-gametest": [
|
||||
"net.fabricmc.fabric.test.resource.conditions.ConditionalResourcesTest"
|
||||
"net.fabricmc.fabric.test.resource.conditions.ConditionalResourcesTest",
|
||||
"net.fabricmc.fabric.test.resource.conditions.DefaultResourceConditionsTest"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue