From 280be3abc9db0bd70cc116cd61af833454bd9120 Mon Sep 17 00:00:00 2001
From: apple502j <33279053+apple502j@users.noreply.github.com>
Date: Sun, 27 Nov 2022 05:00:49 +0900
Subject: [PATCH] Resource Conditions: add feature_enabled (#2658)

* Resource Conditions: add feature_enabled

* Fix impl

* Some refactors

* Address reviews

* Update testmod

* Fix checkstyle

* Move javadoc

* Sort identifiers
---
 .../v1/DefaultResourceConditions.java         | 16 ++++++-
 .../conditions/ResourceConditionsImpl.java    | 44 +++++++++++++++++--
 .../conditions/DataPackContentsMixin.java     | 25 ++++++++---
 .../conditions/ConditionalResourcesTest.java  |  6 ++-
 .../recipes/features_enabled.json             | 20 +++++++++
 5 files changed, 101 insertions(+), 10 deletions(-)
 create mode 100644 fabric-resource-conditions-api-v1/src/testmod/resources/data/fabric-resource-conditions-api-v1-testmod/recipes/features_enabled.json

diff --git a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/api/resource/conditions/v1/DefaultResourceConditions.java b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/api/resource/conditions/v1/DefaultResourceConditions.java
index b57d6a562..8e5c029df 100644
--- a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/api/resource/conditions/v1/DefaultResourceConditions.java
+++ b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/api/resource/conditions/v1/DefaultResourceConditions.java
@@ -20,10 +20,11 @@ import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
 
 import net.minecraft.block.Block;
-import net.minecraft.registry.RegistryKeys;
 import net.minecraft.fluid.Fluid;
 import net.minecraft.item.Item;
+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;
 
@@ -42,6 +43,7 @@ public final class DefaultResourceConditions {
 	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");
 
 	/**
 	 * Creates a NOT condition that returns true if its child condition is false, and false if its child is true.
@@ -146,6 +148,17 @@ public final class DefaultResourceConditions {
 		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);
+	}
+
 	static void init() {
 		// init static
 	}
@@ -169,6 +182,7 @@ public final class DefaultResourceConditions {
 		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);
 	}
 
 	private DefaultResourceConditions() {
diff --git a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/impl/resource/conditions/ResourceConditionsImpl.java b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/impl/resource/conditions/ResourceConditionsImpl.java
index 0c51786e6..1525edfa7 100644
--- a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/impl/resource/conditions/ResourceConditionsImpl.java
+++ b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/impl/resource/conditions/ResourceConditionsImpl.java
@@ -20,6 +20,8 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
 
 import com.google.common.base.Preconditions;
 import com.google.gson.JsonArray;
@@ -31,14 +33,17 @@ import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+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.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.minecraft.registry.Registry;
-import net.minecraft.registry.entry.RegistryEntry;
-import net.minecraft.registry.RegistryKey;
 
 import net.fabricmc.fabric.api.resource.conditions.v1.ConditionJsonProvider;
 import net.fabricmc.loader.api.FabricLoader;
@@ -204,4 +209,37 @@ public final class ResourceConditionsImpl {
 
 		return true;
 	}
+
+	public static ConditionJsonProvider featuresEnabled(Identifier id, final FeatureFlag... features) {
+		final Set<Identifier> ids = new TreeSet<>(FeatureFlags.FEATURE_MANAGER.toId(FeatureFlags.FEATURE_MANAGER.featureSetOf(features)));
+
+		return new ConditionJsonProvider() {
+			@Override
+			public Identifier getConditionId() {
+				return id;
+			}
+
+			@Override
+			public void writeParameters(JsonObject object) {
+				JsonArray array = new JsonArray();
+
+				for (Identifier id : ids) {
+					array.add(id.toString());
+				}
+
+				object.add("features", array);
+			}
+		};
+	}
+
+	public static ThreadLocal<FeatureSet> currentFeature = ThreadLocal.withInitial(() -> FeatureFlags.DEFAULT_ENABLED_FEATURES);
+
+	public static 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);
+		});
+
+		return set.isSubsetOf(currentFeature.get());
+	}
 }
diff --git a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataPackContentsMixin.java b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataPackContentsMixin.java
index 25e0bc25d..1eb18b40a 100644
--- a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataPackContentsMixin.java
+++ b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataPackContentsMixin.java
@@ -16,22 +16,29 @@
 
 package net.fabricmc.fabric.mixin.resource.conditions;
 
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+
 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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
-import net.minecraft.server.DataPackContents;
 import net.minecraft.registry.DynamicRegistryManager;
+import net.minecraft.resource.ResourceManager;
+import net.minecraft.resource.featuretoggle.FeatureSet;
+import net.minecraft.server.DataPackContents;
+import net.minecraft.server.command.CommandManager;
 
 import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl;
 
-/**
- * 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.
- */
 @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")
@@ -39,4 +46,12 @@ public class DataPackContentsMixin {
 	public void hookRefresh(DynamicRegistryManager dynamicRegistryManager, CallbackInfo ci) {
 		ResourceConditionsImpl.clearTags();
 	}
+
+	@Inject(
+			method = "reload",
+			at = @At("HEAD")
+	)
+	private static void hookReload(ResourceManager manager, DynamicRegistryManager.Immutable dynamicRegistryManager, FeatureSet enabledFeatures, CommandManager.RegistrationEnvironment environment, int functionPermissionLevel, Executor prepareExecutor, Executor applyExecutor, CallbackInfoReturnable<CompletableFuture<DataPackContents>> cir) {
+		ResourceConditionsImpl.currentFeature.set(enabledFeatures);
+	}
 }
diff --git a/fabric-resource-conditions-api-v1/src/testmod/java/net/fabricmc/fabric/test/resource/conditions/ConditionalResourcesTest.java b/fabric-resource-conditions-api-v1/src/testmod/java/net/fabricmc/fabric/test/resource/conditions/ConditionalResourcesTest.java
index 3484953f9..707d704bc 100644
--- a/fabric-resource-conditions-api-v1/src/testmod/java/net/fabricmc/fabric/test/resource/conditions/ConditionalResourcesTest.java
+++ b/fabric-resource-conditions-api-v1/src/testmod/java/net/fabricmc/fabric/test/resource/conditions/ConditionalResourcesTest.java
@@ -58,8 +58,12 @@ public class ConditionalResourcesTest {
 			throw new AssertionError("tags_not_populated recipe should not have been loaded.");
 		}
 
+		if (manager.get(id("features_enabled")).isEmpty()) {
+			throw new AssertionError("features_enabled recipe should have been loaded.");
+		}
+
 		long loadedRecipes = manager.values().stream().filter(r -> r.getId().getNamespace().equals(MOD_ID)).count();
-		if (loadedRecipes != 4) throw new AssertionError("Unexpected loaded recipe count: " + loadedRecipes);
+		if (loadedRecipes != 5) throw new AssertionError("Unexpected loaded recipe count: " + loadedRecipes);
 
 		context.complete();
 	}
diff --git a/fabric-resource-conditions-api-v1/src/testmod/resources/data/fabric-resource-conditions-api-v1-testmod/recipes/features_enabled.json b/fabric-resource-conditions-api-v1/src/testmod/resources/data/fabric-resource-conditions-api-v1-testmod/recipes/features_enabled.json
new file mode 100644
index 000000000..833b55006
--- /dev/null
+++ b/fabric-resource-conditions-api-v1/src/testmod/resources/data/fabric-resource-conditions-api-v1-testmod/recipes/features_enabled.json
@@ -0,0 +1,20 @@
+{
+  "type": "minecraft:crafting_shapeless",
+  "ingredients": [
+    {
+      "item": "minecraft:blue_dye"
+    }
+  ],
+  "result": {
+    "item": "minecraft:diamond"
+  },
+  "fabric:load_conditions": [
+    {
+      "condition": "fabric:features_enabled",
+      "features": [
+        "minecraft:vanilla",
+        "minecraft:bundle"
+      ]
+    }
+  ]
+}