diff --git a/patches/minecraft/net/minecraft/core/HolderSet.java.patch b/patches/minecraft/net/minecraft/core/HolderSet.java.patch new file mode 100644 index 0000000000..44daa4c382 --- /dev/null +++ b/patches/minecraft/net/minecraft/core/HolderSet.java.patch @@ -0,0 +1,33 @@ +--- a/net/minecraft/core/HolderSet.java ++++ b/net/minecraft/core/HolderSet.java +@@ -14,7 +_,7 @@ + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + +-public interface HolderSet extends Iterable> { ++public interface HolderSet extends Iterable>, net.minecraftforge.common.extensions.IForgeHolderSet { + Stream> m_203614_(); + + int m_203632_(); +@@ -120,6 +_,9 @@ + + public void m_205835_(List> p_205836_) { + this.f_205830_ = List.copyOf(p_205836_); ++ for (Runnable runnable : this.invalidationCallbacks) { ++ runnable.run(); // FORGE: invalidate listeners when tags rebind ++ } + } + + public TagKey m_205839_() { +@@ -144,6 +_,11 @@ + + public boolean m_207277_(Registry p_211049_) { + return this.f_211044_ == p_211049_; ++ } ++ // FORGE: Keep a list of invalidation callbacks so they can be run when tags rebind ++ private List invalidationCallbacks = new java.util.ArrayList<>(); ++ public void addInvalidationListener(Runnable runnable) { ++ invalidationCallbacks.add(runnable); + } + } + } diff --git a/patches/minecraft/net/minecraft/resources/HolderSetCodec.java.patch b/patches/minecraft/net/minecraft/resources/HolderSetCodec.java.patch new file mode 100644 index 0000000000..9eb0cf6812 --- /dev/null +++ b/patches/minecraft/net/minecraft/resources/HolderSetCodec.java.patch @@ -0,0 +1,45 @@ +--- a/net/minecraft/resources/HolderSetCodec.java ++++ b/net/minecraft/resources/HolderSetCodec.java +@@ -20,6 +_,8 @@ + private final Codec> f_206656_; + private final Codec>> f_206657_; + private final Codec, List>>> f_206658_; ++ private final Codec> forgeDispatchCodec; ++ private final Codec, Either, List>>>> combinedCodec; + + private static Codec>> m_206667_(Codec> p_206668_, boolean p_206669_) { + Function>, DataResult>>> function = ExtraCodecs.m_203984_(Holder::m_203376_); +@@ -42,6 +_,10 @@ + this.f_206656_ = p_206661_; + this.f_206657_ = m_206667_(p_206661_, p_206662_); + this.f_206658_ = Codec.either(TagKey.m_203886_(p_206660_), this.f_206657_); ++ // FORGE: make registry-specific dispatch codec and make forge-or-vanilla either codec ++ this.forgeDispatchCodec = ExtraCodecs.m_184415_(() -> net.minecraftforge.registries.ForgeRegistries.HOLDER_SET_TYPES.get().getCodec()) ++ .dispatch(net.minecraftforge.registries.holdersets.ICustomHolderSet::type, type -> type.makeCodec(p_206660_, p_206661_, p_206662_)); ++ this.combinedCodec = new ExtraCodecs.EitherCodec<>(this.forgeDispatchCodec, this.f_206658_); + } + + public DataResult, T>> decode(DynamicOps p_206696_, T p_206697_) { +@@ -49,9 +_,10 @@ + Optional> optional = registryops.m_206826_(this.f_206655_); + if (optional.isPresent()) { + Registry registry = optional.get(); +- return this.f_206658_.decode(p_206696_, p_206697_).map((p_206682_) -> { ++ // FORGE: use the wrapped codec to decode custom/tag/list instead of just tag/list ++ return this.combinedCodec.decode(p_206696_, p_206697_).map((p_206682_) -> { + return p_206682_.mapFirst((p_206679_) -> { +- return p_206679_.map(registry::m_203561_, HolderSet::m_205800_); ++ return p_206679_.map(java.util.function.Function.identity(), tagOrList -> tagOrList.map(registry::m_203561_, HolderSet::m_205800_)); + }); + }); + } +@@ -68,6 +_,9 @@ + return DataResult.error("HolderSet " + p_206674_ + " is not valid in current registry set"); + } + ++ // FORGE: use the dispatch codec to encode custom holdersets, otherwise fall back to vanilla tag/list ++ if (p_206674_ instanceof net.minecraftforge.registries.holdersets.ICustomHolderSet customHolderSet) ++ return this.forgeDispatchCodec.encode(customHolderSet, p_206675_, p_206676_); + return this.f_206658_.encode(p_206674_.m_203440_().mapRight(List::copyOf), p_206675_, p_206676_); + } + } diff --git a/src/main/java/net/minecraftforge/common/ForgeHooks.java b/src/main/java/net/minecraftforge/common/ForgeHooks.java index 6176e8d18e..c7cb25cc3b 100644 --- a/src/main/java/net/minecraftforge/common/ForgeHooks.java +++ b/src/main/java/net/minecraftforge/common/ForgeHooks.java @@ -25,6 +25,7 @@ import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; +import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; import com.mojang.serialization.Lifecycle; @@ -145,6 +146,7 @@ import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.ForgeRegistry; import net.minecraftforge.registries.GameData; import net.minecraftforge.registries.RegistryManager; +import net.minecraftforge.registries.holdersets.ICustomHolderSet; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -152,6 +154,7 @@ import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import net.minecraft.ChatFormatting; +import net.minecraft.resources.HolderSetCodec; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.damagesource.DamageSource; diff --git a/src/main/java/net/minecraftforge/common/ForgeMod.java b/src/main/java/net/minecraftforge/common/ForgeMod.java index 59dd3cc8e7..8d9e42d727 100644 --- a/src/main/java/net/minecraftforge/common/ForgeMod.java +++ b/src/main/java/net/minecraftforge/common/ForgeMod.java @@ -61,6 +61,11 @@ import net.minecraftforge.fml.*; import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.event.lifecycle.*; import net.minecraftforge.registries.*; +import net.minecraftforge.registries.holdersets.AndHolderSet; +import net.minecraftforge.registries.holdersets.AnyHolderSet; +import net.minecraftforge.registries.holdersets.HolderSetType; +import net.minecraftforge.registries.holdersets.NotHolderSet; +import net.minecraftforge.registries.holdersets.OrHolderSet; import net.minecraftforge.network.NetworkConstants; import net.minecraftforge.event.server.ServerStoppingEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; @@ -120,6 +125,7 @@ public class ForgeMod private static final DeferredRegister> COMMAND_ARGUMENT_TYPES = DeferredRegister.create(Registry.COMMAND_ARGUMENT_TYPE_REGISTRY, "forge"); private static final DeferredRegister> BIOME_MODIFIER_SERIALIZERS = DeferredRegister.create(ForgeRegistries.Keys.BIOME_MODIFIER_SERIALIZERS, "forge"); private static final DeferredRegister> STRUCTURE_MODIFIER_SERIALIZERS = DeferredRegister.create(ForgeRegistries.Keys.STRUCTURE_MODIFIER_SERIALIZERS, "forge"); + private static final DeferredRegister HOLDER_SET_TYPES = DeferredRegister.create(ForgeRegistries.Keys.HOLDER_SET_TYPES, "forge"); @SuppressWarnings({ "unchecked", "rawtypes" }) private static final RegistryObject ENUM_COMMAND_ARGUMENT_TYPE = COMMAND_ARGUMENT_TYPES.register("enum", () -> @@ -212,6 +218,27 @@ public class ForgeMod */ public static final RegistryObject> NONE_STRUCTURE_MODIFIER_TYPE = STRUCTURE_MODIFIER_SERIALIZERS.register("none", () -> Codec.unit(NoneStructureModifier.INSTANCE)); + /** + * Stock holder set type that represents any/all values in a registry. Can be used in a holderset object with {@code { "type": "forge:any" }} + */ + public static final RegistryObject ANY_HOLDER_SET = HOLDER_SET_TYPES.register("any", () -> AnyHolderSet::codec); + + /** + * Stock holder set type that represents an intersection of other holdersets. Can be used in a holderset object with {@code { "type": "forge:and", "values": [list of holdersets] }} + */ + public static final RegistryObject AND_HOLDER_SET = HOLDER_SET_TYPES.register("and", () -> AndHolderSet::codec); + + /** + * Stock holder set type that represents a union of other holdersets. Can be used in a holderset object with {@code { "type": "forge:or", "values": [list of holdersets] }} + */ + public static final RegistryObject OR_HOLDER_SET = HOLDER_SET_TYPES.register("or", () -> OrHolderSet::codec); + + /** + *

Stock holder set type that represents all values in a registry except those in another given set. + * Can be used in a holderset object with {@code { "type": "forge:not", "value": holderset }}

+ */ + public static final RegistryObject NOT_HOLDER_SET = HOLDER_SET_TYPES.register("not", () -> NotHolderSet::codec); + private static final DeferredRegister VANILLA_FLUID_TYPES = DeferredRegister.create(ForgeRegistries.Keys.FLUID_TYPES, "minecraft"); public static final RegistryObject EMPTY_TYPE = VANILLA_FLUID_TYPES.register("empty", () -> @@ -418,6 +445,7 @@ public class ForgeMod COMMAND_ARGUMENT_TYPES.register(modEventBus); BIOME_MODIFIER_SERIALIZERS.register(modEventBus); STRUCTURE_MODIFIER_SERIALIZERS.register(modEventBus); + HOLDER_SET_TYPES.register(modEventBus); VANILLA_FLUID_TYPES.register(modEventBus); MinecraftForge.EVENT_BUS.addListener(this::serverStopping); MinecraftForge.EVENT_BUS.addListener(this::missingSoundMapping); diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeHolderSet.java b/src/main/java/net/minecraftforge/common/extensions/IForgeHolderSet.java new file mode 100644 index 0000000000..efa633a06b --- /dev/null +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeHolderSet.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.common.extensions; + +public interface IForgeHolderSet +{ + /** + *

Adds a callback to run when this holderset's contents invalidate (i.e. because tags were rebound).

+ * + *

The intended usage and use case is with composite holdersets that need to cache sets/list based on other + * holdersets, which may be mutable (because they are tag-based or themselves composite holdersets). + * Composite holdersets should use this to add callbacks to each of their component holdersets when constructed.

+ * + * @param runnable Runnable to invoke when this component holderset's contents are no longer valid. + * This runnable should only clear caches and allow them to be lazily reevaluated later, + * as not all tag holdersets may have been rebound when this is called. + * This runnable should also invalidate all of the caller's listeners. + */ + default public void addInvalidationListener(Runnable runnable) + { + // noop by default, mutable/composite holdersets must override + } +} diff --git a/src/main/java/net/minecraftforge/registries/ForgeDeferredRegistriesSetup.java b/src/main/java/net/minecraftforge/registries/ForgeDeferredRegistriesSetup.java index 5b89582e14..951b11a7ab 100644 --- a/src/main/java/net/minecraftforge/registries/ForgeDeferredRegistriesSetup.java +++ b/src/main/java/net/minecraftforge/registries/ForgeDeferredRegistriesSetup.java @@ -26,6 +26,7 @@ public class ForgeDeferredRegistriesSetup ForgeRegistries.DEFERRED_BIOME_MODIFIERS.register(modEventBus); ForgeRegistries.DEFERRED_STRUCTURE_MODIFIER_SERIALIZERS.register(modEventBus); ForgeRegistries.DEFERRED_STRUCTURE_MODIFIERS.register(modEventBus); + ForgeRegistries.DEFERRED_HOLDER_SET_TYPES.register(modEventBus); setup = true; } diff --git a/src/main/java/net/minecraftforge/registries/ForgeRegistries.java b/src/main/java/net/minecraftforge/registries/ForgeRegistries.java index 411597d002..1549a4f6f0 100644 --- a/src/main/java/net/minecraftforge/registries/ForgeRegistries.java +++ b/src/main/java/net/minecraftforge/registries/ForgeRegistries.java @@ -45,6 +45,7 @@ import net.minecraftforge.common.Tags; import net.minecraftforge.common.loot.IGlobalLootModifier; import net.minecraftforge.common.world.BiomeModifier; import net.minecraftforge.fluids.FluidType; +import net.minecraftforge.registries.holdersets.HolderSetType; import java.util.function.Supplier; import net.minecraftforge.common.world.StructureModifier; @@ -127,6 +128,12 @@ public class ForgeRegistries * Use {@link Keys#FLUID_TYPES} to create a {@link DeferredRegister}. */ public static final Supplier> FLUID_TYPES = DEFERRED_FLUID_TYPES.makeRegistry(GameData::getFluidTypeRegistryBuilder); + static final DeferredRegister DEFERRED_HOLDER_SET_TYPES = DeferredRegister.create(Keys.HOLDER_SET_TYPES, "forge"); + /** + * Calling {@link Supplier#get()} before {@link NewRegistryEvent} is fired will result in a null registry returned. + * Use {@link Keys#HOLDER_SET_TYPES} to create a {@link DeferredRegister}. + */ + public static final Supplier> HOLDER_SET_TYPES = DEFERRED_HOLDER_SET_TYPES.makeRegistry(GameData::getHolderSetTypeRegistryBuilder); // Custom forge datapack registries static final DeferredRegister DEFERRED_BIOME_MODIFIERS = DeferredRegister.create(Keys.BIOME_MODIFIERS, "forge"); @@ -175,6 +182,7 @@ public class ForgeRegistries public static final ResourceKey>> BIOME_MODIFIER_SERIALIZERS = key("forge:biome_modifier_serializers"); public static final ResourceKey>> STRUCTURE_MODIFIER_SERIALIZERS = key("forge:structure_modifier_serializers"); public static final ResourceKey> FLUID_TYPES = key("forge:fluid_type"); + public static final ResourceKey> HOLDER_SET_TYPES = key("forge:holder_set_type"); // Forge Dynamic public static final ResourceKey> BIOME_MODIFIERS = key("forge:biome_modifier"); diff --git a/src/main/java/net/minecraftforge/registries/GameData.java b/src/main/java/net/minecraftforge/registries/GameData.java index 79799e81e8..b3031babf5 100644 --- a/src/main/java/net/minecraftforge/registries/GameData.java +++ b/src/main/java/net/minecraftforge/registries/GameData.java @@ -48,6 +48,8 @@ import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.StartupMessageManager; import net.minecraftforge.fml.util.EnhancedRuntimeException; import net.minecraftforge.fml.util.thread.EffectiveSide; +import net.minecraftforge.registries.holdersets.HolderSetType; + import org.apache.commons.lang3.Validate; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -164,6 +166,11 @@ public class GameData { return makeRegistry(FLUID_TYPES).disableSaving(); } + + static RegistryBuilder getHolderSetTypeRegistryBuilder() + { + return new RegistryBuilder().disableSaving().disableSync(); + } private static RegistryBuilder makeRegistry(ResourceKey> key) { diff --git a/src/main/java/net/minecraftforge/registries/holdersets/AndHolderSet.java b/src/main/java/net/minecraftforge/registries/holdersets/AndHolderSet.java new file mode 100644 index 0000000000..570e72c916 --- /dev/null +++ b/src/main/java/net/minecraftforge/registries/holdersets/AndHolderSet.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.registries.holdersets; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.mojang.serialization.Codec; + +import net.minecraft.core.Holder; +import net.minecraft.core.HolderSet; +import net.minecraft.core.Registry; +import net.minecraft.resources.HolderSetCodec; +import net.minecraft.resources.ResourceKey; +import net.minecraftforge.common.ForgeMod; + +/** + *

Holderset that represents an intersection of other holdersets. Json format:

+ *
+ * {
+ *   "type": "forge:and",
+ *   "values":
+ *   [
+ *      // list of sub-holdersets (strings, lists, or objects)
+ *   ]
+ * }
+ * 
+ */ +public class AndHolderSet extends CompositeHolderSet +{ + public static Codec> codec(ResourceKey> registryKey, Codec> holderCodec, boolean forceList) + { + return HolderSetCodec.create(registryKey, holderCodec, forceList) + .listOf() + .xmap(AndHolderSet::new, AndHolderSet::getComponents) + .fieldOf("values") + .codec(); + } + + public AndHolderSet(List> values) + { + super(values); + } + + @Override + public HolderSetType type() + { + return ForgeMod.AND_HOLDER_SET.get(); + } + + @Override + protected Set> createSet() + { + List> components = this.getComponents(); + if (components.size() < 1) + { + return Set.of(); + } + if (components.size() == 1) + { + return components.get(0).stream().collect(Collectors.toSet()); + } + + List> remainingComponents = components.subList(1, components.size()); + return components.get(0) + .stream() + .filter(holder -> remainingComponents.stream().allMatch(holderset -> holderset.contains(holder))) + .collect(Collectors.toSet()); + } + + @Override + public String toString() + { + return "AndSet[" + this.getComponents() + "]"; + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/registries/holdersets/AnyHolderSet.java b/src/main/java/net/minecraftforge/registries/holdersets/AnyHolderSet.java new file mode 100644 index 0000000000..614d3613e2 --- /dev/null +++ b/src/main/java/net/minecraftforge/registries/holdersets/AnyHolderSet.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.registries.holdersets; + +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.tags.TagKey; +import net.minecraft.util.RandomSource; +import net.minecraftforge.common.ForgeMod; + +/** + *

Holderset that represents all elements of a registry. Json format:

+ *
+ * {
+ *   "type": "forge:any"
+ * }
+ * 
+ */ +public record AnyHolderSet(Registry registry) implements ICustomHolderSet +{ + public static Codec> codec(ResourceKey> registryKey, Codec> holderCodec, boolean forceList) + { + return RegistryOps.retrieveRegistry(registryKey) + .xmap(AnyHolderSet::new, AnyHolderSet::registry) + .codec(); + } + + @Override + public HolderSetType type() + { + return ForgeMod.ANY_HOLDER_SET.get(); + } + + @Override + public Iterator> iterator() + { + return this.stream().iterator(); + } + + @Override + public Stream> stream() + { + return registry.holders().map(Function.identity()); + } + + @Override + public int size() + { + return this.registry.size(); + } + + @Override + public Either, List>> unwrap() + { + return Either.right(this.stream().toList()); + } + + @Override + public Optional> getRandomElement(RandomSource random) + { + return this.registry.getRandom(random); + } + + @Override + public Holder get(int i) + { + return this.registry.getHolder(i).orElseThrow(() -> new NoSuchElementException("No element " + i + " in registry " + this.registry.key())); + } + + @Override + public boolean contains(Holder holder) + { + return holder.unwrapKey().map(this.registry::containsKey).orElse(false); + } + + @Override + public boolean isValidInRegistry(Registry registry) + { + return this.registry == registry; + } + + @Override + public String toString() + { + return "AnySet(" + this.registry.key() + ")"; + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/registries/holdersets/CompositeHolderSet.java b/src/main/java/net/minecraftforge/registries/holdersets/CompositeHolderSet.java new file mode 100644 index 0000000000..1f356317d7 --- /dev/null +++ b/src/main/java/net/minecraftforge/registries/holdersets/CompositeHolderSet.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.registries.holdersets; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import org.jetbrains.annotations.Nullable; + +import com.mojang.datafixers.util.Either; + +import net.minecraft.core.Holder; +import net.minecraft.core.HolderSet; +import net.minecraft.core.Registry; +import net.minecraft.tags.TagKey; +import net.minecraft.util.RandomSource; + +/** + * Composite holdersets have component holdersets and possibly owner holdersets + * (which have this holderset as a component). + * When their component holderset(s) invalidate, they clear any cached data and then + * invalidate their owner holdersets. + */ +public abstract class CompositeHolderSet implements ICustomHolderSet +{ + private final List owners = new ArrayList<>(); + private final List> components; + + @Nullable + private Set> set = null; + @Nullable + private List> list = null; + + public CompositeHolderSet(List> components) + { + this.components = components; + for (HolderSet holderset : components) + { + holderset.addInvalidationListener(this::invalidate); + } + } + + /** + * {@return immutable Set of Holders given this composite holderset's component holdersets} + */ + protected abstract Set> createSet(); + + public List> getComponents() + { + return this.components; + } + + public Set> getSet() + { + Set> thisSet = this.set; + if (thisSet == null) + { + Set> set = this.createSet(); + this.set = set; + return set; + } + else + { + return thisSet; + } + } + + public List> getList() + { + List> thisList = this.list; + if (thisList == null) + { + List> list = List.copyOf(this.getSet()); + this.list = list; + return list; + } + else + { + return thisList; + } + } + + @Override + public void addInvalidationListener(Runnable runnable) + { + this.owners.add(runnable); + } + + private void invalidate() + { + this.set = null; + this.list = null; + for (Runnable runnable : this.owners) + { + runnable.run(); + } + } + + @Override + public Stream> stream() + { + return this.getList().stream(); + } + + @Override + public int size() + { + return this.getList().size(); + } + + @Override + public Either, List>> unwrap() + { + return Either.right(this.getList()); + } + + @Override + public Optional> getRandomElement(RandomSource rand) + { + List> list = this.getList(); + int size = list.size(); + return size > 0 + ? Optional.of(list.get(rand.nextInt(size))) + : Optional.empty(); + } + + @Override + public Holder get(int i) + { + return this.getList().get(i); + } + + @Override + public boolean contains(Holder holder) + { + return this.getSet().contains(holder); + } + + @Override + public boolean isValidInRegistry(Registry registry) + { + for (HolderSet component : this.components) + { + if (!component.isValidInRegistry(registry)) + { + return false; + } + } + return true; + } + + @Override + public Iterator> iterator() + { + return this.getList().iterator(); + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/registries/holdersets/HolderSetType.java b/src/main/java/net/minecraftforge/registries/holdersets/HolderSetType.java new file mode 100644 index 0000000000..b3e0882f54 --- /dev/null +++ b/src/main/java/net/minecraftforge/registries/holdersets/HolderSetType.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.registries.holdersets; + +import com.mojang.serialization.Codec; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; + +@FunctionalInterface +public interface HolderSetType +{ + Codec> makeCodec(ResourceKey> registryKey, Codec> holderCodec, boolean forceList); +} diff --git a/src/main/java/net/minecraftforge/registries/holdersets/ICustomHolderSet.java b/src/main/java/net/minecraftforge/registries/holdersets/ICustomHolderSet.java new file mode 100644 index 0000000000..da05d2ee38 --- /dev/null +++ b/src/main/java/net/minecraftforge/registries/holdersets/ICustomHolderSet.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.registries.holdersets; + +import net.minecraft.core.HolderSet; + +/** + * Interface for mods' custom holderset types + */ +public interface ICustomHolderSet extends HolderSet +{ + /** + * {@return HolderSetType registered to {@link ForgeRegistries.HOLDER_SET_TYPES}} + */ + HolderSetType type(); +} diff --git a/src/main/java/net/minecraftforge/registries/holdersets/NotHolderSet.java b/src/main/java/net/minecraftforge/registries/holdersets/NotHolderSet.java new file mode 100644 index 0000000000..0b3f2db8e0 --- /dev/null +++ b/src/main/java/net/minecraftforge/registries/holdersets/NotHolderSet.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.registries.holdersets; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.jetbrains.annotations.Nullable; + +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import net.minecraft.core.Holder; +import net.minecraft.core.HolderSet; +import net.minecraft.core.Registry; +import net.minecraft.resources.HolderSetCodec; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.tags.TagKey; +import net.minecraft.util.RandomSource; +import net.minecraftforge.common.ForgeMod; + +/** + *

Holderset that represents all elements of a registry not present in another holderset. + * forge:exclusion is preferable when the number of allowed elements is small relative to the size of the registry. + * Json format:

+ *
+ * {
+ *   "type": "forge:not",
+ *   "value": "not_this_holderset" // string, list, or object
+ * }
+ * 
+ */ +// this doesn't extend CompositeHolderSet because it doesn't need to cache a set +public class NotHolderSet implements ICustomHolderSet +{ + public static Codec> codec(ResourceKey> registryKey, Codec> holderCodec, boolean forceList) + { + return RecordCodecBuilder.>create(builder -> builder.group( + RegistryOps.retrieveRegistry(registryKey).forGetter(NotHolderSet::registry), + HolderSetCodec.create(registryKey, holderCodec, forceList).fieldOf("value").forGetter(NotHolderSet::value) + ).apply(builder, NotHolderSet::new)); + } + + private final List owners = new ArrayList<>(); + private final Registry registry; + private final HolderSet value; + @Nullable + private List> list = null; + + public Registry registry() { return this.registry; } + public HolderSet value() { return this.value; } + + public NotHolderSet(Registry registry, HolderSet value) + { + this.registry = registry; + this.value = value; + this.value.addInvalidationListener(this::invalidate); + } + + @Override + public HolderSetType type() + { + return ForgeMod.NOT_HOLDER_SET.get(); + } + + @Override + public void addInvalidationListener(Runnable runnable) + { + this.owners.add(runnable); + } + + @Override + public Iterator> iterator() + { + return this.getList().iterator(); + } + + @Override + public Stream> stream() + { + return this.getList().stream(); + } + + @Override + public int size() + { + return this.getList().size(); + } + + @Override + public Either, List>> unwrap() + { + return Either.right(this.getList()); + } + + @Override + public Optional> getRandomElement(RandomSource random) + { + List> list = this.getList(); + int size = list.size(); + return size > 0 + ? Optional.of(list.get(random.nextInt(size))) + : Optional.empty(); + } + + @Override + public Holder get(int i) + { + return this.getList().get(i); + } + + @Override + public boolean contains(Holder holder) + { + return !this.value.contains(holder); + } + + @Override + public boolean isValidInRegistry(Registry registry) + { + return this.registry == registry; + } + + @Override + public String toString() + { + return "NotSet(" + this.value + ")"; + } + + private List> getList() + { + List> thisList = this.list; + if (thisList == null) + { + List> list = this.registry.holders() + .filter(holder -> !this.value.contains(holder)) + .map(holder -> (Holder)holder) + .toList(); + this.list = list; + return list; + } + else + { + return thisList; + } + } + + private void invalidate() + { + this.list = null; + for (Runnable runnable : this.owners) + { + runnable.run(); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/registries/holdersets/OrHolderSet.java b/src/main/java/net/minecraftforge/registries/holdersets/OrHolderSet.java new file mode 100644 index 0000000000..ede452f1f0 --- /dev/null +++ b/src/main/java/net/minecraftforge/registries/holdersets/OrHolderSet.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.registries.holdersets; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.mojang.serialization.Codec; + +import net.minecraft.core.Holder; +import net.minecraft.core.HolderSet; +import net.minecraft.core.Registry; +import net.minecraft.resources.HolderSetCodec; +import net.minecraft.resources.ResourceKey; +import net.minecraftforge.common.ForgeMod; + +/** + *

Holderset that represents a union of other holdersets. Json format:

+ *
+ * {
+ *   "type": "forge:or",
+ *   "values":
+ *   [
+ *      // list of sub-holdersets (strings, lists, or objects)
+ *   ]
+ * }
+ * 
+ */ +public class OrHolderSet extends CompositeHolderSet +{ + public static Codec> codec(ResourceKey> registryKey, Codec> holderCodec, boolean forceList) + { + return HolderSetCodec.create(registryKey, holderCodec, forceList) + .listOf() + .xmap(OrHolderSet::new, OrHolderSet::getComponents) + .fieldOf("values") + .codec(); + } + + public OrHolderSet(List> values) + { + super(values); + } + + @Override + public HolderSetType type() + { + return ForgeMod.OR_HOLDER_SET.get(); + } + + @Override + protected Set> createSet() + { + return this.getComponents().stream().flatMap(HolderSet::stream).collect(Collectors.toSet()); + } + + @Override + public String toString() + { + return "OrSet[" + this.getComponents() + "]"; + } +} \ No newline at end of file