From bc0f954110037107578f48d1b4b6b525bca3514f Mon Sep 17 00:00:00 2001 From: mine_diver Date: Thu, 14 May 2026 23:10:53 +0500 Subject: [PATCH 1/8] Base system for conditional tags in place --- .../tags/blocks/mineable/pickaxe.json | 11 +- .../api/util/context/Condition.java | 7 + .../api/util/context/ConditionType.java | 43 +++++ .../stationapi/api/util/context/Context.java | 178 ++++++++++++++++++ .../api/tag/conditional/BlockContext.java | 33 ++++ .../tag/conditional/BlockTagConditions.java | 17 ++ .../conditional/BlockTagConditionsImpl.java | 34 ++++ .../mixin/block}/client/InGameHudMixin.java | 7 +- .../src/main/resources/fabric.mod.json | 5 + .../resources/station-blocks-v0.mixins.json | 5 +- .../api/block/AbstractBlockState.java | 38 +++- .../api/item/StationFlatteningItemStack.java | 17 +- .../station-flattening-v0.mixins.json | 1 - .../api/tag/conditional/ItemContext.java | 28 +++ .../tag/conditional/ItemTagConditions.java | 17 ++ .../conditional/ItemTagConditionsImpl.java | 34 ++++ .../stationapi/mixin/item/ItemStackMixin.java | 19 +- .../src/main/resources/fabric.mod.json | 1 + .../stationapi/api/registry/Registry.java | 13 +- .../api/registry/RegistryEntry.java | 55 ++++-- .../api/registry/RegistryEntryList.java | 128 ++++++++++--- .../api/registry/SimpleRegistry.java | 83 ++++++-- .../stationapi/api/tag/TagEntry.java | 71 +++++-- .../stationapi/api/tag/TagFile.java | 12 +- .../stationapi/api/tag/TagGroupLoader.java | 79 +++++--- .../stationapi/api/tag/TagManagerLoader.java | 10 +- .../stationapi/api/tag/TagMatchGroup.java | 17 ++ 27 files changed, 852 insertions(+), 111 deletions(-) create mode 100644 station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/Condition.java create mode 100644 station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ConditionType.java create mode 100644 station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/Context.java create mode 100644 station-blocks-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/BlockContext.java create mode 100644 station-blocks-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/BlockTagConditions.java create mode 100644 station-blocks-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/BlockTagConditionsImpl.java rename {station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening => station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block}/client/InGameHudMixin.java (92%) create mode 100644 station-items-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/ItemContext.java create mode 100644 station-items-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/ItemTagConditions.java create mode 100644 station-items-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/ItemTagConditionsImpl.java create mode 100644 station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagMatchGroup.java diff --git a/src/test/resources/data/minecraft/stationapi/tags/blocks/mineable/pickaxe.json b/src/test/resources/data/minecraft/stationapi/tags/blocks/mineable/pickaxe.json index 7e6758701..f7574ea4c 100644 --- a/src/test/resources/data/minecraft/stationapi/tags/blocks/mineable/pickaxe.json +++ b/src/test/resources/data/minecraft/stationapi/tags/blocks/mineable/pickaxe.json @@ -1,6 +1,15 @@ { "values": [ "sltest:test_block", - "sltest:variation_block" + "sltest:variation_block", + { + "id": "minecraft:wool", + "conditions": [ + { + "type": "stationapi:block_metadata", + "metadata": 1 + } + ] + } ] } \ No newline at end of file diff --git a/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/Condition.java b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/Condition.java new file mode 100644 index 000000000..462e18eb1 --- /dev/null +++ b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/Condition.java @@ -0,0 +1,7 @@ +package net.modificationstation.stationapi.api.util.context; + +public record Condition(ConditionType type, DATA data) { + public boolean test(Context ctx) { + return type.test(data, ctx); + } +} diff --git a/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ConditionType.java b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ConditionType.java new file mode 100644 index 000000000..7ec3f430e --- /dev/null +++ b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ConditionType.java @@ -0,0 +1,43 @@ +package net.modificationstation.stationapi.api.util.context; + +import com.mojang.serialization.MapCodec; +import net.modificationstation.stationapi.api.util.Identifier; + +import java.util.function.BiPredicate; + +public final class ConditionType { + private final Identifier id; + private final MapCodec dataCodec; + private final BiPredicate logic; + private final MapCodec> conditionCodec; + + public ConditionType(Identifier id, MapCodec dataCodec, BiPredicate logic) { + this.id = id; + this.dataCodec = dataCodec; + this.logic = logic; + conditionCodec = dataCodec.xmap( + data -> new Condition<>(this, data), + Condition::data + ); + } + + public boolean test(DATA data, Context ctx) { + return logic.test(data, ctx); + } + + public MapCodec> conditionCodec() { + return conditionCodec; + } + + public Identifier id() { + return id; + } + + public MapCodec dataCodec() { + return dataCodec; + } + + public BiPredicate logic() { + return logic; + } +} diff --git a/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/Context.java b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/Context.java new file mode 100644 index 000000000..243c6c81b --- /dev/null +++ b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/Context.java @@ -0,0 +1,178 @@ +package net.modificationstation.stationapi.api.util.context; + +import net.modificationstation.stationapi.api.util.Identifier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +public interface Context { + /** + * A context that contains no keys and always returns {@code null}. + */ + Context EMPTY = new Empty(); + + /** + * A context that signals the bypassing of conditional checks. + */ + Context ANY = new Any(); + + @SuppressWarnings("unused") + record Key(Identifier id) {} + + /** + * {@return the value associated with the given key, or {@code null} if not + * present} + */ + @Nullable VALUE get(Key key); + + /** + * {@return whether this context matches all conditions unconditionally} + *

+ * When {@code true}, condition evaluation should be bypassed entirely, + * and all entries should be treated as matching. Calling {@link #get} on + * such a context is unsupported and may throw. + */ + default boolean matchesAll() { + return false; + } + + /** + * {@return whether this context contains no keys} + */ + default boolean isEmpty() { + return false; + } + + /** + * {@return an optional containing the value associated with the given key} + */ + default Optional getOptional(Key key) { + return Optional.ofNullable(get(key)); + } + + /** + * {@return a new context that includes the given context as an override} + */ + default Context with(Context other) { + return append(this, other); + } + + /** + * {@return a new context that includes the given contexts as overrides in the + * provided order} + */ + default Context with(Context... others) { + Context result = this; + for (Context other : others) result = result.with(other); + return result; + } + + private static Context append(Context base, Context addition) { + if (addition.isEmpty() || base.matchesAll()) return base; + if (base.isEmpty() || addition.matchesAll()) return addition; + return addition instanceof Composite composite + ? append(append(base, composite.next), composite.head) + : new Composite(addition, base); + } + + /** + * {@return a new context that includes the given key-value pair as an override} + */ + default Context with(Key key, VALUE value) { + return with(new Singleton<>(key, value)); + } + + /** + * A context that contains no keys. + */ + record Empty() implements Context { + @Override + public @Nullable VALUE get(Key key) { + return null; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public @NotNull String toString() { + return "EmptyContext"; + } + } + + /** + * A context that represents any context. Should only be used for reference + * checks to bypass evaluations. + */ + record Any() implements Context { + @Override + public @Nullable VALUE get(Key key) { + throw new UnsupportedOperationException( + "Cannot get values from Context.ANY. It is only meant for evaluation bypasses." + ); + } + + @Override + public boolean matchesAll() { + return true; + } + + @Override + public @NotNull String toString() { + return "AnyContext"; + } + } + + /** + * A context containing a single key-value pair. + */ + record Singleton(Key singletonKey, SINGLETON singletonValue) implements Context { + @Override + public @Nullable VALUE get(Key key) { + // noinspection unchecked + return singletonKey.equals(key) ? (VALUE) singletonValue : null; + } + } + + /** + * A context that delegates to two other contexts. + *

+ * This implementation uses an iterative walk to avoid + * {@link StackOverflowError} on deep chains. + */ + record Composite(Context head, Context next) implements Context { + @Override + public @Nullable VALUE get(Key key) { + Context current = this; + while (current instanceof Composite composite) { + VALUE value = composite.head.get(key); + if (value != null) return value; + current = composite.next; + } + return current.get(key); + } + + @Override + public boolean matchesAll() { + Context current = this; + while (current instanceof Composite composite) { + if (composite.head.matchesAll()) return true; + current = composite.next; + } + return current.matchesAll(); + } + + @Override + public boolean isEmpty() { + Context current = this; + while (current instanceof Composite composite) { + if (!composite.head.isEmpty()) return false; + current = composite.next; + } + return current.isEmpty(); + } + } +} diff --git a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/BlockContext.java b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/BlockContext.java new file mode 100644 index 000000000..c6aa6e2b1 --- /dev/null +++ b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/BlockContext.java @@ -0,0 +1,33 @@ +package net.modificationstation.stationapi.api.tag.conditional; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.BlockView; +import net.modificationstation.stationapi.api.util.context.Context; +import org.jetbrains.annotations.Nullable; + +public final class BlockContext implements Context { + private static final ThreadLocal INSTANCE = ThreadLocal.withInitial(BlockContext::new); + + public static BlockContext of(BlockView world, BlockPos pos) { + return of(world, pos.x, pos.y, pos.z); + } + + public static BlockContext of(BlockView world, int x, int y, int z) { + BlockContext ctx = INSTANCE.get(); + ctx.metadata = world.getBlockMeta(x, y, z); + return ctx; + } + + private Integer metadata; + + private BlockContext() {} + + @Override + public @Nullable VALUE get(Key key) { + if (key == BlockTagConditions.BLOCK_METADATA) { + //noinspection unchecked + return (VALUE) metadata; + } + return null; + } +} diff --git a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/BlockTagConditions.java b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/BlockTagConditions.java new file mode 100644 index 000000000..7997b3d24 --- /dev/null +++ b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/BlockTagConditions.java @@ -0,0 +1,17 @@ +package net.modificationstation.stationapi.api.tag.conditional; + +import net.modificationstation.stationapi.api.util.context.Context; + +import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; + +public final class BlockTagConditions { + /** + * The context key used to evaluate block metadata tag conditions. + *

+ * When populating a context to evaluate a tag that may contain block metadata conditions, + * provide the current metadata integer (0-15) via this key. + */ + public static final Context.Key BLOCK_METADATA = new Context.Key<>(NAMESPACE.id("block_metadata")); + + private BlockTagConditions() {} +} diff --git a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/BlockTagConditionsImpl.java b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/BlockTagConditionsImpl.java new file mode 100644 index 000000000..f7a0d1851 --- /dev/null +++ b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/BlockTagConditionsImpl.java @@ -0,0 +1,34 @@ +package net.modificationstation.stationapi.impl.tag.conditional; + +import com.mojang.serialization.Codec; +import net.mine_diver.unsafeevents.listener.EventListener; +import net.modificationstation.stationapi.api.StationAPI; +import net.modificationstation.stationapi.api.event.registry.BlockRegistryEvent; +import net.modificationstation.stationapi.api.mod.entrypoint.Entrypoint; +import net.modificationstation.stationapi.api.mod.entrypoint.EntrypointManager; +import net.modificationstation.stationapi.api.mod.entrypoint.EventBusPolicy; +import net.modificationstation.stationapi.api.tag.conditional.BlockTagConditions; + +import java.lang.invoke.MethodHandles; + +import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; + +@Entrypoint(eventBus = @EventBusPolicy(registerInstance = false)) +@EventListener(phase = StationAPI.INTERNAL_PHASE) +public class BlockTagConditionsImpl { + static { + EntrypointManager.registerLookup(MethodHandles.lookup()); + } + + @EventListener + private static void registerConditions(BlockRegistryEvent event) { + event.registry.registerTagCondition( + NAMESPACE.id("block_metadata"), + Codec.INT.fieldOf("metadata"), + (metadata, ctx) -> { + Integer ctxMeta = ctx.get(BlockTagConditions.BLOCK_METADATA); + return ctxMeta != null && ctxMeta.equals(metadata); + } + ); + } +} diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/client/InGameHudMixin.java b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/client/InGameHudMixin.java similarity index 92% rename from station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/client/InGameHudMixin.java rename to station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/client/InGameHudMixin.java index ea908cd99..9d097acb2 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/mixin/flattening/client/InGameHudMixin.java +++ b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/client/InGameHudMixin.java @@ -1,4 +1,4 @@ -package net.modificationstation.stationapi.mixin.flattening.client; +package net.modificationstation.stationapi.mixin.block.client; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.block.Block; @@ -13,6 +13,7 @@ import net.modificationstation.stationapi.api.block.BlockState; import net.modificationstation.stationapi.api.state.property.Property; import net.modificationstation.stationapi.api.tag.TagKey; +import net.modificationstation.stationapi.api.tag.conditional.BlockContext; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -59,7 +60,9 @@ private void stationapi_renderHud(float bl, boolean i, int j, int par4, Callback } } - Collection> tags = state.streamTags().toList(); + Collection> tags = state.streamTags( + BlockContext.of(minecraft.world, hit.blockX, hit.blockY, hit.blockZ) + ).toList(); if (!tags.isEmpty()) { text = "Tags:"; drawTextWithShadow(var8, text, var6 - var8.getWidth(text) - 2, offset += 10, 16777215); diff --git a/station-blocks-v0/src/main/resources/fabric.mod.json b/station-blocks-v0/src/main/resources/fabric.mod.json index 6247cd80d..1c10f52d3 100644 --- a/station-blocks-v0/src/main/resources/fabric.mod.json +++ b/station-blocks-v0/src/main/resources/fabric.mod.json @@ -18,6 +18,11 @@ "icon": "assets/station-blocks-v0/icon.png", "environment": "*", + "entrypoints": { + "stationapi:event_bus": [ + "net.modificationstation.stationapi.impl.tag.conditional.BlockTagConditionsImpl" + ] + }, "mixins": [ "station-blocks-v0.mixins.json" ], diff --git a/station-blocks-v0/src/main/resources/station-blocks-v0.mixins.json b/station-blocks-v0/src/main/resources/station-blocks-v0.mixins.json index 2951760da..5df79d72b 100644 --- a/station-blocks-v0/src/main/resources/station-blocks-v0.mixins.json +++ b/station-blocks-v0/src/main/resources/station-blocks-v0.mixins.json @@ -12,5 +12,8 @@ ], "injectors": { "defaultRequire": 1 - } + }, + "client": [ + "client.InGameHudMixin" + ] } diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java index 76e3d3d6f..f54dd3b42 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java @@ -16,6 +16,7 @@ import net.modificationstation.stationapi.api.state.State; import net.modificationstation.stationapi.api.state.property.Property; import net.modificationstation.stationapi.api.tag.TagKey; +import net.modificationstation.stationapi.api.util.context.Context; import net.modificationstation.stationapi.api.util.math.MathHelper; import net.modificationstation.stationapi.impl.block.StationFlatteningBlockInternal; @@ -30,7 +31,8 @@ public abstract class AbstractBlockState extends State { private final boolean opaque; private int luminance = -1; - protected AbstractBlockState(Block block, ImmutableMap, Comparable> propertyMap, MapCodec mapCodec) { + protected AbstractBlockState(Block block, ImmutableMap, Comparable> propertyMap, + MapCodec mapCodec) { super(block, propertyMap, mapCodec); this.isAir = block.material == Material.AIR; this.material = block.material; @@ -51,9 +53,10 @@ public Material getMaterial() { * Returns the light level emitted by this block state. */ public int getLuminance() { - return luminance == -1 ? - luminance = ((StationFlatteningBlockInternal) owner).stationapi_getLuminanceProvider().applyAsInt(asBlockState()) : - luminance; + return luminance == -1 + ? luminance = ((StationFlatteningBlockInternal) owner).stationapi_getLuminanceProvider() + .applyAsInt(asBlockState()) + : luminance; } public boolean isAir() { @@ -84,20 +87,39 @@ public boolean canReplace(ItemPlacementContext context) { return this.getBlock().canReplace(this.asBlockState(), context); } + @Deprecated public boolean isIn(TagKey tag) { - return getBlock().getRegistryEntry().isIn(tag); + return this.isIn(tag, Context.EMPTY); } + public boolean isIn(TagKey tag, Context context) { + return getBlock().getRegistryEntry().isIn(tag, context); + } + + @Deprecated public boolean isIn(TagKey tag, Predicate predicate) { - return this.isIn(tag) && predicate.test(this); + return this.isIn(tag, Context.EMPTY, predicate); + } + + public boolean isIn(TagKey tag, Context context, Predicate predicate) { + return this.isIn(tag, context) && predicate.test(this); } + @Deprecated public boolean isIn(RegistryEntryList blocks) { - return blocks.contains(getBlock().getRegistryEntry()); + return this.isIn(blocks, Context.EMPTY); + } + + public boolean isIn(RegistryEntryList blocks, Context context) { + return blocks.contains(getBlock().getRegistryEntry(), context); } public Stream> streamTags() { - return getBlock().getRegistryEntry().streamTags(); + return this.streamTags(Context.ANY); + } + + public Stream> streamTags(Context context) { + return getBlock().getRegistryEntry().streamTags(context); } public boolean isOf(Block block) { diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/StationFlatteningItemStack.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/StationFlatteningItemStack.java index 89e26b126..7fb397068 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/StationFlatteningItemStack.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/StationFlatteningItemStack.java @@ -8,6 +8,9 @@ import net.modificationstation.stationapi.api.registry.RegistryEntry; import net.modificationstation.stationapi.api.tag.TagKey; import net.modificationstation.stationapi.api.util.Util; +import net.modificationstation.stationapi.api.util.context.Context; + +import java.util.stream.Stream; public interface StationFlatteningItemStack extends ItemStackStrengthWithBlockState { @@ -16,7 +19,19 @@ default RegistryEntry.Reference getRegistryEntry() { } default boolean isIn(TagKey tag) { - return getRegistryEntry().isIn(tag); + return isIn(tag, Context.EMPTY); + } + + default boolean isIn(TagKey tag, Context context) { + return Util.assertImpl(); + } + + default Stream> streamTags() { + return streamTags(Context.ANY); + } + + default Stream> streamTags(Context context) { + return Util.assertImpl(); } @Override diff --git a/station-flattening-v0/src/main/resources/station-flattening-v0.mixins.json b/station-flattening-v0/src/main/resources/station-flattening-v0.mixins.json index 5f9a3b5cc..328e3cd90 100644 --- a/station-flattening-v0/src/main/resources/station-flattening-v0.mixins.json +++ b/station-flattening-v0/src/main/resources/station-flattening-v0.mixins.json @@ -45,7 +45,6 @@ "client": [ "client.BlockRenderManagerMixin", "client.ClientWorldMixin", - "client.InGameHudMixin", "client.InteractionManagerMixin", "client.MinecraftMixin", "client.MultiplayerChunkCacheMixin", diff --git a/station-items-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/ItemContext.java b/station-items-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/ItemContext.java new file mode 100644 index 000000000..0d0a936af --- /dev/null +++ b/station-items-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/ItemContext.java @@ -0,0 +1,28 @@ +package net.modificationstation.stationapi.api.tag.conditional; + +import net.minecraft.item.ItemStack; +import net.modificationstation.stationapi.api.util.context.Context; +import org.jetbrains.annotations.Nullable; + +public final class ItemContext implements Context { + private static final ThreadLocal INSTANCE = ThreadLocal.withInitial(ItemContext::new); + + public static ItemContext of(ItemStack stack) { + ItemContext ctx = INSTANCE.get(); + ctx.damage = stack.getDamage(); + return ctx; + } + + private Integer damage; + + private ItemContext() {} + + @Override + public @Nullable VALUE get(Key key) { + if (key == ItemTagConditions.ITEM_DAMAGE) { + //noinspection unchecked + return (VALUE) damage; + } + return null; + } +} diff --git a/station-items-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/ItemTagConditions.java b/station-items-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/ItemTagConditions.java new file mode 100644 index 000000000..ca9b68f5c --- /dev/null +++ b/station-items-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/ItemTagConditions.java @@ -0,0 +1,17 @@ +package net.modificationstation.stationapi.api.tag.conditional; + +import net.modificationstation.stationapi.api.util.context.Context; + +import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; + +public final class ItemTagConditions { + /** + * The context key used to evaluate item damage tag conditions. + *

+ * When populating a context to evaluate a tag that may contain item damage conditions, + * provide the current item damage integer via this key. + */ + public static final Context.Key ITEM_DAMAGE = new Context.Key<>(NAMESPACE.id("item_damage")); + + private ItemTagConditions() {} +} diff --git a/station-items-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/ItemTagConditionsImpl.java b/station-items-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/ItemTagConditionsImpl.java new file mode 100644 index 000000000..141c4519f --- /dev/null +++ b/station-items-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/ItemTagConditionsImpl.java @@ -0,0 +1,34 @@ +package net.modificationstation.stationapi.impl.tag.conditional; + +import com.mojang.serialization.Codec; +import net.mine_diver.unsafeevents.listener.EventListener; +import net.modificationstation.stationapi.api.StationAPI; +import net.modificationstation.stationapi.api.event.registry.ItemRegistryEvent; +import net.modificationstation.stationapi.api.mod.entrypoint.Entrypoint; +import net.modificationstation.stationapi.api.mod.entrypoint.EntrypointManager; +import net.modificationstation.stationapi.api.mod.entrypoint.EventBusPolicy; +import net.modificationstation.stationapi.api.tag.conditional.ItemTagConditions; + +import java.lang.invoke.MethodHandles; + +import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; + +@Entrypoint(eventBus = @EventBusPolicy(registerInstance = false)) +@EventListener(phase = StationAPI.INTERNAL_PHASE) +public class ItemTagConditionsImpl { + static { + EntrypointManager.registerLookup(MethodHandles.lookup()); + } + + @EventListener + private static void registerConditions(ItemRegistryEvent event) { + event.registry.registerTagCondition( + NAMESPACE.id("item_damage"), + Codec.INT.fieldOf("damage"), + (damage, ctx) -> { + Integer ctxDamage = ctx.get(ItemTagConditions.ITEM_DAMAGE); + return ctxDamage != null && ctxDamage.equals(damage); + } + ); + } +} diff --git a/station-items-v0/src/main/java/net/modificationstation/stationapi/mixin/item/ItemStackMixin.java b/station-items-v0/src/main/java/net/modificationstation/stationapi/mixin/item/ItemStackMixin.java index 241358b12..fc4a05c14 100644 --- a/station-items-v0/src/main/java/net/modificationstation/stationapi/mixin/item/ItemStackMixin.java +++ b/station-items-v0/src/main/java/net/modificationstation/stationapi/mixin/item/ItemStackMixin.java @@ -9,7 +9,11 @@ import net.modificationstation.stationapi.api.StationAPI; import net.modificationstation.stationapi.api.block.BlockState; import net.modificationstation.stationapi.api.event.item.ItemStackEvent; +import net.modificationstation.stationapi.api.item.StationFlatteningItemStack; import net.modificationstation.stationapi.api.item.StationItemStack; +import net.modificationstation.stationapi.api.tag.TagKey; +import net.modificationstation.stationapi.api.tag.conditional.ItemContext; +import net.modificationstation.stationapi.api.util.context.Context; import net.modificationstation.stationapi.impl.item.StationNBTSetter; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -21,12 +25,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.util.Objects; +import java.util.stream.Stream; import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; import static net.modificationstation.stationapi.api.util.Identifier.of; @Mixin(ItemStack.class) -abstract class ItemStackMixin implements StationItemStack, StationNBTSetter { +abstract class ItemStackMixin implements StationItemStack, StationNBTSetter, StationFlatteningItemStack { @Shadow public int itemId; @@ -168,4 +173,16 @@ public NbtCompound getStationNbt() { public void setStationNbt(NbtCompound stationNbt) { this.stationapi_stationNbt = stationNbt; } + + @Override + @Unique + public boolean isIn(TagKey tag, Context context) { + return getRegistryEntry().isIn(tag, ItemContext.of((ItemStack) (Object) this).with(context)); + } + + @Override + @Unique + public Stream> streamTags(Context context) { + return getRegistryEntry().streamTags(ItemContext.of((ItemStack) (Object) this).with(context)); + } } diff --git a/station-items-v0/src/main/resources/fabric.mod.json b/station-items-v0/src/main/resources/fabric.mod.json index b18a60b38..1cd47812f 100644 --- a/station-items-v0/src/main/resources/fabric.mod.json +++ b/station-items-v0/src/main/resources/fabric.mod.json @@ -20,6 +20,7 @@ "environment": "*", "entrypoints": { "stationapi:event_bus": [ + "net.modificationstation.stationapi.impl.tag.conditional.ItemTagConditionsImpl", "net.modificationstation.stationapi.impl.entity.player.ItemCustomReachImpl", "net.modificationstation.stationapi.impl.dispenser.CustomDispenseBehaviorImpl" ], diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java index aab7aff2a..6eeba9a1b 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java @@ -6,15 +6,19 @@ import net.minecraft.block.Block; import net.minecraft.item.Item; import net.modificationstation.stationapi.api.tag.TagKey; +import net.modificationstation.stationapi.api.tag.TagMatchGroup; import net.modificationstation.stationapi.api.util.Identifier; import net.modificationstation.stationapi.api.util.Namespace; import net.modificationstation.stationapi.api.util.collection.IndexedIterable; +import net.modificationstation.stationapi.api.util.context.Condition; +import net.modificationstation.stationapi.api.util.context.Context; import net.modificationstation.stationapi.api.util.dynamic.Codecs; import net.modificationstation.stationapi.api.util.function.BulkBiConsumer; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.ToIntFunction; import java.util.stream.Stream; @@ -436,7 +440,7 @@ default Iterable> iterateEntries(TagKey tag) { void clearTags(); - void populateTags(Map, List>> var1); + void populateTags(Map, Collection>>> var1); default IndexedIterable> getIndexedEntries() { return new IndexedIterable<>() { @@ -499,5 +503,10 @@ public RegistryEntryList.Named getOrThrow(TagKey tag) { } }; } -} + void registerTagCondition( + Identifier id, MapCodec codec, BiPredicate condition + ); + + Codec> getTagConditionCodec(); +} diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntry.java index 09d38f423..bee92f1c1 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntry.java @@ -3,11 +3,10 @@ import com.mojang.datafixers.util.Either; import net.modificationstation.stationapi.api.tag.TagKey; import net.modificationstation.stationapi.api.util.Identifier; +import net.modificationstation.stationapi.api.util.context.Context; import org.jetbrains.annotations.Nullable; -import java.util.Collection; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.function.Predicate; import java.util.stream.Stream; @@ -22,9 +21,29 @@ public interface RegistryEntry { boolean matches(Predicate> predicate); - boolean isIn(TagKey tag); + /** + * @deprecated Use {@link #isIn(TagKey, Context)} instead. + *

This method implicitly uses {@link Context#EMPTY}, meaning it will only evaluate to {@code true} + * for unconditional tag references. + */ + @Deprecated + default boolean isIn(TagKey tag) { + return isIn(tag, Context.EMPTY); + } + + boolean isIn(TagKey tag, Context context); + + /** + * @deprecated Use {@link #streamTags(Context)} instead. + *

This method implicitly uses {@link Context#ANY}, meaning it will yield all tag keys + * this entry belongs to, bypassing all conditional checks. + */ + @Deprecated + default Stream> streamTags() { + return streamTags(Context.ANY); + } - Stream> streamTags(); + Stream> streamTags(Context context); Set> getTags(); @@ -57,7 +76,7 @@ public boolean matchesKey(RegistryKey key) { } @Override - public boolean isIn(TagKey tag) { + public boolean isIn(TagKey tag, Context context) { return false; } @@ -92,7 +111,7 @@ public boolean ownerEquals(RegistryEntryOwner owner) { } @Override - public Stream> streamTags() { + public Stream> streamTags(Context context) { return Stream.of(); } @@ -104,7 +123,7 @@ public Set> getTags() { abstract class Reference implements RegistryEntry { final RegistryEntryOwner owner; - private Set> tags = Set.of(); + private Map, Predicate> tags = Map.of(); private Reference(RegistryEntryOwner owner) { this.owner = owner; @@ -123,8 +142,10 @@ public boolean matchesKey(RegistryKey key) { } @Override - public boolean isIn(TagKey tag) { - return tags.contains(tag); + public boolean isIn(TagKey tag, Context context) { + if (context.matchesAll()) return tags.containsKey(tag); + Predicate predicate = tags.get(tag); + return predicate != null && predicate.test(context); } @Override @@ -156,18 +177,22 @@ public Type getType() { abstract void setValue(ENTRY value); - void setTags(Collection> tags) { - this.tags = Set.copyOf(tags); + void setTags(Map, Predicate> tags) { + this.tags = Map.copyOf(tags); } @Override - public Stream> streamTags() { - return this.tags.stream(); + public Stream> streamTags(Context context) { + if (context.matchesAll()) return this.tags.keySet().stream(); + return this.tags.entrySet().stream() + .filter(entry -> entry.getValue().test(context)) + .map(Map.Entry::getKey); } @Override + @Deprecated public Set> getTags() { - return tags; + return Set.copyOf(this.tags.keySet()); } public String toString() { diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntryList.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntryList.java index e6b401b18..c98868976 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntryList.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntryList.java @@ -2,7 +2,10 @@ import com.mojang.datafixers.util.Either; import net.modificationstation.stationapi.api.tag.TagKey; +import net.modificationstation.stationapi.api.tag.TagMatchGroup; import net.modificationstation.stationapi.api.util.Util; +import net.modificationstation.stationapi.api.util.context.Context; +import net.modificationstation.stationapi.api.util.context.Condition; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.VisibleForTesting; @@ -24,14 +27,39 @@ */ public interface RegistryEntryList extends Iterable> { /** + * This method implicitly uses {@link Context#ANY}, meaning it will stream all registry entries + * in this list, bypassing all conditional checks. * {@return a stream of registry entries in this list} */ - Stream> stream(); + default Stream> stream() { + return stream(Context.ANY); + } /** + * {@return a stream of registry entries in this list, including match groups that pass the given context} + */ + Stream> stream(Context context); + + /** + * {@return an iterable of registry entries in this list, including match groups that pass the given context} + */ + default Iterable> iterable(Context context) { + return () -> stream(context).iterator(); + } + + /** + * This method implicitly uses {@link Context#ANY}, meaning it will count all registry entries + * in this list, bypassing all conditional checks. * {@return the number of entries in this list} */ - int size(); + default int size() { + return size(Context.ANY); + } + + /** + * {@return the number of entries in this list, including match groups that pass the given context} + */ + int size(Context context); /** * {@return the object that identifies this registry entry list} @@ -41,21 +69,53 @@ public interface RegistryEntryList extends Iterable> { Either, List>> getStorage(); /** + * This method implicitly uses {@link Context#ANY}, meaning it may return any registry entry + * in this list, bypassing all conditional checks. * {@return a random entry of the list, or an empty optional if this list is empty} */ - Optional> getRandom(Random var1); + default Optional> getRandom(Random var1) { + return getRandom(var1, Context.ANY); + } /** + * {@return a random entry of the list matching the context, or an empty optional if this list is empty} + */ + Optional> getRandom(Random var1, Context context); + + /** + * This method implicitly uses {@link Context#ANY}, meaning it operates on the flattened list of all registry entries + * in this list, bypassing all conditional checks. * {@return the registry entry at {@code index}} * * @throws IndexOutOfBoundsException if the index is out of bounds */ - RegistryEntry get(int var1); + default RegistryEntry get(int var1) { + return get(var1, Context.ANY); + } + + /** + * {@return the registry entry at {@code index} matching the context} + * + * @throws IndexOutOfBoundsException if the index is out of bounds + */ + RegistryEntry get(int var1, Context context); /** * {@return whether {@code entry} is in this list} + * + * @deprecated Use {@link #contains(RegistryEntry, Context)} instead. + *

This method implicitly uses {@link Context#EMPTY}, meaning it will only evaluate to {@code true} + * for unconditional tag references. + */ + @Deprecated + default boolean contains(RegistryEntry entry) { + return contains(entry, Context.EMPTY); + } + + /** + * {@return whether {@code entry} is in this list, evaluating match groups with the given context} */ - boolean contains(RegistryEntry var1); + boolean contains(RegistryEntry var1, Context context); boolean ownerEquals(RegistryEntryOwner var1); @@ -100,15 +160,17 @@ static Direct of(Function> mapper, List values) class Named extends ListBacked { private final RegistryEntryOwner owner; private final TagKey tag; - private List> entries = List.of(); + private List> allEntries = List.of(); + private List>> matchGroups = List.of(); Named(RegistryEntryOwner owner, TagKey tag) { this.owner = owner; this.tag = tag; } - void copyOf(List> entries) { - this.entries = List.copyOf(entries); + void copyOf(Collection>> matchGroups) { + this.matchGroups = List.copyOf(matchGroups); + this.allEntries = this.matchGroups.stream().flatMap(c -> c.baseItems().stream()).distinct().toList(); } public TagKey getTag() { @@ -117,7 +179,27 @@ public TagKey getTag() { @Override protected List> getEntries() { - return this.entries; + return this.allEntries; + } + + @Override + public Stream> stream(Context context) { + if (context.matchesAll()) return this.getEntries().stream(); + + return this.matchGroups.stream() + .filter(matchGroup -> { + for (Condition condition : matchGroup.conditions()) + if (!condition.test(context)) return false; + return true; + }) + .flatMap(matchGroup -> matchGroup.baseItems().stream()) + .distinct(); + } + + @Override + public int size(Context context) { + if (context.matchesAll()) return this.allEntries.size(); + return (int) this.stream(context).count(); } @Override @@ -131,12 +213,14 @@ public Optional> getTagKey() { } @Override - public boolean contains(RegistryEntry entry) { - return entry.isIn(this.tag); + public boolean contains(RegistryEntry entry, Context context) { + return entry.isIn(this.tag, context); } public String toString() { - return "NamedSet(" + this.tag + ")[" + this.entries + "]"; + return "NamedSet(" + this.tag + ")[" + + this.allEntries + (matchGroups.isEmpty() ? "" : ", matchGroups=" + matchGroups.size()) + + "]"; } @Override @@ -170,7 +254,7 @@ public Optional> getTagKey() { } @Override - public boolean contains(RegistryEntry entry) { + public boolean contains(RegistryEntry entry, Context context) { if (this.entrySet == null) this.entrySet = Set.copyOf(this.entries); return this.entrySet.contains(entry); } @@ -184,33 +268,34 @@ abstract class ListBacked implements RegistryEntryList { protected abstract List> getEntries(); @Override - public int size() { + public int size(Context context) { return this.getEntries().size(); } @Override public Spliterator> spliterator() { - return this.getEntries().spliterator(); + return this.stream(Context.ANY).spliterator(); } @Override public Iterator> iterator() { - return this.getEntries().iterator(); + return this.stream(Context.ANY).iterator(); } @Override - public Stream> stream() { + public Stream> stream(Context context) { return this.getEntries().stream(); } @Override - public Optional> getRandom(Random random) { - return Util.getRandomOrEmpty(this.getEntries(), random); + public Optional> getRandom(Random random, Context context) { + return Util.getRandomOrEmpty(this.stream(context).toList(), random); } @Override - public RegistryEntry get(int index) { - return this.getEntries().get(index); + public RegistryEntry get(int index, Context context) { + return this.stream(context).skip(index).findFirst() + .orElseThrow(() -> new IndexOutOfBoundsException("Index out of bounds: " + index)); } @Override @@ -219,4 +304,3 @@ public boolean ownerEquals(RegistryEntryOwner owner) { } } } - diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java index c10e67742..cc9d67eaf 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java @@ -2,7 +2,9 @@ import com.google.common.collect.*; import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; import com.mojang.serialization.Lifecycle; +import com.mojang.serialization.MapCodec; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; @@ -15,14 +17,22 @@ import net.modificationstation.stationapi.api.registry.RegistryEntryList.Named; import net.modificationstation.stationapi.api.registry.RegistryWrapper.Impl; import net.modificationstation.stationapi.api.tag.TagKey; +import net.modificationstation.stationapi.api.tag.TagMatchGroup; import net.modificationstation.stationapi.api.util.Identifier; import net.modificationstation.stationapi.api.util.Util; +import net.modificationstation.stationapi.api.util.context.Condition; +import net.modificationstation.stationapi.api.util.context.ConditionType; +import net.modificationstation.stationapi.api.util.context.Context; import net.modificationstation.stationapi.impl.registry.sync.RemapStateImpl; import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; import java.util.*; import java.util.Map.Entry; +import java.util.function.BiPredicate; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -40,6 +50,7 @@ public class SimpleRegistry implements MutableRegistry, RemappableRegistry private final Reference2ReferenceMap entryToLifecycle = new Reference2ReferenceOpenHashMap<>(); private Lifecycle lifecycle; private volatile Reference2ReferenceMap, Named> tagToEntryList = new Reference2ReferenceOpenHashMap<>(); + private final Reference2ReferenceMap> tagConditionTypes = new Reference2ReferenceOpenHashMap<>(); private boolean frozen; @Nullable private Reference2ReferenceMap> intrusiveValueToEntry; @@ -50,6 +61,17 @@ public class SimpleRegistry implements MutableRegistry, RemappableRegistry private Reference2IntMap prevIndexedEntries; private BiMap> prevEntries; + private final Codec> tagConditionCodec = Identifier.CODEC.dispatch( + "type", + condition -> condition.type().id(), + id -> { + ConditionType type = tagConditionTypes.get(id); + if (type == null) + throw new IllegalArgumentException("Unknown condition type: " + id + " in registry " + getKey()); + return type.conditionCodec(); + } + ); + private @Nullable MutableEventBus eventBus; public SimpleRegistry(RegistryKey> key, Lifecycle lifecycle) { @@ -315,7 +337,7 @@ public Lifecycle getLifecycle() { } @Override - public Iterator iterator() { + public @NotNull Iterator iterator() { return Iterators.transform(this.getEntries().iterator(), RegistryEntry::value); } @@ -449,28 +471,51 @@ public Optional> getEntryList(TagKey tag) { } @Override - public void populateTags(Map, List>> tagEntries) { - Map, List>> map = new IdentityHashMap<>(); - keyToEntry.values().forEach(entry -> map.put(entry, new ArrayList<>())); - tagEntries.forEach((tag, entries) -> { + public void populateTags(@UnknownNullability Map, Collection>>> tagEntries) { + Map, Map, Predicate>> map = new IdentityHashMap<>(); - for (RegistryEntry entry : entries) { - if (!entry.ownerEquals(getReadOnlyWrapper())) - throw new IllegalStateException("Can't create named set " + tag + " containing value " + entry + " from outside registry " + this); + keyToEntry.values().forEach(entry -> map.put( + entry, new Reference2ReferenceOpenHashMap<>()) + ); - if (!(entry instanceof Reference reference)) - throw new IllegalStateException("Found direct holder " + entry + " value in tag " + tag); + tagEntries.forEach((tag, resolvedTag) -> { + for (TagMatchGroup> matchGroup : resolvedTag) { + Predicate predicate = matchGroup.conditions().isEmpty() + ? ctx -> true + : ctx -> { + for (Condition condition : matchGroup.conditions()) + if (!condition.test(ctx)) return false; + return true; + }; - map.get(reference).add(tag); - } + for (RegistryEntry entry : matchGroup.baseItems()) { + if (!entry.ownerEquals(getReadOnlyWrapper())) + throw new IllegalStateException( + "Can't create named set " + tag + " containing value " + + entry + " from outside registry " + this + ); + + if (!(entry instanceof Reference reference)) + throw new IllegalStateException( + "Found direct holder " + entry + " value in tag " + tag + ); + map.get(reference).merge(tag, predicate, Predicate::or); + } + } }); Set> set = Sets.difference(this.tagToEntryList.keySet(), tagEntries.keySet()); if (!set.isEmpty()) LOGGER.warn("Not all defined tags for registry {} are present in data pack: {}", this.getKey(), set.stream().map(tag -> tag.id().toString()).sorted().collect(Collectors.joining(", "))); Reference2ReferenceMap, Named> map2 = new Reference2ReferenceOpenHashMap<>(this.tagToEntryList); - tagEntries.forEach((tag, entries) -> map2.computeIfAbsent(tag, this::createNamedEntryList).copyOf(entries)); + + tagEntries.forEach( + (tag, resolvedTag) -> map2.computeIfAbsent( + tag, this::createNamedEntryList + ).copyOf(resolvedTag) + ); + map.forEach(Reference::setTags); this.tagToEntryList = map2; } @@ -478,7 +523,7 @@ public void populateTags(Map, List>> tagEntries) { @Override public void clearTags() { this.tagToEntryList.values().forEach(entryList -> entryList.copyOf(List.of())); - this.keyToEntry.values().forEach(entry -> entry.setTags(Set.of())); + this.keyToEntry.values().forEach(entry -> entry.setTags(Map.of())); } @Override @@ -685,4 +730,14 @@ public void unmap(String name) throws RemapException { prevEntries = null; } } + + @Override + public void registerTagCondition(Identifier id, MapCodec codec, BiPredicate condition) { + tagConditionTypes.put(id, new ConditionType<>(id, codec, condition)); + } + + @Override + public Codec> getTagConditionCodec() { + return tagConditionCodec; + } } diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagEntry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagEntry.java index f749bac16..ccd062c76 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagEntry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagEntry.java @@ -4,30 +4,63 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.modificationstation.stationapi.api.util.Identifier; +import net.modificationstation.stationapi.api.util.context.Condition; import net.modificationstation.stationapi.api.util.dynamic.Codecs; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; public class TagEntry { - private static final Codec ENTRY_CODEC = RecordCodecBuilder.create(instance -> instance.group(Codecs.TAG_ENTRY_ID.fieldOf("id").forGetter(TagEntry::getIdForCodec), Codec.BOOL.optionalFieldOf("required", true).forGetter(entry -> entry.required)).apply(instance, TagEntry::new)); - public static final Codec CODEC = Codec.either(Codecs.TAG_ENTRY_ID, ENTRY_CODEC).xmap(either -> either.map(id -> new TagEntry(id, true), tagEntry -> tagEntry), entry -> entry.required ? Either.left(entry.getIdForCodec()) : Either.right(entry)); + public static Codec createCodec(Codec> tagConditionCodec) { + return Codec.either( + Codecs.TAG_ENTRY_ID, + RecordCodecBuilder.create( + instance -> instance.group( + Codecs.TAG_ENTRY_ID.fieldOf("id") + .forGetter(TagEntry::getIdForCodec), + Codec.BOOL.optionalFieldOf("required", true) + .forGetter(entry -> entry.required), + tagConditionCodec.listOf().optionalFieldOf("conditions", List.of()) + .forGetter(entry -> entry.conditions) + ).apply(instance, TagEntry::new) + ) + ).xmap( + either -> either.map( + id -> new TagEntry(id, true), + tagEntry -> tagEntry + ), + entry -> entry.required ? Either.left(entry.getIdForCodec()) : Either.right(entry) + ); + } + private final Identifier id; private final boolean tag; private final boolean required; + private final List> conditions; private TagEntry(Identifier id, boolean tag, boolean required) { this.id = id; this.tag = tag; this.required = required; + this.conditions = List.of(); } private TagEntry(Codecs.TagEntryId id, boolean required) { this.id = id.id(); - this.tag = id.tag(); + tag = id.tag(); + this.required = required; + conditions = List.of(); + } + + private TagEntry(Codecs.TagEntryId id, boolean required, List> conditions) { + this.id = id.id(); + tag = id.tag(); this.required = required; + this.conditions = List.copyOf(conditions); } private Codecs.TagEntryId getIdForCodec() { @@ -50,19 +83,27 @@ public static TagEntry createOptionalTag(Identifier id) { return new TagEntry(id, true, false); } - public boolean resolve(ValueGetter valueGetter, Consumer consumer) { + public boolean resolve( + ValueGetter getter, + Consumer> matchGroupConsumer + ) { if (this.tag) { - Collection collection = valueGetter.tag(this.id); - if (collection == null) { - return !this.required; + Collection> refTag = getter.tag(this.id); + if (refTag == null) return !this.required; + + for (TagMatchGroup refMatchGroup : refTag) { + List> mergedConditions = new ArrayList<>(refMatchGroup.conditions()); + + if (!this.conditions.isEmpty()) mergedConditions.addAll(this.conditions); + + matchGroupConsumer.accept(new TagMatchGroup<>(refMatchGroup.baseItems(), mergedConditions)); } - collection.forEach(consumer); + } else { - T object = valueGetter.direct(this.id); - if (object == null) { - return !this.required; - } - consumer.accept(object); + T value = getter.direct(this.id); + if (value == null) return !this.required; + + matchGroupConsumer.accept(new TagMatchGroup<>(List.of(value), this.conditions)); } return true; } @@ -96,9 +137,9 @@ public String toString() { } public interface ValueGetter { - @Nullable T direct(Identifier var1); + @Nullable T direct(Identifier id); - @Nullable Collection tag(Identifier var1); + @Nullable Collection> tag(Identifier id); } } diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagFile.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagFile.java index 6deaadd25..8f611e242 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagFile.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagFile.java @@ -2,10 +2,20 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.modificationstation.stationapi.api.util.context.Condition; import java.util.List; public record TagFile(List entries, boolean replace) { - public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group(TagEntry.CODEC.listOf().fieldOf("values").forGetter(TagFile::entries), Codec.BOOL.optionalFieldOf("replace", false).forGetter(TagFile::replace)).apply(instance, TagFile::new)); + public static Codec createCodec(Codec> tagConditionCodec) { + return RecordCodecBuilder.create( + instance -> instance.group( + TagEntry.createCodec(tagConditionCodec).listOf().fieldOf("values") + .forGetter(TagFile::entries), + Codec.BOOL.optionalFieldOf("replace", false) + .forGetter(TagFile::replace) + ).apply(instance, TagFile::new) + ); + } } diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagGroupLoader.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagGroupLoader.java index a7bf131c5..27559efad 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagGroupLoader.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagGroupLoader.java @@ -9,7 +9,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonParser; import com.mojang.datafixers.util.Either; -import com.mojang.serialization.DataResult; +import com.mojang.serialization.Codec; import com.mojang.serialization.Dynamic; import com.mojang.serialization.JsonOps; import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; @@ -17,6 +17,7 @@ import net.modificationstation.stationapi.api.util.Identifier; import net.modificationstation.stationapi.api.resource.Resource; import net.modificationstation.stationapi.api.resource.ResourceManager; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.Reader; @@ -33,9 +34,12 @@ public class TagGroupLoader { final Function> registryGetter; private final String dataType; - public TagGroupLoader(Function> registryGetter, String dataType) { + private final Codec tagFileCodec; + + public TagGroupLoader(Function> registryGetter, String dataType, Codec tagFileCodec) { this.registryGetter = registryGetter; this.dataType = dataType; + this.tagFileCodec = tagFileCodec; } public Map> loadTags(ResourceManager manager) { @@ -53,8 +57,7 @@ public Map> loadTags(ResourceManager manager) { try { JsonElement jsonElement = JsonParser.parseReader(reader); List list = map.computeIfAbsent(identifier2, identifierx -> new ArrayList<>()); - DataResult var10000 = TagFile.CODEC.parse(new Dynamic<>(JsonOps.INSTANCE, jsonElement)); - TagFile tagFile = var10000.getOrThrow(); + TagFile tagFile = tagFileCodec.parse(new Dynamic<>(JsonOps.INSTANCE, jsonElement)).getOrThrow(); if (tagFile.replace()) list.clear(); String string2 = resource.getResourcePackName(); @@ -71,7 +74,10 @@ public Map> loadTags(ResourceManager manager) { reader.close(); } catch (Exception var17) { - LOGGER.error("Couldn't read tag list {} from {}"/* in data pack {}"*/, new Object[]{identifier2, identifier/*, resource.getResourcePackName()*/, var17}); + LOGGER.error( + "Couldn't read tag list {} from {} in data pack {}", + identifier2, identifier, resource.getResourcePackName(), var17 + ); } } @@ -94,24 +100,23 @@ private static boolean hasCircularDependency(Multimap mu private static void addReference(Multimap multimap, Identifier identifier, Identifier identifier2) { if (!hasCircularDependency(multimap, identifier, identifier2)) multimap.put(identifier, identifier2); - } - private Either, Collection> resolveAll(TagEntry.ValueGetter valueGetter, List list) { - ImmutableSet.Builder builder = ImmutableSet.builder(); - List list2 = new ArrayList<>(); + private Either, Collection>> resolveAll(TagEntry.ValueGetter valueGetter, List tags) { + ImmutableSet.Builder> matchGroupBuilder = ImmutableSet.builder(); + List missing = new ArrayList<>(); - for (TrackedEntry trackedEntry : list) { - TagEntry var10000 = trackedEntry.entry(); - Objects.requireNonNull(builder); - if (!var10000.resolve(valueGetter, builder::add)) list2.add(trackedEntry); - } + for (TrackedEntry tag : tags) + if (!tag.entry().resolve(valueGetter, matchGroupBuilder::add)) + missing.add(tag); - return list2.isEmpty() ? Either.right(builder.build()) : Either.left(list2); + return missing.isEmpty() + ? Either.right(matchGroupBuilder.build()) + : Either.left(missing); } - public Map> buildGroup(Map> map) { - final Map> map2 = Maps.newHashMap(); + public Map>> buildGroup(Map> map) { + final Map>> map2 = Maps.newHashMap(); TagEntry.ValueGetter valueGetter = new TagEntry.ValueGetter<>() { @Nullable public T direct(Identifier id) { @@ -119,26 +124,52 @@ public T direct(Identifier id) { } @Nullable - public Collection tag(Identifier id) { + public Collection> tag(Identifier id) { return map2.get(id); } }; Multimap multimap = HashMultimap.create(); - map.forEach((identifier, list) -> list.forEach(trackedEntry -> trackedEntry.entry.forEachRequiredTagId(identifier2 -> addReference(multimap, identifier, identifier2)))); - map.forEach((identifier, list) -> list.forEach(trackedEntry -> trackedEntry.entry.forEachOptionalTagId(identifier2 -> addReference(multimap, identifier, identifier2)))); + map.forEach( + (identifier, list) -> list.forEach( + trackedEntry -> trackedEntry.entry.forEachRequiredTagId( + identifier2 -> addReference(multimap, identifier, identifier2 + ) + ) + ) + ); + map.forEach( + (identifier, list) -> list.forEach( + trackedEntry -> trackedEntry.entry.forEachOptionalTagId( + identifier2 -> addReference(multimap, identifier, identifier2) + ) + ) + ); Set set = Sets.newHashSet(); - map.keySet().forEach(identifier -> resolveAll(map, multimap, set, identifier, (identifierx, list) -> this.resolveAll(valueGetter, list).ifLeft(collection -> LOGGER.error("Couldn't load tag {} as it is missing following references: {}", identifierx, collection.stream().map(Objects::toString).collect(Collectors.joining(", ")))).ifRight(collection -> map2.put(identifierx, collection)))); + map.keySet().forEach(identifier -> resolveAll( + map, multimap, set, identifier, (identifierx, list) -> this.resolveAll( + valueGetter, list + ).ifLeft( + collection -> LOGGER.error( + "Couldn't load tag {} as it is missing following references: {}", + identifierx, + collection.stream() + .map(Objects::toString) + .collect(Collectors.joining(", ")) + ) + ).ifRight( + collection -> map2.put(identifierx, collection) + ) + )); return map2; } - public Map> load(ResourceManager manager) { + public Map>> load(ResourceManager manager) { return this.buildGroup(this.loadTags(manager)); } public record TrackedEntry(TagEntry entry, String source) { - @Override - public String toString() { + public @NotNull String toString() { return this.entry.toString() + " (from " + this.source + ")"; } } diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagManagerLoader.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagManagerLoader.java index 7313e6674..7627ade66 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagManagerLoader.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagManagerLoader.java @@ -63,14 +63,18 @@ public CompletableFuture reload( private static void repopulateTags(DynamicRegistryManager dynamicRegistryManager, TagManagerLoader.RegistryTags tags) { RegistryKey> registryKey = tags.key(); - Map, List>> map = tags.tags().entrySet().stream().collect(Collectors.toUnmodifiableMap(entry -> TagKey.of(registryKey, entry.getKey()), entry -> List.copyOf(entry.getValue()))); + Map, Collection>>> map = tags.tags().entrySet().stream().collect(Collectors.toUnmodifiableMap(entry -> TagKey.of(registryKey, entry.getKey()), entry -> entry.getValue())); dynamicRegistryManager.get(registryKey).populateTags(map); } private CompletableFuture> buildRequiredGroup(ResourceManager resourceManager, Executor prepareExecutor, DynamicRegistryManager.Entry requirement) { RegistryKey> registryKey = requirement.key(); Registry registry = requirement.value(); - TagGroupLoader> tagGroupLoader = new TagGroupLoader<>(id -> registry.getEntry(RegistryKey.of(registryKey, id)), getPath(registryKey)); + TagGroupLoader> tagGroupLoader = new TagGroupLoader<>( + id -> registry.getEntry(RegistryKey.of(registryKey, id)), + getPath(registryKey), + TagFile.createCodec(registry.getTagConditionCodec()) + ); return CompletableFuture.supplyAsync(() -> new RegistryTags<>(registryKey, tagGroupLoader.load(resourceManager)), prepareExecutor); } @@ -79,6 +83,6 @@ public Identifier getId() { return TAGS; } - public record RegistryTags(RegistryKey> key, Map>> tags) { } + public record RegistryTags(RegistryKey> key, Map>>> tags) { } } diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagMatchGroup.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagMatchGroup.java new file mode 100644 index 000000000..786e4668e --- /dev/null +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagMatchGroup.java @@ -0,0 +1,17 @@ +package net.modificationstation.stationapi.api.tag; + +import net.modificationstation.stationapi.api.util.context.Condition; +import net.modificationstation.stationapi.api.util.context.Context; + +import java.util.Collection; + +public record TagMatchGroup( + Collection baseItems, + Collection> conditions +) { + public boolean test(T item, Context ctx) { + if (!baseItems.contains(item)) return false; + for (Condition condition : conditions) if (!condition.test(ctx)) return false; + return true; + } +} From b7ba4eceba2aafd8b6a878c55e1b27d49391dbdc Mon Sep 17 00:00:00 2001 From: mine_diver Date: Fri, 15 May 2026 19:20:47 +0500 Subject: [PATCH 2/8] Fixed some internal tag checks --- .../stationapi/mixin/block/FireBlockMixin.java | 5 ++++- .../stationapi/mixin/block/LeavesBlockMixin.java | 11 ++++++----- .../surface/condition/TagSurfaceCondition.java | 3 ++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/FireBlockMixin.java b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/FireBlockMixin.java index 5f139880c..ea40890c3 100644 --- a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/FireBlockMixin.java +++ b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/FireBlockMixin.java @@ -5,6 +5,7 @@ import net.modificationstation.stationapi.api.StationAPI; import net.modificationstation.stationapi.api.event.block.FireBurnableRegisterEvent; import net.modificationstation.stationapi.api.registry.tag.BlockTags; +import net.modificationstation.stationapi.api.tag.conditional.BlockContext; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -41,6 +42,8 @@ private void stationapi_postBurnableRegister(CallbackInfo ci) { } ) private int stationapi_allowInfiniburnBlocks(int constant, World world, int x, int y, int z, Random random) { - return world.getBlockState(x, y - 1, z).isIn(BlockTags.INFINIBURN) ? 1 : 0; + return world.getBlockState(x, y - 1, z).isIn( + BlockTags.INFINIBURN, BlockContext.of(world, x, y - 1, z) + ) ? 1 : 0; } } diff --git a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/LeavesBlockMixin.java b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/LeavesBlockMixin.java index cc5bbf2ff..4198f8bf0 100644 --- a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/LeavesBlockMixin.java +++ b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/LeavesBlockMixin.java @@ -7,6 +7,7 @@ import net.minecraft.world.World; import net.modificationstation.stationapi.api.block.BlockState; import net.modificationstation.stationapi.api.registry.tag.BlockTags; +import net.modificationstation.stationapi.api.tag.conditional.BlockContext; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -14,15 +15,15 @@ public class LeavesBlockMixin { @WrapOperation(method = "onTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;getBlockId(III)I")) - private int makeModdedLogsAndLeavesWork(World instance, int x, int y, int z, Operation original) { - BlockState state = instance.getBlockState(x, y, z); - if (state.isIn(BlockTags.LOGS)) { + private int makeModdedLogsAndLeavesWork(World world, int x, int y, int z, Operation original) { + BlockState state = world.getBlockState(x, y, z); + if (state.isIn(BlockTags.LOGS, BlockContext.of(world, x, y, z))) { return Block.LOG.id; } - if (state.isIn(BlockTags.LEAVES)) { + if (state.isIn(BlockTags.LEAVES, BlockContext.of(world, x, y, z))) { return Block.LEAVES.id; } - return original.call(instance, x, y, z); + return original.call(world, x, y, z); } } diff --git a/station-worldgen-api-v0/src/main/java/net/modificationstation/stationapi/api/worldgen/surface/condition/TagSurfaceCondition.java b/station-worldgen-api-v0/src/main/java/net/modificationstation/stationapi/api/worldgen/surface/condition/TagSurfaceCondition.java index 9b81da372..3c6035348 100644 --- a/station-worldgen-api-v0/src/main/java/net/modificationstation/stationapi/api/worldgen/surface/condition/TagSurfaceCondition.java +++ b/station-worldgen-api-v0/src/main/java/net/modificationstation/stationapi/api/worldgen/surface/condition/TagSurfaceCondition.java @@ -4,6 +4,7 @@ import net.minecraft.world.World; import net.modificationstation.stationapi.api.block.BlockState; import net.modificationstation.stationapi.api.tag.TagKey; +import net.modificationstation.stationapi.api.tag.conditional.BlockContext; public class TagSurfaceCondition implements SurfaceCondition { private final TagKey tag; @@ -14,6 +15,6 @@ public TagSurfaceCondition(TagKey tag) { @Override public boolean canApply(World world, int x, int y, int z, BlockState state) { - return state.isIn(tag); + return state.isIn(tag, BlockContext.of(world, x, y, z)); } } From 19ebb919f5d024495f04ebbf4257ef1849256b55 Mon Sep 17 00:00:00 2001 From: mine_diver Date: Sun, 17 May 2026 13:13:58 +0500 Subject: [PATCH 3/8] Helper method for registering tag conditions without data --- .../stationapi/api/registry/Registry.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java index 6eeba9a1b..2617b90e5 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java @@ -2,6 +2,7 @@ import com.mojang.datafixers.DataFixUtils; import com.mojang.datafixers.util.Pair; +import com.mojang.datafixers.util.Unit; import com.mojang.serialization.*; import net.minecraft.block.Block; import net.minecraft.item.Item; @@ -20,6 +21,7 @@ import java.util.*; import java.util.function.BiPredicate; import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.ToIntFunction; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -504,6 +506,10 @@ public RegistryEntryList.Named getOrThrow(TagKey tag) { }; } + default void registerTagCondition(Identifier id, Predicate condition) { + registerTagCondition(id, MapCodec.unit(Unit.INSTANCE), (data, ctx) -> condition.test(ctx)); + } + void registerTagCondition( Identifier id, MapCodec codec, BiPredicate condition ); From 7c1848b03ecc7558f8eba8c9fb1b8424ed4aec85 Mon Sep 17 00:00:00 2001 From: mine_diver Date: Wed, 27 May 2026 21:19:35 +0500 Subject: [PATCH 4/8] Tag removal, better data/resources sorting, better specialized contexts, general context system improvements --- .../api/util/context/ActorContext.java | 26 +++ .../stationapi/api/util/context/Context.java | 217 +++++++++--------- .../api/tag/conditional/BlockContext.java | 33 --- .../tag/conditional/BlockTagConditions.java | 17 -- .../conditional/BlockTagConditionsImpl.java | 8 +- .../resources/station-blocks-v0.mixins.json | 5 +- .../api/block/AbstractBlockState.java | 37 ++- .../stationapi/api/block/BlockContext.java | 116 ++++++++++ .../stationapi/api/item/ItemContext.java | 92 ++++++++ .../api/item/StationFlatteningItemStack.java | 7 +- .../api/registry/BlockRegistry.java | 33 ++- .../stationapi/api/registry/ItemRegistry.java | 33 ++- .../api/tag/conditional/ItemContext.java | 28 --- .../tag/conditional/ItemTagConditions.java | 17 -- .../conditional/ItemTagConditionsImpl.java | 8 +- .../stationapi/mixin/item/ItemStackMixin.java | 7 +- .../stationapi/api/registry/Registry.java | 35 +++ .../api/registry/RegistryEntry.java | 25 +- .../api/registry/RegistryEntryList.java | 36 +-- .../api/registry/SimpleRegistry.java | 7 +- .../stationapi/api/tag/TagEntry.java | 4 +- .../stationapi/api/tag/TagFile.java | 6 +- .../stationapi/api/tag/TagGroupLoader.java | 9 +- .../stationapi/api/tag/TagMatchGroup.java | 3 +- .../util/context/TagEvaluationContext.java | 21 ++ .../impl/resource/ModResourcePackUtil.java | 69 +++++- .../vanillafix}/client/InGameHudMixin.java | 2 +- .../src/main/resources/fabric.mod.json | 3 + .../station-vanilla-fix-v0.mixins.json | 1 + 29 files changed, 614 insertions(+), 291 deletions(-) create mode 100644 station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ActorContext.java delete mode 100644 station-blocks-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/BlockContext.java delete mode 100644 station-blocks-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/BlockTagConditions.java create mode 100644 station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/BlockContext.java create mode 100644 station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/ItemContext.java delete mode 100644 station-items-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/ItemContext.java delete mode 100644 station-items-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/ItemTagConditions.java create mode 100644 station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/util/context/TagEvaluationContext.java rename {station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block => station-vanilla-fix-v0/src/main/java/net/modificationstation/stationapi/mixin/vanillafix}/client/InGameHudMixin.java (98%) diff --git a/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ActorContext.java b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ActorContext.java new file mode 100644 index 000000000..d9e3a3e12 --- /dev/null +++ b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ActorContext.java @@ -0,0 +1,26 @@ +package net.modificationstation.stationapi.api.util.context; + +import org.jetbrains.annotations.Nullable; + +import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; + +/** + * A context that includes an actor that performed the action. + */ +public interface ActorContext extends Context { + /** + * The context key used to evaluate actor-related conditions. + */ + Context.Key ACTOR = new Context.Key<>(NAMESPACE.id("actor")); + + static ActorContext of(Context context) { + return context instanceof ActorContext a ? a : context::getRaw; + } + + /** + * {@return the actor performing the action, or {@code null} if none} + */ + default @Nullable Object actor() { + return get(ACTOR); + } +} diff --git a/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/Context.java b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/Context.java index 243c6c81b..0a5fc67f7 100644 --- a/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/Context.java +++ b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/Context.java @@ -1,178 +1,175 @@ package net.modificationstation.stationapi.api.util.context; import net.modificationstation.stationapi.api.util.Identifier; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Optional; +/** + * A data source that contains keys and their associated values. + *

+ * This is a functional interface that can be implemented with a lambda + * to provide raw values based on the given {@link Identifier}. + */ +@FunctionalInterface public interface Context { /** - * A context that contains no keys and always returns {@code null}. + * A delegate interface for creating zero-allocation projections. */ - Context EMPTY = new Empty(); + @FunctionalInterface + interface Delegate extends Context { + Context delegate(); + + @Override + default @Nullable Object getRaw(Identifier id) { + return delegate().getRaw(id); + } + + @Override + default boolean contains(Identifier id) { + return delegate().contains(id); + } + + @Override + default int getIntRaw(Identifier id, int defaultValue) { + return delegate().getIntRaw(id, defaultValue); + } + } /** - * A context that signals the bypassing of conditional checks. + * A context that contains no keys and always returns {@code null}. */ - Context ANY = new Any(); + Context EMPTY = id -> null; @SuppressWarnings("unused") record Key(Identifier id) {} /** - * {@return the value associated with the given key, or {@code null} if not - * present} + * {@return a new context containing a single key-value pair} */ - @Nullable VALUE get(Key key); + static Context of(Key key, VALUE value) { + Identifier id = key.id(); + return k -> id == k ? value : null; + } /** - * {@return whether this context matches all conditions unconditionally} + * {@return the raw value associated with the given identifier, or {@code null} if not + * present} *

- * When {@code true}, condition evaluation should be bypassed entirely, - * and all entries should be treated as matching. Calling {@link #get} on - * such a context is unsupported and may throw. + * This is the primary abstract method for implementations. */ - default boolean matchesAll() { - return false; - } + @Nullable Object getRaw(Identifier id); /** - * {@return whether this context contains no keys} + * {@return the value associated with the given key, or {@code null} if not + * present} */ - default boolean isEmpty() { - return false; + @SuppressWarnings("unchecked") + default @Nullable VALUE get(Key key) { + return (VALUE) getRaw(key.id()); } /** - * {@return an optional containing the value associated with the given key} + * {@return whether this context contains a value associated with the given identifier} */ - default Optional getOptional(Key key) { - return Optional.ofNullable(get(key)); + default boolean contains(Identifier id) { + return getRaw(id) != null; } /** - * {@return a new context that includes the given context as an override} + * {@return whether this context contains the given key} */ - default Context with(Context other) { - return append(this, other); + default boolean contains(Key key) { + return contains(key.id()); } /** - * {@return a new context that includes the given contexts as overrides in the - * provided order} + * {@return an optional containing the value associated with the given identifier} */ - default Context with(Context... others) { - Context result = this; - for (Context other : others) result = result.with(other); - return result; + default Optional getOptional(Identifier id) { + return Optional.ofNullable(getRaw(id)); } - private static Context append(Context base, Context addition) { - if (addition.isEmpty() || base.matchesAll()) return base; - if (base.isEmpty() || addition.matchesAll()) return addition; - return addition instanceof Composite composite - ? append(append(base, composite.next), composite.head) - : new Composite(addition, base); + /** + * {@return an optional containing the value associated with the given key} + */ + default Optional getOptional(Key key) { + return Optional.ofNullable(get(key)); } /** - * {@return a new context that includes the given key-value pair as an override} + * {@return the unboxed integer associated with the given key, or {@code defaultValue} if not present} */ - default Context with(Key key, VALUE value) { - return with(new Singleton<>(key, value)); + default int getInt(Key key, int defaultValue) { + return getIntRaw(key.id(), defaultValue); } /** - * A context that contains no keys. + * {@return the unboxed integer associated with the given identifier, or {@code defaultValue} if not present} */ - record Empty() implements Context { - @Override - public @Nullable VALUE get(Key key) { - return null; - } - - @Override - public boolean isEmpty() { - return true; - } - - @Override - public @NotNull String toString() { - return "EmptyContext"; - } + default int getIntRaw(Identifier id, int defaultValue) { + Object raw = getRaw(id); + return raw instanceof Integer i ? i : defaultValue; } /** - * A context that represents any context. Should only be used for reference - * checks to bypass evaluations. + * {@return a new context that includes the given key-value pair as an override} */ - record Any() implements Context { - @Override - public @Nullable VALUE get(Key key) { - throw new UnsupportedOperationException( - "Cannot get values from Context.ANY. It is only meant for evaluation bypasses." - ); - } - - @Override - public boolean matchesAll() { - return true; - } - - @Override - public @NotNull String toString() { - return "AnyContext"; - } + default Context with(Key key, VALUE value) { + Identifier id = key.id(); + return with(k -> id == k ? value : null); } /** - * A context containing a single key-value pair. + * {@return a new context that includes the given context as an override} */ - record Singleton(Key singletonKey, SINGLETON singletonValue) implements Context { - @Override - public @Nullable VALUE get(Key key) { - // noinspection unchecked - return singletonKey.equals(key) ? (VALUE) singletonValue : null; - } + default Context with(Context other) { + return append(this, other); } /** - * A context that delegates to two other contexts. - *

- * This implementation uses an iterative walk to avoid - * {@link StackOverflowError} on deep chains. + * {@return a new context that includes the given contexts as overrides in the + * provided order} */ - record Composite(Context head, Context next) implements Context { - @Override - public @Nullable VALUE get(Key key) { - Context current = this; - while (current instanceof Composite composite) { - VALUE value = composite.head.get(key); - if (value != null) return value; - current = composite.next; + default Context with(Context... others) { + Context result = this; + for (Context other : others) result = result.with(other); + return result; + } + + private static Context append(Context base, Context addition) { + if (addition == EMPTY) return base; + if (base == EMPTY) return addition; + + record Composite(Context head, Context next) implements Context { + private Context find(Identifier id) { + Context current = this; + while (current instanceof Composite comp) { + if (comp.head.contains(id)) return comp.head; + current = comp.next; + } + return current != null && current.contains(id) ? current : null; } - return current.get(key); - } - @Override - public boolean matchesAll() { - Context current = this; - while (current instanceof Composite composite) { - if (composite.head.matchesAll()) return true; - current = composite.next; + @Override + public @Nullable Object getRaw(Identifier id) { + Context ctx = find(id); + return ctx == null ? null : ctx.getRaw(id); } - return current.matchesAll(); - } - @Override - public boolean isEmpty() { - Context current = this; - while (current instanceof Composite composite) { - if (!composite.head.isEmpty()) return false; - current = composite.next; + @Override + public boolean contains(Identifier id) { + return find(id) != null; + } + + @Override + public int getIntRaw(Identifier id, int defaultValue) { + Context ctx = find(id); + return ctx == null ? defaultValue : ctx.getIntRaw(id, defaultValue); } - return current.isEmpty(); } + return addition instanceof Composite composite + ? append(append(base, composite.next), composite.head) + : new Composite(addition, base); } } diff --git a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/BlockContext.java b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/BlockContext.java deleted file mode 100644 index c6aa6e2b1..000000000 --- a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/BlockContext.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.modificationstation.stationapi.api.tag.conditional; - -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.BlockView; -import net.modificationstation.stationapi.api.util.context.Context; -import org.jetbrains.annotations.Nullable; - -public final class BlockContext implements Context { - private static final ThreadLocal INSTANCE = ThreadLocal.withInitial(BlockContext::new); - - public static BlockContext of(BlockView world, BlockPos pos) { - return of(world, pos.x, pos.y, pos.z); - } - - public static BlockContext of(BlockView world, int x, int y, int z) { - BlockContext ctx = INSTANCE.get(); - ctx.metadata = world.getBlockMeta(x, y, z); - return ctx; - } - - private Integer metadata; - - private BlockContext() {} - - @Override - public @Nullable VALUE get(Key key) { - if (key == BlockTagConditions.BLOCK_METADATA) { - //noinspection unchecked - return (VALUE) metadata; - } - return null; - } -} diff --git a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/BlockTagConditions.java b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/BlockTagConditions.java deleted file mode 100644 index 7997b3d24..000000000 --- a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/BlockTagConditions.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.modificationstation.stationapi.api.tag.conditional; - -import net.modificationstation.stationapi.api.util.context.Context; - -import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; - -public final class BlockTagConditions { - /** - * The context key used to evaluate block metadata tag conditions. - *

- * When populating a context to evaluate a tag that may contain block metadata conditions, - * provide the current metadata integer (0-15) via this key. - */ - public static final Context.Key BLOCK_METADATA = new Context.Key<>(NAMESPACE.id("block_metadata")); - - private BlockTagConditions() {} -} diff --git a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/BlockTagConditionsImpl.java b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/BlockTagConditionsImpl.java index f7a0d1851..efe91b1b7 100644 --- a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/BlockTagConditionsImpl.java +++ b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/BlockTagConditionsImpl.java @@ -7,7 +7,6 @@ import net.modificationstation.stationapi.api.mod.entrypoint.Entrypoint; import net.modificationstation.stationapi.api.mod.entrypoint.EntrypointManager; import net.modificationstation.stationapi.api.mod.entrypoint.EventBusPolicy; -import net.modificationstation.stationapi.api.tag.conditional.BlockTagConditions; import java.lang.invoke.MethodHandles; @@ -22,13 +21,10 @@ public class BlockTagConditionsImpl { @EventListener private static void registerConditions(BlockRegistryEvent event) { - event.registry.registerTagCondition( + event.registry.registerBlockTagCondition( NAMESPACE.id("block_metadata"), Codec.INT.fieldOf("metadata"), - (metadata, ctx) -> { - Integer ctxMeta = ctx.get(BlockTagConditions.BLOCK_METADATA); - return ctxMeta != null && ctxMeta.equals(metadata); - } + (metadata, ctx) -> ctx.hasBlockMeta() && ctx.blockMeta() == metadata ); } } diff --git a/station-blocks-v0/src/main/resources/station-blocks-v0.mixins.json b/station-blocks-v0/src/main/resources/station-blocks-v0.mixins.json index 5df79d72b..2951760da 100644 --- a/station-blocks-v0/src/main/resources/station-blocks-v0.mixins.json +++ b/station-blocks-v0/src/main/resources/station-blocks-v0.mixins.json @@ -12,8 +12,5 @@ ], "injectors": { "defaultRequire": 1 - }, - "client": [ - "client.InGameHudMixin" - ] + } } diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java index f54dd3b42..f6b3d2427 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java @@ -87,27 +87,42 @@ public boolean canReplace(ItemPlacementContext context) { return this.getBlock().canReplace(this.asBlockState(), context); } + /** + * @deprecated Use {@link #isIn(TagKey, Context, Predicate)} instead. + * Relying on tag checks without a {@link Context} can lead to broken behavior if the tag contains conditions + * that require contextual information (such as the block's world position, metadata, or the actor interacting with it). + */ @Deprecated - public boolean isIn(TagKey tag) { - return this.isIn(tag, Context.EMPTY); - } - - public boolean isIn(TagKey tag, Context context) { - return getBlock().getRegistryEntry().isIn(tag, context); + public boolean isIn(TagKey tag, Predicate predicate) { + return isIn(tag) && predicate.test(this); } + /** + * @deprecated Use {@link #isIn(TagKey, Context)} instead. + * Relying on tag checks without a {@link Context} can lead to broken behavior if the tag contains conditions + * that require contextual information (such as the block's world position, metadata, or the actor interacting with it). + */ @Deprecated - public boolean isIn(TagKey tag, Predicate predicate) { - return this.isIn(tag, Context.EMPTY, predicate); + public boolean isIn(TagKey tag) { + return isIn(tag, Context.EMPTY); } public boolean isIn(TagKey tag, Context context, Predicate predicate) { - return this.isIn(tag, context) && predicate.test(this); + return isIn(tag, context) && predicate.test(this); } + public boolean isIn(TagKey tag, Context context) { + return getBlock().getRegistryEntry().isIn(tag, context); + } + + /** + * @deprecated Use {@link #isIn(RegistryEntryList, Context)} instead. + * Relying on list checks without a {@link Context} can lead to broken behavior if the underlying entries + * have contextual conditions. + */ @Deprecated public boolean isIn(RegistryEntryList blocks) { - return this.isIn(blocks, Context.EMPTY); + return isIn(blocks, Context.EMPTY); } public boolean isIn(RegistryEntryList blocks, Context context) { @@ -115,7 +130,7 @@ public boolean isIn(RegistryEntryList blocks, Context context) { } public Stream> streamTags() { - return this.streamTags(Context.ANY); + return streamTags(TagEvaluationContext.BYPASSED); } public Stream> streamTags(Context context) { diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/BlockContext.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/BlockContext.java new file mode 100644 index 000000000..2867dcd29 --- /dev/null +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/BlockContext.java @@ -0,0 +1,116 @@ +package net.modificationstation.stationapi.api.block; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.BlockView; +import net.modificationstation.stationapi.api.util.Identifier; +import net.modificationstation.stationapi.api.util.context.Context; +import org.jetbrains.annotations.Nullable; + +import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; + +/** + * Context for block-related evaluation. + * Contains only the intrinsic state of a block in the world. + */ +@FunctionalInterface +public interface BlockContext extends Context { + BlockContext EMPTY = id -> null; + + /** + * The context key used to evaluate world-related conditions in a block context. + */ + Context.Key BLOCK_VIEW = new Context.Key<>(NAMESPACE.id("block_view")); + + /** + * The context key used to evaluate position-related conditions in a block context. + */ + Context.Key BLOCK_POS = new Context.Key<>(NAMESPACE.id("block_pos")); + + /** + * The context key used to evaluate block metadata conditions. + */ + Context.Key BLOCK_METADATA = new Context.Key<>(NAMESPACE.id("block_metadata")); + + /** + * {@return whether this context has block metadata} + */ + default boolean hasBlockMeta() { + return contains(BLOCK_METADATA) || (blockView() != null && blockPos() != null); + } + + /** + * {@return the block view the block interaction is occurring in} + */ + default @Nullable BlockView blockView() { return get(BLOCK_VIEW); } + + /** + * {@return the position of the block interaction} + */ + default @Nullable BlockPos blockPos() { return get(BLOCK_POS); } + + /** + * {@return the block metadata} + */ + default int blockMeta() { + return getIntRaw(BLOCK_METADATA.id(), 0); + } + + interface DataProvider extends BlockContext { + @Override + @Nullable BlockView blockView(); + + @Override + @Nullable BlockPos blockPos(); + + @Override + default int blockMeta() { + BlockView view = blockView(); + BlockPos pos = blockPos(); + return view != null && pos != null ? view.getBlockMeta(pos.x, pos.y, pos.z) : 0; + } + + @Override + default Object getRaw(Identifier id) { + if (BLOCK_VIEW.id() == id) return blockView(); + if (BLOCK_POS.id() == id) return blockPos(); + if (BLOCK_METADATA.id() == id) return blockView() != null && blockPos() != null ? blockMeta() : null; + return null; + } + + @Override + default int getIntRaw(Identifier id, int defaultValue) { + if (BLOCK_METADATA.id() == id) return blockMeta(); + return BlockContext.super.getIntRaw(id, defaultValue); + } + } + + /** + * Creates a new block context. + */ + static BlockContext of(BlockView world, BlockPos pos) { + record Impl( + @Nullable BlockView blockView, + @Nullable BlockPos blockPos + ) implements DataProvider {} + return new Impl(world, pos); + } + + /** + * Creates a new block context. + */ + static BlockContext of(BlockView world, int x, int y, int z) { + return of(world, new BlockPos(x, y, z)); + } + + /** + * Projects a generic context into a block context view. + * + * @param context the context to project + * @return the block context view + */ + static BlockContext of(Context context) { + if (context instanceof BlockContext b) return b; + interface BlockContextDelegate extends BlockContext, Delegate {} + return (BlockContextDelegate) () -> context; + } +} diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/ItemContext.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/ItemContext.java new file mode 100644 index 000000000..8fdac326f --- /dev/null +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/ItemContext.java @@ -0,0 +1,92 @@ +package net.modificationstation.stationapi.api.item; + +import net.minecraft.item.ItemStack; +import net.modificationstation.stationapi.api.util.Identifier; +import net.modificationstation.stationapi.api.util.context.Context; +import org.jetbrains.annotations.Nullable; + +import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; + +/** + * Context for item-related evaluation. + */ +@FunctionalInterface +public interface ItemContext extends Context { + ItemContext EMPTY = id -> null; + + /** + * The context key used to evaluate item-related conditions in an item context. + */ + Key ITEM_STACK = new Key<>(NAMESPACE.id("item_stack")); + + /** + * The context key used to evaluate item damage conditions. + */ + Key ITEM_DAMAGE = new Key<>(NAMESPACE.id("item_damage")); + + /** + * {@return whether this context has item damage data} + */ + default boolean hasDamage() { + return contains(ITEM_DAMAGE) || itemStack() != null; + } + + @FunctionalInterface + interface DataProvider extends ItemContext { + @Override + @Nullable ItemStack itemStack(); + + @Override + default int damage() { + ItemStack stack = itemStack(); + return stack == null ? 0 : stack.getDamage(); + } + + @Override + default Object getRaw(Identifier id) { + if (ITEM_STACK.id() == id) return itemStack(); + if (ITEM_DAMAGE.id() == id) return itemStack() == null ? null : damage(); + return null; + } + + @Override + default int getIntRaw(Identifier id, int defaultValue) { + if (ITEM_DAMAGE.id() == id) return damage(); + return ItemContext.super.getIntRaw(id, defaultValue); + } + } + + /** + * {@return the item stack being evaluated} + */ + default @Nullable ItemStack itemStack() { return get(ITEM_STACK); } + + /** + * {@return the item damage} + */ + default int damage() { + Integer dmg = get(ITEM_DAMAGE); + if (dmg != null) return dmg; + ItemStack stack = itemStack(); + return stack == null ? 0 : stack.getDamage(); + } + + /** + * Creates a new item context. + */ + static ItemContext of(ItemStack stack) { + return (DataProvider) () -> stack; + } + + /** + * Projects a generic context into an item context view. + * + * @param context the context to project + * @return the item context view + */ + static ItemContext of(Context context) { + if (context instanceof ItemContext i) return i; + interface ItemContextDelegate extends ItemContext, Delegate {} + return (ItemContextDelegate) () -> context; + } +} diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/StationFlatteningItemStack.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/StationFlatteningItemStack.java index 7fb397068..12eb20adb 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/StationFlatteningItemStack.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/StationFlatteningItemStack.java @@ -18,6 +18,11 @@ default RegistryEntry.Reference getRegistryEntry() { return Util.assertImpl(); } + /** + * Since an {@link ItemStack} is itself an item's context, + * there's no need to provide the context manually here, + * it gets prepended internally + */ default boolean isIn(TagKey tag) { return isIn(tag, Context.EMPTY); } @@ -27,7 +32,7 @@ default boolean isIn(TagKey tag, Context context) { } default Stream> streamTags() { - return streamTags(Context.ANY); + return streamTags(TagEvaluationContext.BYPASSED); } default Stream> streamTags(Context context) { diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/BlockRegistry.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/BlockRegistry.java index e165c9068..c05e9f308 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/BlockRegistry.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/BlockRegistry.java @@ -1,14 +1,21 @@ package net.modificationstation.stationapi.api.registry; import com.mojang.serialization.Lifecycle; +import com.mojang.serialization.MapCodec; import net.minecraft.block.Block; import net.modificationstation.stationapi.api.event.registry.RegistryAttribute; import net.modificationstation.stationapi.api.event.registry.RegistryAttributeHolder; +import net.modificationstation.stationapi.api.block.BlockContext; +import net.modificationstation.stationapi.api.util.Identifier; +import net.modificationstation.stationapi.api.util.context.Context; + +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Predicate; import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; public final class BlockRegistry extends SimpleRegistry { - public static final RegistryKey> KEY = RegistryKey.ofRegistry(NAMESPACE.id("blocks")); public static final BlockRegistry INSTANCE = Registries.create(KEY, new BlockRegistry(), Lifecycle.experimental()); @@ -17,4 +24,28 @@ private BlockRegistry() { RegistryAttributeHolder.get(this).addAttribute(RegistryAttribute.SYNCED); nextId = 256; } + + /** + * Registers a data-less tag condition that operates on a {@link BlockContext}. + *

+ * This is a convenience overload that automatically projects the raw context + * via {@link BlockContext#of(Context)}. + * + * @see #registerTagCondition(Identifier, Function, Predicate) + */ + public void registerBlockTagCondition(Identifier id, Predicate condition) { + registerTagCondition(id, BlockContext::of, condition); + } + + /** + * Registers a data-backed tag condition that operates on a {@link BlockContext}. + *

+ * This is a convenience overload that automatically projects the raw context + * via {@link BlockContext#of(Context)}. + * + * @see #registerTagCondition(Identifier, MapCodec, Function, BiPredicate) + */ + public void registerBlockTagCondition(Identifier id, MapCodec codec, BiPredicate condition) { + registerTagCondition(id, codec, BlockContext::of, condition); + } } diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/ItemRegistry.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/ItemRegistry.java index 9707b599d..6d4df9521 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/ItemRegistry.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/ItemRegistry.java @@ -1,15 +1,22 @@ package net.modificationstation.stationapi.api.registry; import com.mojang.serialization.Lifecycle; +import com.mojang.serialization.MapCodec; import it.unimi.dsi.fastutil.ints.Int2IntFunction; import net.minecraft.item.Item; import net.modificationstation.stationapi.api.event.registry.RegistryAttribute; import net.modificationstation.stationapi.api.event.registry.RegistryAttributeHolder; +import net.modificationstation.stationapi.api.item.ItemContext; +import net.modificationstation.stationapi.api.util.Identifier; +import net.modificationstation.stationapi.api.util.context.Context; + +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Predicate; import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; public final class ItemRegistry extends SimpleRegistry { - public static final RegistryKey> KEY = RegistryKey.ofRegistry(NAMESPACE.id("items")); public static final ItemRegistry INSTANCE = Registries.create(KEY, new ItemRegistry(), Lifecycle.experimental()); public static final int ID_SHIFT = 256; @@ -20,4 +27,28 @@ private ItemRegistry() { super(KEY, Lifecycle.experimental(), true); RegistryAttributeHolder.get(this).addAttribute(RegistryAttribute.SYNCED); } + + /** + * Registers a data-less tag condition that operates on an {@link ItemContext}. + *

+ * This is a convenience overload that automatically projects the raw context + * via {@link ItemContext#of(Context)}. + * + * @see #registerTagCondition(Identifier, Function, Predicate) + */ + public void registerItemTagCondition(Identifier id, Predicate condition) { + registerTagCondition(id, ItemContext::of, condition); + } + + /** + * Registers a data-backed tag condition that operates on an {@link ItemContext}. + *

+ * This is a convenience overload that automatically projects the raw context + * via {@link ItemContext#of(Context)}. + * + * @see #registerTagCondition(Identifier, MapCodec, Function, BiPredicate) + */ + public void registerItemTagCondition(Identifier id, MapCodec codec, BiPredicate condition) { + registerTagCondition(id, codec, ItemContext::of, condition); + } } diff --git a/station-items-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/ItemContext.java b/station-items-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/ItemContext.java deleted file mode 100644 index 0d0a936af..000000000 --- a/station-items-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/ItemContext.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.modificationstation.stationapi.api.tag.conditional; - -import net.minecraft.item.ItemStack; -import net.modificationstation.stationapi.api.util.context.Context; -import org.jetbrains.annotations.Nullable; - -public final class ItemContext implements Context { - private static final ThreadLocal INSTANCE = ThreadLocal.withInitial(ItemContext::new); - - public static ItemContext of(ItemStack stack) { - ItemContext ctx = INSTANCE.get(); - ctx.damage = stack.getDamage(); - return ctx; - } - - private Integer damage; - - private ItemContext() {} - - @Override - public @Nullable VALUE get(Key key) { - if (key == ItemTagConditions.ITEM_DAMAGE) { - //noinspection unchecked - return (VALUE) damage; - } - return null; - } -} diff --git a/station-items-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/ItemTagConditions.java b/station-items-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/ItemTagConditions.java deleted file mode 100644 index ca9b68f5c..000000000 --- a/station-items-v0/src/main/java/net/modificationstation/stationapi/api/tag/conditional/ItemTagConditions.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.modificationstation.stationapi.api.tag.conditional; - -import net.modificationstation.stationapi.api.util.context.Context; - -import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; - -public final class ItemTagConditions { - /** - * The context key used to evaluate item damage tag conditions. - *

- * When populating a context to evaluate a tag that may contain item damage conditions, - * provide the current item damage integer via this key. - */ - public static final Context.Key ITEM_DAMAGE = new Context.Key<>(NAMESPACE.id("item_damage")); - - private ItemTagConditions() {} -} diff --git a/station-items-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/ItemTagConditionsImpl.java b/station-items-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/ItemTagConditionsImpl.java index 141c4519f..692312f1c 100644 --- a/station-items-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/ItemTagConditionsImpl.java +++ b/station-items-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/ItemTagConditionsImpl.java @@ -7,7 +7,6 @@ import net.modificationstation.stationapi.api.mod.entrypoint.Entrypoint; import net.modificationstation.stationapi.api.mod.entrypoint.EntrypointManager; import net.modificationstation.stationapi.api.mod.entrypoint.EventBusPolicy; -import net.modificationstation.stationapi.api.tag.conditional.ItemTagConditions; import java.lang.invoke.MethodHandles; @@ -22,13 +21,10 @@ public class ItemTagConditionsImpl { @EventListener private static void registerConditions(ItemRegistryEvent event) { - event.registry.registerTagCondition( + event.registry.registerItemTagCondition( NAMESPACE.id("item_damage"), Codec.INT.fieldOf("damage"), - (damage, ctx) -> { - Integer ctxDamage = ctx.get(ItemTagConditions.ITEM_DAMAGE); - return ctxDamage != null && ctxDamage.equals(damage); - } + (damage, ctx) -> ctx.hasDamage() && ctx.damage() == damage ); } } diff --git a/station-items-v0/src/main/java/net/modificationstation/stationapi/mixin/item/ItemStackMixin.java b/station-items-v0/src/main/java/net/modificationstation/stationapi/mixin/item/ItemStackMixin.java index fc4a05c14..42b071548 100644 --- a/station-items-v0/src/main/java/net/modificationstation/stationapi/mixin/item/ItemStackMixin.java +++ b/station-items-v0/src/main/java/net/modificationstation/stationapi/mixin/item/ItemStackMixin.java @@ -54,6 +54,9 @@ private void stationapi_onCreation(World arg, PlayerEntity arg1, CallbackInfo ci @Unique private NbtCompound stationapi_stationNbt = new NbtCompound(); + @Unique + private final ItemContext stationapi_itemContext = ItemContext.of((ItemStack) (Object) this); + @Inject( method = "split", at = @At("RETURN") @@ -177,12 +180,12 @@ public void setStationNbt(NbtCompound stationNbt) { @Override @Unique public boolean isIn(TagKey tag, Context context) { - return getRegistryEntry().isIn(tag, ItemContext.of((ItemStack) (Object) this).with(context)); + return getRegistryEntry().isIn(tag, stationapi_itemContext.with(context)); } @Override @Unique public Stream> streamTags(Context context) { - return getRegistryEntry().streamTags(ItemContext.of((ItemStack) (Object) this).with(context)); + return getRegistryEntry().streamTags(stationapi_itemContext.with(context)); } } diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java index 2617b90e5..ef079ab80 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java @@ -510,9 +510,44 @@ default void registerTagCondition(Identifier id, Predicate condition) { registerTagCondition(id, MapCodec.unit(Unit.INSTANCE), (data, ctx) -> condition.test(ctx)); } + /** + * Registers a tag condition with a context projection. + * + * @param id the condition ID + * @param projection the function to project the raw context into a specific view + * @param condition the condition to test against the projected view + * @param the type of the projected context + */ + default void registerTagCondition( + Identifier id, + Function projection, + Predicate condition + ) { + registerTagCondition(id, ctx -> condition.test(projection.apply(ctx))); + } + void registerTagCondition( Identifier id, MapCodec codec, BiPredicate condition ); + /** + * Registers a data-backed tag condition with a view context projection. + * + * @param id the condition ID + * @param codec the codec for the condition data + * @param projection the function to project the raw context into a specific view + * @param condition the condition to test against the data and projected view + * @param the type of the condition data + * @param the type of the projected context + */ + default void registerTagCondition( + Identifier id, + MapCodec codec, + Function projection, + BiPredicate condition + ) { + registerTagCondition(id, codec, (data, ctx) -> condition.test(data, projection.apply(ctx))); + } + Codec> getTagConditionCodec(); } diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntry.java index bee92f1c1..32b8feec0 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntry.java @@ -4,9 +4,12 @@ import net.modificationstation.stationapi.api.tag.TagKey; import net.modificationstation.stationapi.api.util.Identifier; import net.modificationstation.stationapi.api.util.context.Context; +import net.modificationstation.stationapi.api.util.context.TagEvaluationContext; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Stream; @@ -33,14 +36,8 @@ default boolean isIn(TagKey tag) { boolean isIn(TagKey tag, Context context); - /** - * @deprecated Use {@link #streamTags(Context)} instead. - *

This method implicitly uses {@link Context#ANY}, meaning it will yield all tag keys - * this entry belongs to, bypassing all conditional checks. - */ - @Deprecated default Stream> streamTags() { - return streamTags(Context.ANY); + return streamTags(TagEvaluationContext.BYPASSED); } Stream> streamTags(Context context); @@ -143,9 +140,10 @@ public boolean matchesKey(RegistryKey key) { @Override public boolean isIn(TagKey tag, Context context) { - if (context.matchesAll()) return tags.containsKey(tag); + if (!tags.containsKey(tag)) return false; + if (Boolean.TRUE.equals(context.get(TagEvaluationContext.IGNORE_TAG_CONDITIONS))) return true; Predicate predicate = tags.get(tag); - return predicate != null && predicate.test(context); + return predicate == null || predicate.test(context); } @Override @@ -183,9 +181,10 @@ void setTags(Map, Predicate> tags) { @Override public Stream> streamTags(Context context) { - if (context.matchesAll()) return this.tags.keySet().stream(); - return this.tags.entrySet().stream() - .filter(entry -> entry.getValue().test(context)) + return Boolean.TRUE.equals(context.get(TagEvaluationContext.IGNORE_TAG_CONDITIONS)) + ? this.tags.keySet().stream() + : this.tags.entrySet().stream() + .filter(entry -> entry.getValue() == null || entry.getValue().test(context)) .map(Map.Entry::getKey); } diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntryList.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntryList.java index c98868976..d1e51405b 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntryList.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntryList.java @@ -4,8 +4,9 @@ import net.modificationstation.stationapi.api.tag.TagKey; import net.modificationstation.stationapi.api.tag.TagMatchGroup; import net.modificationstation.stationapi.api.util.Util; -import net.modificationstation.stationapi.api.util.context.Context; import net.modificationstation.stationapi.api.util.context.Condition; +import net.modificationstation.stationapi.api.util.context.Context; +import net.modificationstation.stationapi.api.util.context.TagEvaluationContext; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.VisibleForTesting; @@ -14,12 +15,12 @@ import java.util.stream.Stream; /** - * A registry entry list is an immutable list of registry entries. This, is either a direct + * A registry entry list is an immutable list of registry entries. This is either a direct * reference to each item, or a reference to a tag. A tag is a way * to dynamically define a list of registered values. Anything registered in a registry * can be tagged, and each registry holds a list of tags it recognizes. * - *

This can be iterated directly (i.e. {@code for (RegistryEntry entry : entries)}. + *

This can be iterated directly (i.e. {@code for (RegistryEntry entry : entries)}). * Note that this does not implement {@link java.util.Collection}. * * @see Registry @@ -27,12 +28,12 @@ */ public interface RegistryEntryList extends Iterable> { /** - * This method implicitly uses {@link Context#ANY}, meaning it will stream all registry entries + * This method implicitly uses {@link TagEvaluationContext#BYPASSED}, meaning it will stream all registry entries * in this list, bypassing all conditional checks. * {@return a stream of registry entries in this list} */ default Stream> stream() { - return stream(Context.ANY); + return stream(TagEvaluationContext.BYPASSED); } /** @@ -48,12 +49,12 @@ default Iterable> iterable(Context context) { } /** - * This method implicitly uses {@link Context#ANY}, meaning it will count all registry entries + * This method implicitly uses {@link TagEvaluationContext#BYPASSED}, meaning it will count all registry entries * in this list, bypassing all conditional checks. * {@return the number of entries in this list} */ default int size() { - return size(Context.ANY); + return size(TagEvaluationContext.BYPASSED); } /** @@ -69,12 +70,12 @@ default int size() { Either, List>> getStorage(); /** - * This method implicitly uses {@link Context#ANY}, meaning it may return any registry entry + * This method implicitly uses {@link TagEvaluationContext#BYPASSED}, meaning it may return any registry entry * in this list, bypassing all conditional checks. * {@return a random entry of the list, or an empty optional if this list is empty} */ default Optional> getRandom(Random var1) { - return getRandom(var1, Context.ANY); + return getRandom(var1, TagEvaluationContext.BYPASSED); } /** @@ -83,14 +84,14 @@ default Optional> getRandom(Random var1) { Optional> getRandom(Random var1, Context context); /** - * This method implicitly uses {@link Context#ANY}, meaning it operates on the flattened list of all registry entries + * This method implicitly uses {@link TagEvaluationContext#BYPASSED}, meaning it operates on the flattened list of all registry entries * in this list, bypassing all conditional checks. * {@return the registry entry at {@code index}} * * @throws IndexOutOfBoundsException if the index is out of bounds */ default RegistryEntry get(int var1) { - return get(var1, Context.ANY); + return get(var1, TagEvaluationContext.BYPASSED); } /** @@ -184,9 +185,9 @@ protected List> getEntries() { @Override public Stream> stream(Context context) { - if (context.matchesAll()) return this.getEntries().stream(); - - return this.matchGroups.stream() + return Boolean.TRUE.equals(context.get(TagEvaluationContext.IGNORE_TAG_CONDITIONS)) + ? this.getEntries().stream() + : this.matchGroups.stream() .filter(matchGroup -> { for (Condition condition : matchGroup.conditions()) if (!condition.test(context)) return false; @@ -198,8 +199,7 @@ public Stream> stream(Context context) { @Override public int size(Context context) { - if (context.matchesAll()) return this.allEntries.size(); - return (int) this.stream(context).count(); + return Boolean.TRUE.equals(context.get(TagEvaluationContext.IGNORE_TAG_CONDITIONS)) ? this.allEntries.size() : (int) this.stream(context).count(); } @Override @@ -274,12 +274,12 @@ public int size(Context context) { @Override public Spliterator> spliterator() { - return this.stream(Context.ANY).spliterator(); + return this.stream(TagEvaluationContext.BYPASSED).spliterator(); } @Override public Iterator> iterator() { - return this.stream(Context.ANY).iterator(); + return this.stream(TagEvaluationContext.BYPASSED).iterator(); } @Override diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java index cc9d67eaf..8ac84bc99 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java @@ -500,7 +500,12 @@ public void populateTags(@UnknownNullability Map, Collection oldPred.and(predicate.negate()) + ); + else + map.get(reference).merge(tag, predicate, Predicate::or); } } }); diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagEntry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagEntry.java index ccd062c76..f960ed290 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagEntry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagEntry.java @@ -96,14 +96,14 @@ public boolean resolve( if (!this.conditions.isEmpty()) mergedConditions.addAll(this.conditions); - matchGroupConsumer.accept(new TagMatchGroup<>(refMatchGroup.baseItems(), mergedConditions)); + matchGroupConsumer.accept(new TagMatchGroup<>(refMatchGroup.baseItems(), mergedConditions, false)); } } else { T value = getter.direct(this.id); if (value == null) return !this.required; - matchGroupConsumer.accept(new TagMatchGroup<>(List.of(value), this.conditions)); + matchGroupConsumer.accept(new TagMatchGroup<>(List.of(value), this.conditions, false)); } return true; } diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagFile.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagFile.java index 8f611e242..79e8ce637 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagFile.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagFile.java @@ -6,12 +6,14 @@ import java.util.List; -public record TagFile(List entries, boolean replace) { +public record TagFile(List entries, List remove, boolean replace) { public static Codec createCodec(Codec> tagConditionCodec) { return RecordCodecBuilder.create( instance -> instance.group( - TagEntry.createCodec(tagConditionCodec).listOf().fieldOf("values") + TagEntry.createCodec(tagConditionCodec).listOf().optionalFieldOf("values", List.of()) .forGetter(TagFile::entries), + TagEntry.createCodec(tagConditionCodec).listOf().optionalFieldOf("remove", List.of()) + .forGetter(TagFile::remove), Codec.BOOL.optionalFieldOf("replace", false) .forGetter(TagFile::replace) ).apply(instance, TagFile::new) diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagGroupLoader.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagGroupLoader.java index 27559efad..bd30091ae 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagGroupLoader.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagGroupLoader.java @@ -14,9 +14,9 @@ import com.mojang.serialization.JsonOps; import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; -import net.modificationstation.stationapi.api.util.Identifier; import net.modificationstation.stationapi.api.resource.Resource; import net.modificationstation.stationapi.api.resource.ResourceManager; +import net.modificationstation.stationapi.api.util.Identifier; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -61,7 +61,8 @@ public Map> loadTags(ResourceManager manager) { if (tagFile.replace()) list.clear(); String string2 = resource.getResourcePackName(); - tagFile.entries().forEach(tagEntry -> list.add(new TrackedEntry(tagEntry, string2))); + tagFile.entries().forEach(tagEntry -> list.add(new TrackedEntry(tagEntry, false, string2))); + tagFile.remove().forEach(tagEntry -> list.add(new TrackedEntry(tagEntry, true, string2))); } catch (Throwable var16) { if (reader != null) try { reader.close(); @@ -107,7 +108,7 @@ private Either, Collection>> resolveAl List missing = new ArrayList<>(); for (TrackedEntry tag : tags) - if (!tag.entry().resolve(valueGetter, matchGroupBuilder::add)) + if (!tag.entry().resolve(valueGetter, matchGroup -> matchGroupBuilder.add(new TagMatchGroup<>(matchGroup.baseItems(), matchGroup.conditions(), tag.remove())))) missing.add(tag); return missing.isEmpty() @@ -167,7 +168,7 @@ public Map>> load(ResourceManager manage return this.buildGroup(this.loadTags(manager)); } - public record TrackedEntry(TagEntry entry, String source) { + public record TrackedEntry(TagEntry entry, boolean remove, String source) { @Override public @NotNull String toString() { return this.entry.toString() + " (from " + this.source + ")"; diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagMatchGroup.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagMatchGroup.java index 786e4668e..b759d2027 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagMatchGroup.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagMatchGroup.java @@ -7,7 +7,8 @@ public record TagMatchGroup( Collection baseItems, - Collection> conditions + Collection> conditions, + boolean remove ) { public boolean test(T item, Context ctx) { if (!baseItems.contains(item)) return false; diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/util/context/TagEvaluationContext.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/util/context/TagEvaluationContext.java new file mode 100644 index 000000000..11c9f3603 --- /dev/null +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/util/context/TagEvaluationContext.java @@ -0,0 +1,21 @@ +package net.modificationstation.stationapi.api.util.context; + +import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; + +@FunctionalInterface +public interface TagEvaluationContext extends Context { + Context.Key IGNORE_TAG_CONDITIONS = new Context.Key<>(NAMESPACE.id("ignore_tag_conditions")); + + TagEvaluationContext DEFAULT = of(Context.EMPTY); + TagEvaluationContext BYPASSED = of(Context.EMPTY.with(IGNORE_TAG_CONDITIONS, true)); + + default boolean ignoreTagConditions() { + return Boolean.TRUE.equals(get(IGNORE_TAG_CONDITIONS)); + } + + static TagEvaluationContext of(Context context) { + if (context instanceof TagEvaluationContext t) return t; + interface TagEvaluationContextDelegate extends TagEvaluationContext, Delegate {} + return (TagEvaluationContextDelegate) () -> context; + } +} diff --git a/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/ModResourcePackUtil.java b/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/ModResourcePackUtil.java index ae3fbf603..58298b4bb 100644 --- a/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/ModResourcePackUtil.java +++ b/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/ModResourcePackUtil.java @@ -17,8 +17,7 @@ import org.jetbrains.annotations.Nullable; import java.io.InputStream; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -59,16 +58,62 @@ public static void appendModResourcePacks(List packs, ResourceT packs.add(pack); } } - packs.sort((pack1, pack2) -> { - String id1 = pack1.getFabricModMetadata().getId(); - String id2 = pack2.getFabricModMetadata().getId(); - ObjectSet s; - return - lowerThan.containsKey(id1) && ((s = lowerThan.get(id1)).contains("*") || s.contains(id2)) || - higherThan.containsKey(id2) && ((s = higherThan.get(id2)).contains("*") || s.contains(id1)) ? -1 : - higherThan.containsKey(id1) && ((s = higherThan.get(id1)).contains("*") || s.contains(id2)) || - lowerThan.containsKey(id2) && ((s = lowerThan.get(id2)).contains("*") || s.contains(id1)) ? 1 : 0; - }); + Map> edges = new HashMap<>(); + Map inDegree = new HashMap<>(); + for (ModResourcePack p : packs) { + edges.put(p, new HashSet<>()); + inDegree.put(p, 0); + } + + for (ModResourcePack p1 : packs) { + String id1 = p1.getFabricModMetadata().getId(); + for (ModResourcePack p2 : packs) { + if (p1 == p2) continue; + String id2 = p2.getFabricModMetadata().getId(); + + boolean p1BeforeP2 = false; + ObjectSet s; + if (lowerThan.containsKey(id1) && ((s = lowerThan.get(id1)).contains("*") || s.contains(id2))) { + p1BeforeP2 = true; + } + if (higherThan.containsKey(id2) && ((s = higherThan.get(id2)).contains("*") || s.contains(id1))) { + p1BeforeP2 = true; + } + + if (p1BeforeP2) { + if (edges.get(p1).add(p2)) { + inDegree.put(p2, inDegree.get(p2) + 1); + } + } + } + } + + List sorted = new ArrayList<>(); + Queue queue = new LinkedList<>(); + for (ModResourcePack p : packs) { + if (inDegree.get(p) == 0) queue.add(p); + } + + while (!queue.isEmpty()) { + ModResourcePack current = queue.poll(); + sorted.add(current); + for (ModResourcePack neighbor : edges.get(current)) { + int deg = inDegree.get(neighbor) - 1; + inDegree.put(neighbor, deg); + if (deg == 0) { + queue.add(neighbor); + } + } + } + + for (ModResourcePack p : packs) { + if (inDegree.get(p) > 0) { + sorted.add(p); + } + } + + packs.clear(); + packs.addAll(sorted); } private static void updatePriorities(Object2ReferenceMap> setToUpdate, String id, CustomValue.CvObject priority, String keyToUpdate) { diff --git a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/client/InGameHudMixin.java b/station-vanilla-fix-v0/src/main/java/net/modificationstation/stationapi/mixin/vanillafix/client/InGameHudMixin.java similarity index 98% rename from station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/client/InGameHudMixin.java rename to station-vanilla-fix-v0/src/main/java/net/modificationstation/stationapi/mixin/vanillafix/client/InGameHudMixin.java index 9d097acb2..aff2c4b03 100644 --- a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/client/InGameHudMixin.java +++ b/station-vanilla-fix-v0/src/main/java/net/modificationstation/stationapi/mixin/vanillafix/client/InGameHudMixin.java @@ -1,4 +1,4 @@ -package net.modificationstation.stationapi.mixin.block.client; +package net.modificationstation.stationapi.mixin.vanillafix.client; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.block.Block; diff --git a/station-vanilla-fix-v0/src/main/resources/fabric.mod.json b/station-vanilla-fix-v0/src/main/resources/fabric.mod.json index dffd34acd..186bdfcfe 100644 --- a/station-vanilla-fix-v0/src/main/resources/fabric.mod.json +++ b/station-vanilla-fix-v0/src/main/resources/fabric.mod.json @@ -46,6 +46,9 @@ "modmenu:api": true, "station-resource-loader-v0:assets_priority": { "lowerThan": "*" + }, + "station-resource-loader-v0:data_priority": { + "lowerThan": "*" } } } diff --git a/station-vanilla-fix-v0/src/main/resources/station-vanilla-fix-v0.mixins.json b/station-vanilla-fix-v0/src/main/resources/station-vanilla-fix-v0.mixins.json index e6f62fff3..f50be035f 100644 --- a/station-vanilla-fix-v0/src/main/resources/station-vanilla-fix-v0.mixins.json +++ b/station-vanilla-fix-v0/src/main/resources/station-vanilla-fix-v0.mixins.json @@ -13,6 +13,7 @@ "server": [ ], "client": [ + "client.InGameHudMixin", "client.MinecraftMixin", "client.ScreenAccessor", "client.SelectWorldScreenAccessor", From 6f85658782f8f465bc8aaf0a1b3e2219a85b84f7 Mon Sep 17 00:00:00 2001 From: mine_diver Date: Sat, 6 Jun 2026 23:24:07 +0500 Subject: [PATCH 5/8] Explicit tag contexts and empty context cleanup --- .../api/util/context/ActorContext.java | 4 +- .../stationapi/api/util/context/Context.java | 23 ++++++++--- .../mixin/block/FireBlockMixin.java | 4 +- .../mixin/block/LeavesBlockMixin.java | 6 +-- .../api/block/AbstractBlockState.java | 28 ++++++------- .../api/block/{ => context}/BlockContext.java | 26 ++++++------ .../api/block/context/BlockTagContext.java | 40 ++++++++++++++++++ .../api/item/StationFlatteningItemStack.java | 9 ++-- .../api/item/{ => context}/ItemContext.java | 20 ++++----- .../api/item/context/ItemTagContext.java | 29 +++++++++++++ .../api/registry/BlockRegistry.java | 2 +- .../stationapi/api/registry/ItemRegistry.java | 2 +- .../stationapi/mixin/item/ItemStackMixin.java | 21 ++++++---- .../api/registry/RegistryEntry.java | 24 +++++------ .../api/registry/RegistryEntryList.java | 41 +++++++++---------- .../context/TagEvaluationContext.java | 14 +++++-- .../vanillafix/client/InGameHudMixin.java | 4 +- .../condition/TagSurfaceCondition.java | 4 +- 18 files changed, 195 insertions(+), 106 deletions(-) rename station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/{ => context}/BlockContext.java (77%) create mode 100644 station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/context/BlockTagContext.java rename station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/{ => context}/ItemContext.java (79%) create mode 100644 station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/context/ItemTagContext.java rename station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/{util => tag}/context/TagEvaluationContext.java (56%) diff --git a/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ActorContext.java b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ActorContext.java index d9e3a3e12..68a37f43f 100644 --- a/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ActorContext.java +++ b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ActorContext.java @@ -11,7 +11,7 @@ public interface ActorContext extends Context { /** * The context key used to evaluate actor-related conditions. */ - Context.Key ACTOR = new Context.Key<>(NAMESPACE.id("actor")); + Context.Key ACTOR_KEY = new Context.Key<>(NAMESPACE.id("actor")); static ActorContext of(Context context) { return context instanceof ActorContext a ? a : context::getRaw; @@ -21,6 +21,6 @@ static ActorContext of(Context context) { * {@return the actor performing the action, or {@code null} if none} */ default @Nullable Object actor() { - return get(ACTOR); + return get(ACTOR_KEY); } } diff --git a/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/Context.java b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/Context.java index 0a5fc67f7..88df917c9 100644 --- a/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/Context.java +++ b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/Context.java @@ -5,6 +5,8 @@ import java.util.Optional; +import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; + /** * A data source that contains keys and their associated values. *

@@ -36,13 +38,22 @@ default int getIntRaw(Identifier id, int defaultValue) { } } + @SuppressWarnings("unused") + record Key(Identifier id) {} + /** - * A context that contains no keys and always returns {@code null}. + * A key used to mark a context as explicitly empty/inert. + * Composing with an empty context will yield the other context unchanged. */ - Context EMPTY = id -> null; + Key EMPTY_KEY = new Key<>(NAMESPACE.id("empty")); - @SuppressWarnings("unused") - record Key(Identifier id) {} + /** + * An empty context that acts as an identity element for composition. + *

+ * Composing with an empty context yields the other context unchanged. + * Any {@link Delegate} wrapping an empty context is also considered empty. + */ + Context EMPTY = id -> EMPTY_KEY.id() == id ? Boolean.TRUE : null; /** * {@return a new context containing a single key-value pair} @@ -138,8 +149,8 @@ default Context with(Context... others) { } private static Context append(Context base, Context addition) { - if (addition == EMPTY) return base; - if (base == EMPTY) return addition; + if (addition == EMPTY || Boolean.TRUE.equals(addition.get(EMPTY_KEY))) return base; + if (base == EMPTY || Boolean.TRUE.equals(base.get(EMPTY_KEY))) return addition; record Composite(Context head, Context next) implements Context { private Context find(Identifier id) { diff --git a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/FireBlockMixin.java b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/FireBlockMixin.java index ea40890c3..dc902291c 100644 --- a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/FireBlockMixin.java +++ b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/FireBlockMixin.java @@ -3,9 +3,9 @@ import net.minecraft.block.FireBlock; import net.minecraft.world.World; import net.modificationstation.stationapi.api.StationAPI; +import net.modificationstation.stationapi.api.block.context.BlockTagContext; import net.modificationstation.stationapi.api.event.block.FireBurnableRegisterEvent; import net.modificationstation.stationapi.api.registry.tag.BlockTags; -import net.modificationstation.stationapi.api.tag.conditional.BlockContext; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -43,7 +43,7 @@ private void stationapi_postBurnableRegister(CallbackInfo ci) { ) private int stationapi_allowInfiniburnBlocks(int constant, World world, int x, int y, int z, Random random) { return world.getBlockState(x, y - 1, z).isIn( - BlockTags.INFINIBURN, BlockContext.of(world, x, y - 1, z) + BlockTags.INFINIBURN, BlockTagContext.of(world, x, y - 1, z) ) ? 1 : 0; } } diff --git a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/LeavesBlockMixin.java b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/LeavesBlockMixin.java index 4198f8bf0..943b1a9a4 100644 --- a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/LeavesBlockMixin.java +++ b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/mixin/block/LeavesBlockMixin.java @@ -6,8 +6,8 @@ import net.minecraft.block.LeavesBlock; import net.minecraft.world.World; import net.modificationstation.stationapi.api.block.BlockState; +import net.modificationstation.stationapi.api.block.context.BlockTagContext; import net.modificationstation.stationapi.api.registry.tag.BlockTags; -import net.modificationstation.stationapi.api.tag.conditional.BlockContext; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -17,10 +17,10 @@ public class LeavesBlockMixin { @WrapOperation(method = "onTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;getBlockId(III)I")) private int makeModdedLogsAndLeavesWork(World world, int x, int y, int z, Operation original) { BlockState state = world.getBlockState(x, y, z); - if (state.isIn(BlockTags.LOGS, BlockContext.of(world, x, y, z))) { + if (state.isIn(BlockTags.LOGS, BlockTagContext.of(world, x, y, z))) { return Block.LOG.id; } - if (state.isIn(BlockTags.LEAVES, BlockContext.of(world, x, y, z))) { + if (state.isIn(BlockTags.LEAVES, BlockTagContext.of(world, x, y, z))) { return Block.LEAVES.id; } diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java index f6b3d2427..ca1bb96a5 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/AbstractBlockState.java @@ -11,12 +11,12 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.world.BlockView; import net.minecraft.world.World; +import net.modificationstation.stationapi.api.block.context.BlockTagContext; import net.modificationstation.stationapi.api.item.ItemPlacementContext; import net.modificationstation.stationapi.api.registry.RegistryEntryList; import net.modificationstation.stationapi.api.state.State; import net.modificationstation.stationapi.api.state.property.Property; import net.modificationstation.stationapi.api.tag.TagKey; -import net.modificationstation.stationapi.api.util.context.Context; import net.modificationstation.stationapi.api.util.math.MathHelper; import net.modificationstation.stationapi.impl.block.StationFlatteningBlockInternal; @@ -88,8 +88,8 @@ public boolean canReplace(ItemPlacementContext context) { } /** - * @deprecated Use {@link #isIn(TagKey, Context, Predicate)} instead. - * Relying on tag checks without a {@link Context} can lead to broken behavior if the tag contains conditions + * @deprecated Use {@link #isIn(TagKey, BlockTagContext, Predicate)} instead. + * Relying on tag checks without a {@link BlockTagContext} can lead to broken behavior if the tag contains conditions * that require contextual information (such as the block's world position, metadata, or the actor interacting with it). */ @Deprecated @@ -98,42 +98,42 @@ public boolean isIn(TagKey tag, Predicate predicate) } /** - * @deprecated Use {@link #isIn(TagKey, Context)} instead. - * Relying on tag checks without a {@link Context} can lead to broken behavior if the tag contains conditions + * @deprecated Use {@link #isIn(TagKey, BlockTagContext)} instead. + * Relying on tag checks without a {@link BlockTagContext} can lead to broken behavior if the tag contains conditions * that require contextual information (such as the block's world position, metadata, or the actor interacting with it). */ @Deprecated public boolean isIn(TagKey tag) { - return isIn(tag, Context.EMPTY); + return isIn(tag, BlockTagContext.DEFAULT); } - public boolean isIn(TagKey tag, Context context, Predicate predicate) { + public boolean isIn(TagKey tag, BlockTagContext context, Predicate predicate) { return isIn(tag, context) && predicate.test(this); } - public boolean isIn(TagKey tag, Context context) { + public boolean isIn(TagKey tag, BlockTagContext context) { return getBlock().getRegistryEntry().isIn(tag, context); } /** - * @deprecated Use {@link #isIn(RegistryEntryList, Context)} instead. - * Relying on list checks without a {@link Context} can lead to broken behavior if the underlying entries + * @deprecated Use {@link #isIn(RegistryEntryList, BlockTagContext)} instead. + * Relying on list checks without a {@link BlockTagContext} can lead to broken behavior if the underlying entries * have contextual conditions. */ @Deprecated public boolean isIn(RegistryEntryList blocks) { - return isIn(blocks, Context.EMPTY); + return isIn(blocks, BlockTagContext.DEFAULT); } - public boolean isIn(RegistryEntryList blocks, Context context) { + public boolean isIn(RegistryEntryList blocks, BlockTagContext context) { return blocks.contains(getBlock().getRegistryEntry(), context); } public Stream> streamTags() { - return streamTags(TagEvaluationContext.BYPASSED); + return streamTags(BlockTagContext.BYPASSED); } - public Stream> streamTags(Context context) { + public Stream> streamTags(BlockTagContext context) { return getBlock().getRegistryEntry().streamTags(context); } diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/BlockContext.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/context/BlockContext.java similarity index 77% rename from station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/BlockContext.java rename to station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/context/BlockContext.java index 2867dcd29..04796dd1d 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/BlockContext.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/context/BlockContext.java @@ -1,4 +1,4 @@ -package net.modificationstation.stationapi.api.block; +package net.modificationstation.stationapi.api.block.context; import net.minecraft.util.math.BlockPos; import net.minecraft.world.BlockView; @@ -14,45 +14,43 @@ */ @FunctionalInterface public interface BlockContext extends Context { - BlockContext EMPTY = id -> null; - /** * The context key used to evaluate world-related conditions in a block context. */ - Context.Key BLOCK_VIEW = new Context.Key<>(NAMESPACE.id("block_view")); + Context.Key BLOCK_VIEW_KEY = new Context.Key<>(NAMESPACE.id("block_view")); /** * The context key used to evaluate position-related conditions in a block context. */ - Context.Key BLOCK_POS = new Context.Key<>(NAMESPACE.id("block_pos")); + Context.Key BLOCK_POS_KEY = new Context.Key<>(NAMESPACE.id("block_pos")); /** * The context key used to evaluate block metadata conditions. */ - Context.Key BLOCK_METADATA = new Context.Key<>(NAMESPACE.id("block_metadata")); + Context.Key BLOCK_METADATA_KEY = new Context.Key<>(NAMESPACE.id("block_metadata")); /** * {@return whether this context has block metadata} */ default boolean hasBlockMeta() { - return contains(BLOCK_METADATA) || (blockView() != null && blockPos() != null); + return contains(BLOCK_METADATA_KEY) || (blockView() != null && blockPos() != null); } /** * {@return the block view the block interaction is occurring in} */ - default @Nullable BlockView blockView() { return get(BLOCK_VIEW); } + default @Nullable BlockView blockView() { return get(BLOCK_VIEW_KEY); } /** * {@return the position of the block interaction} */ - default @Nullable BlockPos blockPos() { return get(BLOCK_POS); } + default @Nullable BlockPos blockPos() { return get(BLOCK_POS_KEY); } /** * {@return the block metadata} */ default int blockMeta() { - return getIntRaw(BLOCK_METADATA.id(), 0); + return getIntRaw(BLOCK_METADATA_KEY.id(), 0); } interface DataProvider extends BlockContext { @@ -71,15 +69,15 @@ default int blockMeta() { @Override default Object getRaw(Identifier id) { - if (BLOCK_VIEW.id() == id) return blockView(); - if (BLOCK_POS.id() == id) return blockPos(); - if (BLOCK_METADATA.id() == id) return blockView() != null && blockPos() != null ? blockMeta() : null; + if (BLOCK_VIEW_KEY.id() == id) return blockView(); + if (BLOCK_POS_KEY.id() == id) return blockPos(); + if (BLOCK_METADATA_KEY.id() == id) return blockView() != null && blockPos() != null ? blockMeta() : null; return null; } @Override default int getIntRaw(Identifier id, int defaultValue) { - if (BLOCK_METADATA.id() == id) return blockMeta(); + if (BLOCK_METADATA_KEY.id() == id) return blockMeta(); return BlockContext.super.getIntRaw(id, defaultValue); } } diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/context/BlockTagContext.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/context/BlockTagContext.java new file mode 100644 index 000000000..6cffa8590 --- /dev/null +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/block/context/BlockTagContext.java @@ -0,0 +1,40 @@ +package net.modificationstation.stationapi.api.block.context; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.BlockView; +import net.modificationstation.stationapi.api.tag.context.TagEvaluationContext; +import net.modificationstation.stationapi.api.util.context.Context; + +public interface BlockTagContext extends BlockContext, TagEvaluationContext { + BlockTagContext DEFAULT = of(TagEvaluationContext.DEFAULT); + BlockTagContext BYPASSED = of(TagEvaluationContext.BYPASSED); + + static BlockTagContext of(BlockView view, BlockPos pos) { + Context data = BlockContext.of(view, pos); + interface BlockTagContextDelegate extends BlockTagContext, Delegate {} + return (BlockTagContextDelegate) () -> data; + } + + static BlockTagContext of(BlockView view, BlockPos pos, boolean ignoreTagConditions) { + Context data = BlockContext.of(view, pos).with(TagEvaluationContext.of(ignoreTagConditions)); + interface BlockTagContextDelegate extends BlockTagContext, Delegate {} + return (BlockTagContextDelegate) () -> data; + } + + static BlockTagContext of(BlockView view, int x, int y, int z) { + return of(view, new BlockPos(x, y, z)); + } + + static BlockTagContext of(BlockView view, int x, int y, int z, boolean ignoreTagConditions) { + Context data = BlockContext.of(view, x, y, z).with(TagEvaluationContext.of(ignoreTagConditions)); + interface BlockTagContextDelegate extends BlockTagContext, Delegate {} + return (BlockTagContextDelegate) () -> data; + } + + static BlockTagContext of(Context context) { + if (context instanceof BlockTagContext b) return b; + interface BlockTagContextDelegate extends BlockTagContext, Delegate {} + return (BlockTagContextDelegate) () -> context; + } + +} diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/StationFlatteningItemStack.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/StationFlatteningItemStack.java index 12eb20adb..5207ce3da 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/StationFlatteningItemStack.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/StationFlatteningItemStack.java @@ -2,13 +2,14 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; import net.minecraft.world.BlockView; import net.modificationstation.stationapi.api.block.BlockState; import net.modificationstation.stationapi.api.registry.RegistryEntry; import net.modificationstation.stationapi.api.tag.TagKey; +import net.modificationstation.stationapi.api.tag.context.TagEvaluationContext; import net.modificationstation.stationapi.api.util.Util; -import net.modificationstation.stationapi.api.util.context.Context; import java.util.stream.Stream; @@ -24,10 +25,10 @@ default RegistryEntry.Reference getRegistryEntry() { * it gets prepended internally */ default boolean isIn(TagKey tag) { - return isIn(tag, Context.EMPTY); + return isIn(tag, TagEvaluationContext.DEFAULT); } - default boolean isIn(TagKey tag, Context context) { + default boolean isIn(TagKey tag, TagEvaluationContext context) { return Util.assertImpl(); } @@ -35,7 +36,7 @@ default Stream> streamTags() { return streamTags(TagEvaluationContext.BYPASSED); } - default Stream> streamTags(Context context) { + default Stream> streamTags(TagEvaluationContext context) { return Util.assertImpl(); } diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/ItemContext.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/context/ItemContext.java similarity index 79% rename from station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/ItemContext.java rename to station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/context/ItemContext.java index 8fdac326f..72682e040 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/ItemContext.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/context/ItemContext.java @@ -1,4 +1,4 @@ -package net.modificationstation.stationapi.api.item; +package net.modificationstation.stationapi.api.item.context; import net.minecraft.item.ItemStack; import net.modificationstation.stationapi.api.util.Identifier; @@ -12,23 +12,21 @@ */ @FunctionalInterface public interface ItemContext extends Context { - ItemContext EMPTY = id -> null; - /** * The context key used to evaluate item-related conditions in an item context. */ - Key ITEM_STACK = new Key<>(NAMESPACE.id("item_stack")); + Key ITEM_STACK_KEY = new Key<>(NAMESPACE.id("item_stack")); /** * The context key used to evaluate item damage conditions. */ - Key ITEM_DAMAGE = new Key<>(NAMESPACE.id("item_damage")); + Key ITEM_DAMAGE_KEY = new Key<>(NAMESPACE.id("item_damage")); /** * {@return whether this context has item damage data} */ default boolean hasDamage() { - return contains(ITEM_DAMAGE) || itemStack() != null; + return contains(ITEM_DAMAGE_KEY) || itemStack() != null; } @FunctionalInterface @@ -44,14 +42,14 @@ default int damage() { @Override default Object getRaw(Identifier id) { - if (ITEM_STACK.id() == id) return itemStack(); - if (ITEM_DAMAGE.id() == id) return itemStack() == null ? null : damage(); + if (ITEM_STACK_KEY.id() == id) return itemStack(); + if (ITEM_DAMAGE_KEY.id() == id) return itemStack() == null ? null : damage(); return null; } @Override default int getIntRaw(Identifier id, int defaultValue) { - if (ITEM_DAMAGE.id() == id) return damage(); + if (ITEM_DAMAGE_KEY.id() == id) return damage(); return ItemContext.super.getIntRaw(id, defaultValue); } } @@ -59,13 +57,13 @@ default int getIntRaw(Identifier id, int defaultValue) { /** * {@return the item stack being evaluated} */ - default @Nullable ItemStack itemStack() { return get(ITEM_STACK); } + default @Nullable ItemStack itemStack() { return get(ITEM_STACK_KEY); } /** * {@return the item damage} */ default int damage() { - Integer dmg = get(ITEM_DAMAGE); + Integer dmg = get(ITEM_DAMAGE_KEY); if (dmg != null) return dmg; ItemStack stack = itemStack(); return stack == null ? 0 : stack.getDamage(); diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/context/ItemTagContext.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/context/ItemTagContext.java new file mode 100644 index 000000000..876ecc32a --- /dev/null +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/item/context/ItemTagContext.java @@ -0,0 +1,29 @@ +package net.modificationstation.stationapi.api.item.context; + +import net.minecraft.item.ItemStack; +import net.modificationstation.stationapi.api.tag.context.TagEvaluationContext; +import net.modificationstation.stationapi.api.util.context.Context; + +public interface ItemTagContext extends ItemContext, TagEvaluationContext { + ItemTagContext DEFAULT = of(TagEvaluationContext.DEFAULT); + ItemTagContext BYPASSED = of(TagEvaluationContext.BYPASSED); + + static ItemTagContext of(ItemStack stack) { + Context data = ItemContext.of(stack); + interface ItemTagContextDelegate extends ItemTagContext, Delegate {} + return (ItemTagContextDelegate) () -> data; + } + + static ItemTagContext of(ItemStack stack, boolean ignoreTagConditions) { + Context data = ItemContext.of(stack).with(TagEvaluationContext.of(ignoreTagConditions)); + interface ItemTagContextDelegate extends ItemTagContext, Delegate {} + return (ItemTagContextDelegate) () -> data; + } + + static ItemTagContext of(Context context) { + if (context instanceof ItemTagContext i) return i; + interface ItemTagContextDelegate extends ItemTagContext, Delegate {} + return (ItemTagContextDelegate) () -> context; + } + +} diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/BlockRegistry.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/BlockRegistry.java index c05e9f308..6dd9b2276 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/BlockRegistry.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/BlockRegistry.java @@ -3,9 +3,9 @@ import com.mojang.serialization.Lifecycle; import com.mojang.serialization.MapCodec; import net.minecraft.block.Block; +import net.modificationstation.stationapi.api.block.context.BlockContext; import net.modificationstation.stationapi.api.event.registry.RegistryAttribute; import net.modificationstation.stationapi.api.event.registry.RegistryAttributeHolder; -import net.modificationstation.stationapi.api.block.BlockContext; import net.modificationstation.stationapi.api.util.Identifier; import net.modificationstation.stationapi.api.util.context.Context; diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/ItemRegistry.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/ItemRegistry.java index 6d4df9521..299ba26d2 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/ItemRegistry.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/ItemRegistry.java @@ -6,7 +6,7 @@ import net.minecraft.item.Item; import net.modificationstation.stationapi.api.event.registry.RegistryAttribute; import net.modificationstation.stationapi.api.event.registry.RegistryAttributeHolder; -import net.modificationstation.stationapi.api.item.ItemContext; +import net.modificationstation.stationapi.api.item.context.ItemContext; import net.modificationstation.stationapi.api.util.Identifier; import net.modificationstation.stationapi.api.util.context.Context; diff --git a/station-items-v0/src/main/java/net/modificationstation/stationapi/mixin/item/ItemStackMixin.java b/station-items-v0/src/main/java/net/modificationstation/stationapi/mixin/item/ItemStackMixin.java index 42b071548..835771db6 100644 --- a/station-items-v0/src/main/java/net/modificationstation/stationapi/mixin/item/ItemStackMixin.java +++ b/station-items-v0/src/main/java/net/modificationstation/stationapi/mixin/item/ItemStackMixin.java @@ -11,9 +11,9 @@ import net.modificationstation.stationapi.api.event.item.ItemStackEvent; import net.modificationstation.stationapi.api.item.StationFlatteningItemStack; import net.modificationstation.stationapi.api.item.StationItemStack; +import net.modificationstation.stationapi.api.item.context.ItemContext; import net.modificationstation.stationapi.api.tag.TagKey; -import net.modificationstation.stationapi.api.tag.conditional.ItemContext; -import net.modificationstation.stationapi.api.util.context.Context; +import net.modificationstation.stationapi.api.tag.context.TagEvaluationContext; import net.modificationstation.stationapi.impl.item.StationNBTSetter; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -55,7 +55,14 @@ private void stationapi_onCreation(World arg, PlayerEntity arg1, CallbackInfo ci private NbtCompound stationapi_stationNbt = new NbtCompound(); @Unique - private final ItemContext stationapi_itemContext = ItemContext.of((ItemStack) (Object) this); + private ItemContext stationapi_itemContext; + + @Unique + private ItemContext stationapi_getItemContext() { + if (stationapi_itemContext == null) + stationapi_itemContext = ItemContext.of((ItemStack) (Object) this); + return stationapi_itemContext; + } @Inject( method = "split", @@ -179,13 +186,13 @@ public void setStationNbt(NbtCompound stationNbt) { @Override @Unique - public boolean isIn(TagKey tag, Context context) { - return getRegistryEntry().isIn(tag, stationapi_itemContext.with(context)); + public boolean isIn(TagKey tag, TagEvaluationContext context) { + return getRegistryEntry().isIn(tag, TagEvaluationContext.of(stationapi_getItemContext().with(context))); } @Override @Unique - public Stream> streamTags(Context context) { - return getRegistryEntry().streamTags(stationapi_itemContext.with(context)); + public Stream> streamTags(TagEvaluationContext context) { + return getRegistryEntry().streamTags(TagEvaluationContext.of(stationapi_getItemContext().with(context))); } } diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntry.java index 32b8feec0..c49d2315a 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntry.java @@ -2,9 +2,9 @@ import com.mojang.datafixers.util.Either; import net.modificationstation.stationapi.api.tag.TagKey; +import net.modificationstation.stationapi.api.tag.context.TagEvaluationContext; import net.modificationstation.stationapi.api.util.Identifier; import net.modificationstation.stationapi.api.util.context.Context; -import net.modificationstation.stationapi.api.util.context.TagEvaluationContext; import org.jetbrains.annotations.Nullable; import java.util.Map; @@ -25,22 +25,22 @@ public interface RegistryEntry { boolean matches(Predicate> predicate); /** - * @deprecated Use {@link #isIn(TagKey, Context)} instead. - *

This method implicitly uses {@link Context#EMPTY}, meaning it will only evaluate to {@code true} + * @deprecated Use {@link #isIn(TagKey, TagEvaluationContext)} instead. + *

This method implicitly uses {@link TagEvaluationContext#DEFAULT}, meaning it will only evaluate to {@code true} * for unconditional tag references. */ @Deprecated default boolean isIn(TagKey tag) { - return isIn(tag, Context.EMPTY); + return isIn(tag, TagEvaluationContext.DEFAULT); } - boolean isIn(TagKey tag, Context context); + boolean isIn(TagKey tag, TagEvaluationContext context); default Stream> streamTags() { return streamTags(TagEvaluationContext.BYPASSED); } - Stream> streamTags(Context context); + Stream> streamTags(TagEvaluationContext context); Set> getTags(); @@ -73,7 +73,7 @@ public boolean matchesKey(RegistryKey key) { } @Override - public boolean isIn(TagKey tag, Context context) { + public boolean isIn(TagKey tag, TagEvaluationContext context) { return false; } @@ -108,7 +108,7 @@ public boolean ownerEquals(RegistryEntryOwner owner) { } @Override - public Stream> streamTags(Context context) { + public Stream> streamTags(TagEvaluationContext context) { return Stream.of(); } @@ -139,9 +139,9 @@ public boolean matchesKey(RegistryKey key) { } @Override - public boolean isIn(TagKey tag, Context context) { + public boolean isIn(TagKey tag, TagEvaluationContext context) { if (!tags.containsKey(tag)) return false; - if (Boolean.TRUE.equals(context.get(TagEvaluationContext.IGNORE_TAG_CONDITIONS))) return true; + if (context.ignoreTagConditions()) return true; Predicate predicate = tags.get(tag); return predicate == null || predicate.test(context); } @@ -180,8 +180,8 @@ void setTags(Map, Predicate> tags) { } @Override - public Stream> streamTags(Context context) { - return Boolean.TRUE.equals(context.get(TagEvaluationContext.IGNORE_TAG_CONDITIONS)) + public Stream> streamTags(TagEvaluationContext context) { + return context.ignoreTagConditions() ? this.tags.keySet().stream() : this.tags.entrySet().stream() .filter(entry -> entry.getValue() == null || entry.getValue().test(context)) diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntryList.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntryList.java index d1e51405b..7ac28b25a 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntryList.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntryList.java @@ -3,10 +3,9 @@ import com.mojang.datafixers.util.Either; import net.modificationstation.stationapi.api.tag.TagKey; import net.modificationstation.stationapi.api.tag.TagMatchGroup; +import net.modificationstation.stationapi.api.tag.context.TagEvaluationContext; import net.modificationstation.stationapi.api.util.Util; import net.modificationstation.stationapi.api.util.context.Condition; -import net.modificationstation.stationapi.api.util.context.Context; -import net.modificationstation.stationapi.api.util.context.TagEvaluationContext; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.VisibleForTesting; @@ -39,12 +38,12 @@ default Stream> stream() { /** * {@return a stream of registry entries in this list, including match groups that pass the given context} */ - Stream> stream(Context context); + Stream> stream(TagEvaluationContext context); /** * {@return an iterable of registry entries in this list, including match groups that pass the given context} */ - default Iterable> iterable(Context context) { + default Iterable> iterable(TagEvaluationContext context) { return () -> stream(context).iterator(); } @@ -60,7 +59,7 @@ default int size() { /** * {@return the number of entries in this list, including match groups that pass the given context} */ - int size(Context context); + int size(TagEvaluationContext context); /** * {@return the object that identifies this registry entry list} @@ -81,7 +80,7 @@ default Optional> getRandom(Random var1) { /** * {@return a random entry of the list matching the context, or an empty optional if this list is empty} */ - Optional> getRandom(Random var1, Context context); + Optional> getRandom(Random var1, TagEvaluationContext context); /** * This method implicitly uses {@link TagEvaluationContext#BYPASSED}, meaning it operates on the flattened list of all registry entries @@ -99,24 +98,24 @@ default RegistryEntry get(int var1) { * * @throws IndexOutOfBoundsException if the index is out of bounds */ - RegistryEntry get(int var1, Context context); + RegistryEntry get(int var1, TagEvaluationContext context); /** * {@return whether {@code entry} is in this list} * - * @deprecated Use {@link #contains(RegistryEntry, Context)} instead. - *

This method implicitly uses {@link Context#EMPTY}, meaning it will only evaluate to {@code true} + * @deprecated Use {@link #contains(RegistryEntry, TagEvaluationContext)} instead. + *

This method implicitly uses {@link TagEvaluationContext#DEFAULT}, meaning it will only evaluate to {@code true} * for unconditional tag references. */ @Deprecated default boolean contains(RegistryEntry entry) { - return contains(entry, Context.EMPTY); + return contains(entry, TagEvaluationContext.DEFAULT); } /** * {@return whether {@code entry} is in this list, evaluating match groups with the given context} */ - boolean contains(RegistryEntry var1, Context context); + boolean contains(RegistryEntry var1, TagEvaluationContext context); boolean ownerEquals(RegistryEntryOwner var1); @@ -184,8 +183,8 @@ protected List> getEntries() { } @Override - public Stream> stream(Context context) { - return Boolean.TRUE.equals(context.get(TagEvaluationContext.IGNORE_TAG_CONDITIONS)) + public Stream> stream(TagEvaluationContext context) { + return context.ignoreTagConditions() ? this.getEntries().stream() : this.matchGroups.stream() .filter(matchGroup -> { @@ -198,8 +197,8 @@ public Stream> stream(Context context) { } @Override - public int size(Context context) { - return Boolean.TRUE.equals(context.get(TagEvaluationContext.IGNORE_TAG_CONDITIONS)) ? this.allEntries.size() : (int) this.stream(context).count(); + public int size(TagEvaluationContext context) { + return context.ignoreTagConditions() ? this.allEntries.size() : (int) this.stream(context).count(); } @Override @@ -213,7 +212,7 @@ public Optional> getTagKey() { } @Override - public boolean contains(RegistryEntry entry, Context context) { + public boolean contains(RegistryEntry entry, TagEvaluationContext context) { return entry.isIn(this.tag, context); } @@ -254,7 +253,7 @@ public Optional> getTagKey() { } @Override - public boolean contains(RegistryEntry entry, Context context) { + public boolean contains(RegistryEntry entry, TagEvaluationContext context) { if (this.entrySet == null) this.entrySet = Set.copyOf(this.entries); return this.entrySet.contains(entry); } @@ -268,7 +267,7 @@ abstract class ListBacked implements RegistryEntryList { protected abstract List> getEntries(); @Override - public int size(Context context) { + public int size(TagEvaluationContext context) { return this.getEntries().size(); } @@ -283,17 +282,17 @@ public Iterator> iterator() { } @Override - public Stream> stream(Context context) { + public Stream> stream(TagEvaluationContext context) { return this.getEntries().stream(); } @Override - public Optional> getRandom(Random random, Context context) { + public Optional> getRandom(Random random, TagEvaluationContext context) { return Util.getRandomOrEmpty(this.stream(context).toList(), random); } @Override - public RegistryEntry get(int index, Context context) { + public RegistryEntry get(int index, TagEvaluationContext context) { return this.stream(context).skip(index).findFirst() .orElseThrow(() -> new IndexOutOfBoundsException("Index out of bounds: " + index)); } diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/util/context/TagEvaluationContext.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/context/TagEvaluationContext.java similarity index 56% rename from station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/util/context/TagEvaluationContext.java rename to station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/context/TagEvaluationContext.java index 11c9f3603..d9a1b01f1 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/util/context/TagEvaluationContext.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/context/TagEvaluationContext.java @@ -1,18 +1,24 @@ -package net.modificationstation.stationapi.api.util.context; +package net.modificationstation.stationapi.api.tag.context; + +import net.modificationstation.stationapi.api.util.context.Context; import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; @FunctionalInterface public interface TagEvaluationContext extends Context { - Context.Key IGNORE_TAG_CONDITIONS = new Context.Key<>(NAMESPACE.id("ignore_tag_conditions")); + Context.Key IGNORE_TAG_CONDITIONS_KEY = new Context.Key<>(NAMESPACE.id("ignore_tag_conditions")); TagEvaluationContext DEFAULT = of(Context.EMPTY); - TagEvaluationContext BYPASSED = of(Context.EMPTY.with(IGNORE_TAG_CONDITIONS, true)); + TagEvaluationContext BYPASSED = of(Context.EMPTY.with(IGNORE_TAG_CONDITIONS_KEY, true)); default boolean ignoreTagConditions() { - return Boolean.TRUE.equals(get(IGNORE_TAG_CONDITIONS)); + return Boolean.TRUE.equals(get(IGNORE_TAG_CONDITIONS_KEY)); } + static TagEvaluationContext of(boolean ignoreTagConditions) { + return ignoreTagConditions ? BYPASSED : DEFAULT; + } + static TagEvaluationContext of(Context context) { if (context instanceof TagEvaluationContext t) return t; interface TagEvaluationContextDelegate extends TagEvaluationContext, Delegate {} diff --git a/station-vanilla-fix-v0/src/main/java/net/modificationstation/stationapi/mixin/vanillafix/client/InGameHudMixin.java b/station-vanilla-fix-v0/src/main/java/net/modificationstation/stationapi/mixin/vanillafix/client/InGameHudMixin.java index aff2c4b03..3ec86b2c0 100644 --- a/station-vanilla-fix-v0/src/main/java/net/modificationstation/stationapi/mixin/vanillafix/client/InGameHudMixin.java +++ b/station-vanilla-fix-v0/src/main/java/net/modificationstation/stationapi/mixin/vanillafix/client/InGameHudMixin.java @@ -11,9 +11,9 @@ import net.minecraft.util.hit.HitResult; import net.minecraft.util.hit.HitResultType; import net.modificationstation.stationapi.api.block.BlockState; +import net.modificationstation.stationapi.api.block.context.BlockTagContext; import net.modificationstation.stationapi.api.state.property.Property; import net.modificationstation.stationapi.api.tag.TagKey; -import net.modificationstation.stationapi.api.tag.conditional.BlockContext; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -61,7 +61,7 @@ private void stationapi_renderHud(float bl, boolean i, int j, int par4, Callback } Collection> tags = state.streamTags( - BlockContext.of(minecraft.world, hit.blockX, hit.blockY, hit.blockZ) + BlockTagContext.of(minecraft.world, hit.blockX, hit.blockY, hit.blockZ) ).toList(); if (!tags.isEmpty()) { text = "Tags:"; diff --git a/station-worldgen-api-v0/src/main/java/net/modificationstation/stationapi/api/worldgen/surface/condition/TagSurfaceCondition.java b/station-worldgen-api-v0/src/main/java/net/modificationstation/stationapi/api/worldgen/surface/condition/TagSurfaceCondition.java index 3c6035348..ec601d2c6 100644 --- a/station-worldgen-api-v0/src/main/java/net/modificationstation/stationapi/api/worldgen/surface/condition/TagSurfaceCondition.java +++ b/station-worldgen-api-v0/src/main/java/net/modificationstation/stationapi/api/worldgen/surface/condition/TagSurfaceCondition.java @@ -3,8 +3,8 @@ import net.minecraft.block.Block; import net.minecraft.world.World; import net.modificationstation.stationapi.api.block.BlockState; +import net.modificationstation.stationapi.api.block.context.BlockTagContext; import net.modificationstation.stationapi.api.tag.TagKey; -import net.modificationstation.stationapi.api.tag.conditional.BlockContext; public class TagSurfaceCondition implements SurfaceCondition { private final TagKey tag; @@ -15,6 +15,6 @@ public TagSurfaceCondition(TagKey tag) { @Override public boolean canApply(World world, int x, int y, int z, BlockState state) { - return state.isIn(tag, BlockContext.of(world, x, y, z)); + return state.isIn(tag, BlockTagContext.of(world, x, y, z)); } } From 2c49dff4e2c85c50f97035e2ac5bdc967bb85e13 Mon Sep 17 00:00:00 2001 From: mine_diver Date: Sat, 6 Jun 2026 23:56:46 +0500 Subject: [PATCH 6/8] Builder for ConditionType --- .../api/util/context/ConditionType.java | 38 +++++++++++++++++-- .../conditional/BlockTagConditionsImpl.java | 4 +- .../api/registry/BlockRegistry.java | 18 +++++---- .../stationapi/api/registry/ItemRegistry.java | 18 +++++---- .../conditional/ItemTagConditionsImpl.java | 4 +- .../stationapi/api/registry/Registry.java | 25 +++++++----- .../api/registry/SimpleRegistry.java | 6 +-- 7 files changed, 75 insertions(+), 38 deletions(-) diff --git a/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ConditionType.java b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ConditionType.java index 7ec3f430e..dee5d8eb4 100644 --- a/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ConditionType.java +++ b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ConditionType.java @@ -4,6 +4,8 @@ import net.modificationstation.stationapi.api.util.Identifier; import java.util.function.BiPredicate; +import java.util.function.Consumer; +import java.util.function.Function; public final class ConditionType { private final Identifier id; @@ -11,16 +13,44 @@ public final class ConditionType { private final BiPredicate logic; private final MapCodec> conditionCodec; - public ConditionType(Identifier id, MapCodec dataCodec, BiPredicate logic) { - this.id = id; - this.dataCodec = dataCodec; - this.logic = logic; + private ConditionType(Builder builder) { + this.id = builder.id; + this.dataCodec = builder.codec; + + BiPredicate logic = builder.logic; + Function projection = builder.projection; + this.logic = (data, ctx) -> logic.test(data, projection.apply(ctx)); + conditionCodec = dataCodec.xmap( data -> new Condition<>(this, data), Condition::data ); } + public static Builder builder(Identifier id, MapCodec codec, Function projection, BiPredicate logic, Consumer> registryCallback) { + return new Builder<>(id, codec, projection, logic, registryCallback); + } + + public static class Builder { + private final Identifier id; + private final MapCodec codec; + private final Function projection; + private final BiPredicate logic; + private final Consumer> registryCallback; + + private Builder(Identifier id, MapCodec codec, Function projection, BiPredicate logic, Consumer> registryCallback) { + this.id = id; + this.codec = codec; + this.projection = projection; + this.logic = logic; + this.registryCallback = registryCallback; + } + + public void register() { + registryCallback.accept(new ConditionType<>(this)); + } + } + public boolean test(DATA data, Context ctx) { return logic.test(data, ctx); } diff --git a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/BlockTagConditionsImpl.java b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/BlockTagConditionsImpl.java index efe91b1b7..373b3e899 100644 --- a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/BlockTagConditionsImpl.java +++ b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/BlockTagConditionsImpl.java @@ -21,10 +21,10 @@ public class BlockTagConditionsImpl { @EventListener private static void registerConditions(BlockRegistryEvent event) { - event.registry.registerBlockTagCondition( + event.registry.buildBlockTagCondition( NAMESPACE.id("block_metadata"), Codec.INT.fieldOf("metadata"), (metadata, ctx) -> ctx.hasBlockMeta() && ctx.blockMeta() == metadata - ); + ).register(); } } diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/BlockRegistry.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/BlockRegistry.java index 6dd9b2276..904f8dd01 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/BlockRegistry.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/BlockRegistry.java @@ -1,5 +1,6 @@ package net.modificationstation.stationapi.api.registry; +import com.mojang.datafixers.util.Unit; import com.mojang.serialization.Lifecycle; import com.mojang.serialization.MapCodec; import net.minecraft.block.Block; @@ -7,6 +8,7 @@ import net.modificationstation.stationapi.api.event.registry.RegistryAttribute; import net.modificationstation.stationapi.api.event.registry.RegistryAttributeHolder; import net.modificationstation.stationapi.api.util.Identifier; +import net.modificationstation.stationapi.api.util.context.ConditionType; import net.modificationstation.stationapi.api.util.context.Context; import java.util.function.BiPredicate; @@ -26,26 +28,26 @@ private BlockRegistry() { } /** - * Registers a data-less tag condition that operates on a {@link BlockContext}. + * Creates a builder for a data-less tag condition that operates on a {@link BlockContext}. *

* This is a convenience overload that automatically projects the raw context * via {@link BlockContext#of(Context)}. * - * @see #registerTagCondition(Identifier, Function, Predicate) + * @see #buildTagCondition(Identifier, Function, Predicate) */ - public void registerBlockTagCondition(Identifier id, Predicate condition) { - registerTagCondition(id, BlockContext::of, condition); + public ConditionType.Builder buildBlockTagCondition(Identifier id, Predicate condition) { + return buildTagCondition(id, BlockContext::of, condition); } /** - * Registers a data-backed tag condition that operates on a {@link BlockContext}. + * Creates a builder for a data-backed tag condition that operates on a {@link BlockContext}. *

* This is a convenience overload that automatically projects the raw context * via {@link BlockContext#of(Context)}. * - * @see #registerTagCondition(Identifier, MapCodec, Function, BiPredicate) + * @see #buildTagCondition(Identifier, MapCodec, Function, BiPredicate) */ - public void registerBlockTagCondition(Identifier id, MapCodec codec, BiPredicate condition) { - registerTagCondition(id, codec, BlockContext::of, condition); + public ConditionType.Builder buildBlockTagCondition(Identifier id, MapCodec codec, BiPredicate condition) { + return buildTagCondition(id, codec, BlockContext::of, condition); } } diff --git a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/ItemRegistry.java b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/ItemRegistry.java index 299ba26d2..f5ba62559 100644 --- a/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/ItemRegistry.java +++ b/station-flattening-v0/src/main/java/net/modificationstation/stationapi/api/registry/ItemRegistry.java @@ -1,5 +1,6 @@ package net.modificationstation.stationapi.api.registry; +import com.mojang.datafixers.util.Unit; import com.mojang.serialization.Lifecycle; import com.mojang.serialization.MapCodec; import it.unimi.dsi.fastutil.ints.Int2IntFunction; @@ -8,6 +9,7 @@ import net.modificationstation.stationapi.api.event.registry.RegistryAttributeHolder; import net.modificationstation.stationapi.api.item.context.ItemContext; import net.modificationstation.stationapi.api.util.Identifier; +import net.modificationstation.stationapi.api.util.context.ConditionType; import net.modificationstation.stationapi.api.util.context.Context; import java.util.function.BiPredicate; @@ -29,26 +31,26 @@ private ItemRegistry() { } /** - * Registers a data-less tag condition that operates on an {@link ItemContext}. + * Creates a builder for a data-less tag condition that operates on an {@link ItemContext}. *

* This is a convenience overload that automatically projects the raw context * via {@link ItemContext#of(Context)}. * - * @see #registerTagCondition(Identifier, Function, Predicate) + * @see #buildTagCondition(Identifier, Function, Predicate) */ - public void registerItemTagCondition(Identifier id, Predicate condition) { - registerTagCondition(id, ItemContext::of, condition); + public ConditionType.Builder buildItemTagCondition(Identifier id, Predicate condition) { + return buildTagCondition(id, ItemContext::of, condition); } /** - * Registers a data-backed tag condition that operates on an {@link ItemContext}. + * Creates a builder for a data-backed tag condition that operates on an {@link ItemContext}. *

* This is a convenience overload that automatically projects the raw context * via {@link ItemContext#of(Context)}. * - * @see #registerTagCondition(Identifier, MapCodec, Function, BiPredicate) + * @see #buildTagCondition(Identifier, MapCodec, Function, BiPredicate) */ - public void registerItemTagCondition(Identifier id, MapCodec codec, BiPredicate condition) { - registerTagCondition(id, codec, ItemContext::of, condition); + public ConditionType.Builder buildItemTagCondition(Identifier id, MapCodec codec, BiPredicate condition) { + return buildTagCondition(id, codec, ItemContext::of, condition); } } diff --git a/station-items-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/ItemTagConditionsImpl.java b/station-items-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/ItemTagConditionsImpl.java index 692312f1c..ee0e64225 100644 --- a/station-items-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/ItemTagConditionsImpl.java +++ b/station-items-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/ItemTagConditionsImpl.java @@ -21,10 +21,10 @@ public class ItemTagConditionsImpl { @EventListener private static void registerConditions(ItemRegistryEvent event) { - event.registry.registerItemTagCondition( + event.registry.buildItemTagCondition( NAMESPACE.id("item_damage"), Codec.INT.fieldOf("damage"), (damage, ctx) -> ctx.hasDamage() && ctx.damage() == damage - ); + ).register(); } } diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java index ef079ab80..6ccf76634 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java @@ -12,6 +12,7 @@ import net.modificationstation.stationapi.api.util.Namespace; import net.modificationstation.stationapi.api.util.collection.IndexedIterable; import net.modificationstation.stationapi.api.util.context.Condition; +import net.modificationstation.stationapi.api.util.context.ConditionType; import net.modificationstation.stationapi.api.util.context.Context; import net.modificationstation.stationapi.api.util.dynamic.Codecs; import net.modificationstation.stationapi.api.util.function.BulkBiConsumer; @@ -506,32 +507,36 @@ public RegistryEntryList.Named getOrThrow(TagKey tag) { }; } - default void registerTagCondition(Identifier id, Predicate condition) { - registerTagCondition(id, MapCodec.unit(Unit.INSTANCE), (data, ctx) -> condition.test(ctx)); + void registerTagCondition(ConditionType conditionType); + + default ConditionType.Builder buildTagCondition(Identifier id, Predicate condition) { + return ConditionType.builder(id, MapCodec.unit(Unit.INSTANCE), Function.identity(), (data, ctx) -> condition.test(ctx), this::registerTagCondition); } /** - * Registers a tag condition with a context projection. + * Creates a builder for a tag condition with a context projection. * * @param id the condition ID * @param projection the function to project the raw context into a specific view * @param condition the condition to test against the projected view * @param the type of the projected context */ - default void registerTagCondition( + default ConditionType.Builder buildTagCondition( Identifier id, Function projection, Predicate condition ) { - registerTagCondition(id, ctx -> condition.test(projection.apply(ctx))); + return ConditionType.builder(id, MapCodec.unit(Unit.INSTANCE), projection, (data, ctx) -> condition.test(ctx), this::registerTagCondition); } - void registerTagCondition( + default ConditionType.Builder buildTagCondition( Identifier id, MapCodec codec, BiPredicate condition - ); + ) { + return ConditionType.builder(id, codec, Function.identity(), condition, this::registerTagCondition); + } /** - * Registers a data-backed tag condition with a view context projection. + * Creates a builder for a data-backed tag condition with a view context projection. * * @param id the condition ID * @param codec the codec for the condition data @@ -540,13 +545,13 @@ void registerTagCondition( * @param the type of the condition data * @param the type of the projected context */ - default void registerTagCondition( + default ConditionType.Builder buildTagCondition( Identifier id, MapCodec codec, Function projection, BiPredicate condition ) { - registerTagCondition(id, codec, (data, ctx) -> condition.test(data, projection.apply(ctx))); + return ConditionType.builder(id, codec, projection, condition, this::registerTagCondition); } Codec> getTagConditionCodec(); diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java index 8ac84bc99..7b4193cc6 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java @@ -4,7 +4,6 @@ import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Codec; import com.mojang.serialization.Lifecycle; -import com.mojang.serialization.MapCodec; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; @@ -31,7 +30,6 @@ import java.util.*; import java.util.Map.Entry; -import java.util.function.BiPredicate; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -737,8 +735,8 @@ public void unmap(String name) throws RemapException { } @Override - public void registerTagCondition(Identifier id, MapCodec codec, BiPredicate condition) { - tagConditionTypes.put(id, new ConditionType<>(id, codec, condition)); + public void registerTagCondition(ConditionType conditionType) { + tagConditionTypes.put(conditionType.id(), conditionType); } @Override From 8f354e1e170216645f71448711d69786e7dca323 Mon Sep 17 00:00:00 2001 From: mine_diver Date: Sun, 7 Jun 2026 01:39:39 +0500 Subject: [PATCH 7/8] Conditional tag shorthands --- .../tags/blocks/mineable/pickaxe.json | 10 +--- .../api/util/context/ConditionType.java | 22 +++++++ .../conditional/BlockTagConditionsImpl.java | 19 ++++-- .../conditional/ItemTagConditionsImpl.java | 19 ++++-- .../stationapi/api/registry/Registry.java | 2 + .../api/registry/SimpleRegistry.java | 5 ++ .../stationapi/api/tag/TagEntry.java | 59 ++++++++++++++++--- .../stationapi/api/tag/TagFile.java | 7 ++- .../stationapi/api/tag/TagManagerLoader.java | 7 ++- 9 files changed, 119 insertions(+), 31 deletions(-) diff --git a/src/test/resources/data/minecraft/stationapi/tags/blocks/mineable/pickaxe.json b/src/test/resources/data/minecraft/stationapi/tags/blocks/mineable/pickaxe.json index f7574ea4c..b81eabf3c 100644 --- a/src/test/resources/data/minecraft/stationapi/tags/blocks/mineable/pickaxe.json +++ b/src/test/resources/data/minecraft/stationapi/tags/blocks/mineable/pickaxe.json @@ -2,14 +2,6 @@ "values": [ "sltest:test_block", "sltest:variation_block", - { - "id": "minecraft:wool", - "conditions": [ - { - "type": "stationapi:block_metadata", - "metadata": 1 - } - ] - } + "minecraft:wool@1" ] } \ No newline at end of file diff --git a/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ConditionType.java b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ConditionType.java index dee5d8eb4..35ed51cad 100644 --- a/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ConditionType.java +++ b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ConditionType.java @@ -1,21 +1,27 @@ package net.modificationstation.stationapi.api.util.context; +import com.mojang.serialization.Dynamic; import com.mojang.serialization.MapCodec; import net.modificationstation.stationapi.api.util.Identifier; import java.util.function.BiPredicate; import java.util.function.Consumer; import java.util.function.Function; +import java.util.regex.Pattern; public final class ConditionType { private final Identifier id; private final MapCodec dataCodec; private final BiPredicate logic; + private final Pattern shorthandPattern; + private final Function, Dynamic> unfolder; private final MapCodec> conditionCodec; private ConditionType(Builder builder) { this.id = builder.id; this.dataCodec = builder.codec; + this.shorthandPattern = builder.shorthandPattern; + this.unfolder = builder.unfolder; BiPredicate logic = builder.logic; Function projection = builder.projection; @@ -37,6 +43,8 @@ public static class Builder { private final Function projection; private final BiPredicate logic; private final Consumer> registryCallback; + private Pattern shorthandPattern; + private Function, Dynamic> unfolder; private Builder(Identifier id, MapCodec codec, Function projection, BiPredicate logic, Consumer> registryCallback) { this.id = id; @@ -46,6 +54,12 @@ private Builder(Identifier id, MapCodec codec, Function projec this.registryCallback = registryCallback; } + public Builder shorthand(Pattern pattern, Function, Dynamic> unfolder) { + this.shorthandPattern = pattern; + this.unfolder = unfolder; + return this; + } + public void register() { registryCallback.accept(new ConditionType<>(this)); } @@ -70,4 +84,12 @@ public MapCodec dataCodec() { public BiPredicate logic() { return logic; } + + public Pattern shorthandPattern() { + return shorthandPattern; + } + + public Function, Dynamic> unfolder() { + return unfolder; + } } diff --git a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/BlockTagConditionsImpl.java b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/BlockTagConditionsImpl.java index 373b3e899..0f895fd92 100644 --- a/station-blocks-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/BlockTagConditionsImpl.java +++ b/station-blocks-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/BlockTagConditionsImpl.java @@ -9,6 +9,7 @@ import net.modificationstation.stationapi.api.mod.entrypoint.EventBusPolicy; import java.lang.invoke.MethodHandles; +import java.util.regex.Pattern; import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; @@ -21,10 +22,18 @@ public class BlockTagConditionsImpl { @EventListener private static void registerConditions(BlockRegistryEvent event) { - event.registry.buildBlockTagCondition( - NAMESPACE.id("block_metadata"), - Codec.INT.fieldOf("metadata"), - (metadata, ctx) -> ctx.hasBlockMeta() && ctx.blockMeta() == metadata - ).register(); + event.registry + .buildBlockTagCondition( + NAMESPACE.id("block_metadata"), + Codec.INT.fieldOf("metadata"), + (metadata, ctx) -> ctx.hasBlockMeta() && ctx.blockMeta() == metadata + ) + .shorthand( + Pattern.compile("@(\\d+)"), + dynamic -> dynamic.emptyMap().set( + "metadata", dynamic.createInt(Integer.parseInt(dynamic.asString("0"))) + ) + ) + .register(); } } diff --git a/station-items-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/ItemTagConditionsImpl.java b/station-items-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/ItemTagConditionsImpl.java index ee0e64225..59e65e47c 100644 --- a/station-items-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/ItemTagConditionsImpl.java +++ b/station-items-v0/src/main/java/net/modificationstation/stationapi/impl/tag/conditional/ItemTagConditionsImpl.java @@ -9,6 +9,7 @@ import net.modificationstation.stationapi.api.mod.entrypoint.EventBusPolicy; import java.lang.invoke.MethodHandles; +import java.util.regex.Pattern; import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE; @@ -21,10 +22,18 @@ public class ItemTagConditionsImpl { @EventListener private static void registerConditions(ItemRegistryEvent event) { - event.registry.buildItemTagCondition( - NAMESPACE.id("item_damage"), - Codec.INT.fieldOf("damage"), - (damage, ctx) -> ctx.hasDamage() && ctx.damage() == damage - ).register(); + event.registry + .buildItemTagCondition( + NAMESPACE.id("item_damage"), + Codec.INT.fieldOf("damage"), + (damage, ctx) -> ctx.hasDamage() && ctx.damage() == damage + ) + .shorthand( + Pattern.compile("@(\\d+)"), + dynamic -> dynamic.emptyMap().set( + "damage", dynamic.createInt(Integer.parseInt(dynamic.asString("0"))) + ) + ) + .register(); } } diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java index 6ccf76634..6db75db26 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/Registry.java @@ -555,4 +555,6 @@ default ConditionType.Builder buildTagConditi } Codec> getTagConditionCodec(); + + Iterable> getTagConditionTypes(); } diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java index 7b4193cc6..25bf342a6 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java @@ -743,4 +743,9 @@ public void registerTagCondition(ConditionType conditionType) { public Codec> getTagConditionCodec() { return tagConditionCodec; } + + @Override + public Iterable> getTagConditionTypes() { + return Collections.unmodifiableCollection(tagConditionTypes.values()); + } } diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagEntry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagEntry.java index f960ed290..f34cbf618 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagEntry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagEntry.java @@ -1,23 +1,52 @@ package net.modificationstation.stationapi.api.tag; import com.mojang.datafixers.util.Either; +import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.JavaOps; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.modificationstation.stationapi.api.util.Identifier; import net.modificationstation.stationapi.api.util.context.Condition; +import net.modificationstation.stationapi.api.util.context.ConditionType; import net.modificationstation.stationapi.api.util.dynamic.Codecs; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class TagEntry { - public static Codec createCodec(Codec> tagConditionCodec) { + public static Codec createCodec(Codec> tagConditionCodec, Iterable> tagConditionTypes) { return Codec.either( - Codecs.TAG_ENTRY_ID, + Codec.STRING.comapFlatMap( + s -> { + String path = s; + List> conditions = new ArrayList<>(); + boolean matched; + do { + matched = false; + for (ConditionType type : tagConditionTypes) { + Optional>> result = captureAndParse(type, path); + if (result.isPresent()) { + path = result.get().getFirst(); + conditions.add(result.get().getSecond()); + matched = true; + break; + } + } + } while (matched); + return Codecs.TAG_ENTRY_ID.parse(JavaOps.INSTANCE, path) + .map(id -> new TagEntry(id, true, conditions)); + }, + entry -> entry.getIdForCodec().toString() + ), RecordCodecBuilder.create( instance -> instance.group( Codecs.TAG_ENTRY_ID.fieldOf("id") @@ -29,14 +58,30 @@ public static Codec createCodec(Codec> tagConditionCodec) ).apply(instance, TagEntry::new) ) ).xmap( - either -> either.map( - id -> new TagEntry(id, true), - tagEntry -> tagEntry - ), - entry -> entry.required ? Either.left(entry.getIdForCodec()) : Either.right(entry) + Either::unwrap, + entry -> entry.required && entry.conditions.isEmpty() + ? Either.left(entry) + : Either.right(entry) ); } + private static Optional>> captureAndParse(ConditionType type, String path) { + Pattern pattern = type.shorthandPattern(); + if (pattern == null) return Optional.empty(); + + Matcher matcher = pattern.matcher(path); + if (!matcher.find()) return Optional.empty(); + + String remaining = path.substring(0, matcher.start()) + path.substring(matcher.end()); + String extracted = matcher.group(1); + + Dynamic dynamic = new Dynamic<>(JavaOps.INSTANCE, extracted); + Dynamic unfolded = type.unfolder().apply(dynamic); + + DataResult result = type.dataCodec().codec().parse(unfolded); + return result.result().map(data -> Pair.of(remaining, new Condition<>(type, data))); + } + private final Identifier id; private final boolean tag; private final boolean required; diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagFile.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagFile.java index 79e8ce637..2b747172a 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagFile.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagFile.java @@ -3,16 +3,17 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.modificationstation.stationapi.api.util.context.Condition; +import net.modificationstation.stationapi.api.util.context.ConditionType; import java.util.List; public record TagFile(List entries, List remove, boolean replace) { - public static Codec createCodec(Codec> tagConditionCodec) { + public static Codec createCodec(Codec> tagConditionCodec, Iterable> tagConditionTypes) { return RecordCodecBuilder.create( instance -> instance.group( - TagEntry.createCodec(tagConditionCodec).listOf().optionalFieldOf("values", List.of()) + TagEntry.createCodec(tagConditionCodec, tagConditionTypes).listOf().optionalFieldOf("values", List.of()) .forGetter(TagFile::entries), - TagEntry.createCodec(tagConditionCodec).listOf().optionalFieldOf("remove", List.of()) + TagEntry.createCodec(tagConditionCodec, tagConditionTypes).listOf().optionalFieldOf("remove", List.of()) .forGetter(TagFile::remove), Codec.BOOL.optionalFieldOf("replace", false) .forGetter(TagFile::replace) diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagManagerLoader.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagManagerLoader.java index 7627ade66..559178a1d 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagManagerLoader.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagManagerLoader.java @@ -1,7 +1,10 @@ package net.modificationstation.stationapi.api.tag; import lombok.val; -import net.modificationstation.stationapi.api.registry.*; +import net.modificationstation.stationapi.api.registry.DynamicRegistryManager; +import net.modificationstation.stationapi.api.registry.Registry; +import net.modificationstation.stationapi.api.registry.RegistryEntry; +import net.modificationstation.stationapi.api.registry.RegistryKey; import net.modificationstation.stationapi.api.resource.IdentifiableResourceReloadListener; import net.modificationstation.stationapi.api.resource.ResourceManager; import net.modificationstation.stationapi.api.resource.ResourceReloader; @@ -73,7 +76,7 @@ private CompletableFuture> buildRequiredGroup(ResourceManage TagGroupLoader> tagGroupLoader = new TagGroupLoader<>( id -> registry.getEntry(RegistryKey.of(registryKey, id)), getPath(registryKey), - TagFile.createCodec(registry.getTagConditionCodec()) + TagFile.createCodec(registry.getTagConditionCodec(), registry.getTagConditionTypes()) ); return CompletableFuture.supplyAsync(() -> new RegistryTags<>(registryKey, tagGroupLoader.load(resourceManager)), prepareExecutor); } From fb771b9e8774bb20e09a8dd91a97b915276713b6 Mon Sep 17 00:00:00 2001 From: mine_diver Date: Sun, 7 Jun 2026 15:45:00 +0500 Subject: [PATCH 8/8] Cleanup --- .../api/util/context/ActorContext.java | 4 +++- .../api/registry/RegistryEntry.java | 10 +++++----- .../api/registry/SimpleRegistry.java | 19 ++++++++++++------- .../stationapi/api/tag/TagMatchGroup.java | 9 +-------- .../impl/resource/ModResourcePackUtil.java | 15 ++++++++++----- 5 files changed, 31 insertions(+), 26 deletions(-) diff --git a/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ActorContext.java b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ActorContext.java index 68a37f43f..c4776b1c5 100644 --- a/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ActorContext.java +++ b/station-api-base/src/main/java/net/modificationstation/stationapi/api/util/context/ActorContext.java @@ -14,7 +14,9 @@ public interface ActorContext extends Context { Context.Key ACTOR_KEY = new Context.Key<>(NAMESPACE.id("actor")); static ActorContext of(Context context) { - return context instanceof ActorContext a ? a : context::getRaw; + if (context instanceof ActorContext a) return a; + interface ActorContextDelegate extends ActorContext, Delegate {} + return (ActorContextDelegate) () -> context; } /** diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntry.java index c49d2315a..a08bdcfa3 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/RegistryEntry.java @@ -120,7 +120,7 @@ public Set> getTags() { abstract class Reference implements RegistryEntry { final RegistryEntryOwner owner; - private Map, Predicate> tags = Map.of(); + private volatile Map, Predicate> tags = Map.of(); private Reference(RegistryEntryOwner owner) { this.owner = owner; @@ -140,10 +140,10 @@ public boolean matchesKey(RegistryKey key) { @Override public boolean isIn(TagKey tag, TagEvaluationContext context) { - if (!tags.containsKey(tag)) return false; - if (context.ignoreTagConditions()) return true; Predicate predicate = tags.get(tag); - return predicate == null || predicate.test(context); + if (predicate == null) return false; + if (context.ignoreTagConditions()) return true; + return predicate.test(context); } @Override @@ -184,7 +184,7 @@ public Stream> streamTags(TagEvaluationContext context) { return context.ignoreTagConditions() ? this.tags.keySet().stream() : this.tags.entrySet().stream() - .filter(entry -> entry.getValue() == null || entry.getValue().test(context)) + .filter(entry -> entry.getValue().test(context)) .map(Map.Entry::getKey); } diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java index 25bf342a6..9b10d0855 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/registry/SimpleRegistry.java @@ -39,6 +39,9 @@ import static net.modificationstation.stationapi.api.util.Namespace.MINECRAFT; public class SimpleRegistry implements MutableRegistry, RemappableRegistry, ListenableRegistry { + + + final RegistryKey> key; private final ReferenceList> rawIdToEntry = new ReferenceArrayList<>(256); private final Reference2IntMap entryToRawId = Util.make(new Reference2IntOpenHashMap<>(), map -> map.defaultReturnValue(-1)); @@ -478,7 +481,8 @@ public void populateTags(@UnknownNullability Map, Collection { for (TagMatchGroup> matchGroup : resolvedTag) { - Predicate predicate = matchGroup.conditions().isEmpty() + boolean isUnconditional = matchGroup.conditions().isEmpty(); + Predicate predicate = isUnconditional ? ctx -> true : ctx -> { for (Condition condition : matchGroup.conditions()) @@ -498,12 +502,13 @@ public void populateTags(@UnknownNullability Map, Collection oldPred.and(predicate.negate()) - ); - else - map.get(reference).merge(tag, predicate, Predicate::or); + Map, Predicate> tags = map.get(reference); + if (matchGroup.remove()) { + Predicate oldPred = tags.get(tag); + if (oldPred != null) if (isUnconditional) tags.remove(tag); + else tags.put(tag, oldPred.and(predicate.negate())); + } else if (isUnconditional) tags.put(tag, ctx -> true); + else tags.compute(tag, (k, oldPred) -> oldPred == null ? predicate : oldPred.or(predicate)); } } }); diff --git a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagMatchGroup.java b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagMatchGroup.java index b759d2027..c0305db87 100644 --- a/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagMatchGroup.java +++ b/station-registry-api-v0/src/main/java/net/modificationstation/stationapi/api/tag/TagMatchGroup.java @@ -1,7 +1,6 @@ package net.modificationstation.stationapi.api.tag; import net.modificationstation.stationapi.api.util.context.Condition; -import net.modificationstation.stationapi.api.util.context.Context; import java.util.Collection; @@ -9,10 +8,4 @@ public record TagMatchGroup( Collection baseItems, Collection> conditions, boolean remove -) { - public boolean test(T item, Context ctx) { - if (!baseItems.contains(item)) return false; - for (Condition condition : conditions) if (!condition.test(ctx)) return false; - return true; - } -} +) {} diff --git a/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/ModResourcePackUtil.java b/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/ModResourcePackUtil.java index 58298b4bb..aa168c807 100644 --- a/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/ModResourcePackUtil.java +++ b/station-resource-loader-v0/src/main/java/net/modificationstation/stationapi/impl/resource/ModResourcePackUtil.java @@ -11,6 +11,7 @@ import net.fabricmc.loader.api.ModContainer; import net.fabricmc.loader.api.metadata.CustomValue; import net.fabricmc.loader.api.metadata.ModMetadata; +import net.modificationstation.stationapi.api.StationAPI; import net.modificationstation.stationapi.api.resource.ResourcePackActivationType; import net.modificationstation.stationapi.api.resource.ResourceType; import org.apache.commons.io.IOUtils; @@ -106,11 +107,15 @@ public static void appendModResourcePacks(List packs, ResourceT } } - for (ModResourcePack p : packs) { - if (inDegree.get(p) > 0) { - sorted.add(p); - } - } + List cyclicPacks = packs.stream() + .filter(p -> inDegree.get(p) > 0) + .peek(sorted::add) + .map(ModResourcePack::getFabricModMetadata) + .map(ModMetadata::getId) + .toList(); + if (!cyclicPacks.isEmpty()) StationAPI.LOGGER.warn( + "Resource pack priority cycle detected for {}! Order is not guaranteed.", cyclicPacks + ); packs.clear(); packs.addAll(sorted);