diff --git a/fabric-resource-conditions-api-v1/build.gradle b/fabric-resource-conditions-api-v1/build.gradle index 1514172c8..20dfb1f90 100644 --- a/fabric-resource-conditions-api-v1/build.gradle +++ b/fabric-resource-conditions-api-v1/build.gradle @@ -4,4 +4,8 @@ loom { accessWidenerPath = file("src/main/resources/fabric-resource-conditions-api-v1.accesswidener") } -testDependencies(project, [':fabric-gametest-api-v1']) +testDependencies(project, [ + ':fabric-gametest-api-v1', + ':fabric-lifecycle-events-v1', + ':fabric-resource-loader-v0' +]) diff --git a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/api/resource/conditions/v1/ResourceConditions.java b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/api/resource/conditions/v1/ResourceConditions.java index e232fb4ad..6129b74e5 100644 --- a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/api/resource/conditions/v1/ResourceConditions.java +++ b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/api/resource/conditions/v1/ResourceConditions.java @@ -48,6 +48,11 @@ public final class ResourceConditions { */ public static final String CONDITIONS_KEY = "fabric:load_conditions"; + /** + * The JSON key for conditional overlays in pack.mcmeta files. + */ + public static final String OVERLAYS_KEY = "fabric:overlays"; + private ResourceConditions() { } diff --git a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/impl/resource/conditions/OverlayConditionsMetadata.java b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/impl/resource/conditions/OverlayConditionsMetadata.java new file mode 100644 index 000000000..a856fe03b --- /dev/null +++ b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/impl/resource/conditions/OverlayConditionsMetadata.java @@ -0,0 +1,60 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import net.minecraft.resource.metadata.ResourceMetadataSerializer; + +import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition; +import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions; + +public record OverlayConditionsMetadata(List overlays) { + public static final Codec CODEC = Entry.CODEC.listOf().fieldOf("entries").xmap(OverlayConditionsMetadata::new, OverlayConditionsMetadata::overlays).codec(); + public static final ResourceMetadataSerializer SERIALIZER = ResourceMetadataSerializer.fromCodec(ResourceConditions.OVERLAYS_KEY, CODEC); + + public List appliedOverlays() { + List appliedOverlays = new ArrayList<>(); + + for (Entry entry : this.overlays()) { + if (entry.condition().test(null)) { + appliedOverlays.add(entry.directory()); + } + } + + return appliedOverlays; + } + + public record Entry(String directory, ResourceCondition condition) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.validate(Entry::validateDirectory).fieldOf("directory").forGetter(Entry::directory), + ResourceCondition.CODEC.fieldOf("condition").forGetter(Entry::condition) + ).apply(instance, Entry::new)); + private static final Pattern DIRECTORY_NAME_PATTERN = Pattern.compile("[-_a-zA-Z0-9.]+"); + + private static DataResult validateDirectory(String directory) { + boolean valid = DIRECTORY_NAME_PATTERN.matcher(directory).matches(); + return valid ? DataResult.success(directory) : DataResult.error(() -> "Directory name is invalid"); + } + } +} diff --git a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/ResourcePackProfileMixin.java b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/ResourcePackProfileMixin.java new file mode 100644 index 000000000..ae0f0d0ee --- /dev/null +++ b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/ResourcePackProfileMixin.java @@ -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.mixin.resource.conditions; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.llamalad7.mixinextras.sugar.Local; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +import net.minecraft.resource.ResourcePack; +import net.minecraft.resource.ResourcePackProfile; + +import net.fabricmc.fabric.impl.resource.conditions.OverlayConditionsMetadata; + +@Mixin(ResourcePackProfile.class) +public class ResourcePackProfileMixin { + @ModifyVariable(method = "loadMetadata", at = @At("STORE")) + private static List applyOverlayConditions(List overlays, @Local ResourcePack resourcePack) throws IOException { + List appliedOverlays = new ArrayList<>(overlays); + OverlayConditionsMetadata overlayMetadata = resourcePack.parseMetadata(OverlayConditionsMetadata.SERIALIZER); + + if (overlayMetadata != null) { + appliedOverlays.addAll(overlayMetadata.appliedOverlays()); + } + + return List.copyOf(appliedOverlays); + } +} diff --git a/fabric-resource-conditions-api-v1/src/main/resources/fabric-resource-conditions-api-v1.mixins.json b/fabric-resource-conditions-api-v1/src/main/resources/fabric-resource-conditions-api-v1.mixins.json index fc394fe8f..5838e2c4e 100644 --- a/fabric-resource-conditions-api-v1/src/main/resources/fabric-resource-conditions-api-v1.mixins.json +++ b/fabric-resource-conditions-api-v1/src/main/resources/fabric-resource-conditions-api-v1.mixins.json @@ -9,6 +9,7 @@ "RecipeManagerMixin", "RegistryLoaderMixin", "ReloadableRegistriesMixin", + "ResourcePackProfileMixin", "ServerAdvancementLoaderMixin", "SinglePreparationResourceReloaderMixin", "TagManagerLoaderMixin" 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 3d84c4560..114faf0c2 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 @@ -121,4 +121,19 @@ public class ConditionalResourcesTest { context.complete(); } + + @GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE) + public void conditionalOverlays(TestContext context) { + ReloadableRegistries.Lookup registries = context.getWorld().getServer().getReloadableRegistries(); + + if (!registries.getRegistryManager().get(RegistryKeys.PREDICATE).containsId(id("do_overlay"))) { + throw new AssertionError("do_overlay predicate should have been overlayed."); + } + + if (registries.getRegistryManager().get(RegistryKeys.PREDICATE).containsId(id("dont_overlay"))) { + throw new AssertionError("dont_overlay predicate should not have been overlayed."); + } + + context.complete(); + } } diff --git a/fabric-resource-conditions-api-v1/src/testmod/resources/do_overlay/data/fabric-resource-conditions-api-v1-testmod/predicate/do_overlay.json b/fabric-resource-conditions-api-v1/src/testmod/resources/do_overlay/data/fabric-resource-conditions-api-v1-testmod/predicate/do_overlay.json new file mode 100644 index 000000000..2a5a44359 --- /dev/null +++ b/fabric-resource-conditions-api-v1/src/testmod/resources/do_overlay/data/fabric-resource-conditions-api-v1-testmod/predicate/do_overlay.json @@ -0,0 +1,3 @@ +{ + "condition": "minecraft:survives_explosion" +} diff --git a/fabric-resource-conditions-api-v1/src/testmod/resources/dont_overlay/data/fabric-resource-conditions-api-v1-testmod/predicate/dont_overlay.json b/fabric-resource-conditions-api-v1/src/testmod/resources/dont_overlay/data/fabric-resource-conditions-api-v1-testmod/predicate/dont_overlay.json new file mode 100644 index 000000000..2a5a44359 --- /dev/null +++ b/fabric-resource-conditions-api-v1/src/testmod/resources/dont_overlay/data/fabric-resource-conditions-api-v1-testmod/predicate/dont_overlay.json @@ -0,0 +1,3 @@ +{ + "condition": "minecraft:survives_explosion" +} diff --git a/fabric-resource-conditions-api-v1/src/testmod/resources/pack.mcmeta b/fabric-resource-conditions-api-v1/src/testmod/resources/pack.mcmeta new file mode 100644 index 000000000..079554319 --- /dev/null +++ b/fabric-resource-conditions-api-v1/src/testmod/resources/pack.mcmeta @@ -0,0 +1,25 @@ +{ + "pack": { + "pack_format": 48, + "description": "" + }, + "fabric:overlays": { + "entries": [ + { + "directory": "do_overlay", + "condition": { + "condition": "fabric:true" + } + }, + { + "directory": "dont_overlay", + "condition": { + "condition": "fabric:not", + "value": { + "condition": "fabric:true" + } + } + } + ] + } +} diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourceManagerHelperImpl.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourceManagerHelperImpl.java index 9621b69b7..afd382e1d 100644 --- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourceManagerHelperImpl.java +++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourceManagerHelperImpl.java @@ -37,6 +37,7 @@ import org.slf4j.LoggerFactory; import net.minecraft.recipe.RecipeManager; import net.minecraft.registry.RegistryWrapper; +import net.minecraft.resource.OverlayResourcePack; import net.minecraft.resource.ResourcePack; import net.minecraft.resource.ResourcePackInfo; import net.minecraft.resource.ResourcePackPosition; @@ -145,8 +146,19 @@ public class ResourceManagerHelperImpl implements ResourceManagerHelper { @Override public ResourcePack openWithOverlays(ResourcePackInfo var1, ResourcePackProfile.Metadata metadata) { - // Don't support overlays in builtin res packs. - return entry.getRight(); + ModNioResourcePack pack = entry.getRight(); + + if (metadata.overlays().isEmpty()) { + return pack; + } + + List overlays = new ArrayList<>(metadata.overlays().size()); + + for (String overlay : metadata.overlays()) { + overlays.add(pack.createOverlay(overlay)); + } + + return new OverlayResourcePack(pack, overlays); } }, resourceType, info2); consumer.accept(profile);