Skip to content
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"values": [
"sltest:test_block",
"sltest:variation_block"
"sltest:variation_block",
"minecraft:wool@1"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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<Object> ACTOR_KEY = new Context.Key<>(NAMESPACE.id("actor"));

static ActorContext of(Context context) {
if (context instanceof ActorContext a) return a;
interface ActorContextDelegate extends ActorContext, Delegate {}
return (ActorContextDelegate) () -> context;
}

/**
* {@return the actor performing the action, or {@code null} if none}
*/
default @Nullable Object actor() {
return get(ACTOR_KEY);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package net.modificationstation.stationapi.api.util.context;

public record Condition<DATA>(ConditionType<DATA> type, DATA data) {
public boolean test(Context ctx) {
return type.test(data, ctx);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
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<DATA> {
private final Identifier id;
private final MapCodec<DATA> dataCodec;
private final BiPredicate<DATA, Context> logic;
private final Pattern shorthandPattern;
private final Function<Dynamic<?>, Dynamic<?>> unfolder;
private final MapCodec<Condition<DATA>> conditionCodec;

private <C extends Context> ConditionType(Builder<DATA, C> builder) {
this.id = builder.id;
this.dataCodec = builder.codec;
this.shorthandPattern = builder.shorthandPattern;
this.unfolder = builder.unfolder;

BiPredicate<DATA, C> logic = builder.logic;
Function<Context, C> 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 <DATA, C extends Context> Builder<DATA, C> builder(Identifier id, MapCodec<DATA> codec, Function<Context, C> projection, BiPredicate<DATA, C> logic, Consumer<ConditionType<DATA>> registryCallback) {
return new Builder<>(id, codec, projection, logic, registryCallback);
}

public static class Builder<DATA, C extends Context> {
private final Identifier id;
private final MapCodec<DATA> codec;
private final Function<Context, C> projection;
private final BiPredicate<DATA, C> logic;
private final Consumer<ConditionType<DATA>> registryCallback;
private Pattern shorthandPattern;
private Function<Dynamic<?>, Dynamic<?>> unfolder;

private Builder(Identifier id, MapCodec<DATA> codec, Function<Context, C> projection, BiPredicate<DATA, C> logic, Consumer<ConditionType<DATA>> registryCallback) {
this.id = id;
this.codec = codec;
this.projection = projection;
this.logic = logic;
this.registryCallback = registryCallback;
}

public Builder<DATA, C> shorthand(Pattern pattern, Function<Dynamic<?>, Dynamic<?>> unfolder) {
this.shorthandPattern = pattern;
this.unfolder = unfolder;
return this;
}

public void register() {
registryCallback.accept(new ConditionType<>(this));
}
}

public boolean test(DATA data, Context ctx) {
return logic.test(data, ctx);
}

public MapCodec<Condition<DATA>> conditionCodec() {
return conditionCodec;
}

public Identifier id() {
return id;
}

public MapCodec<DATA> dataCodec() {
return dataCodec;
}

public BiPredicate<DATA, Context> logic() {
return logic;
}

public Pattern shorthandPattern() {
return shorthandPattern;
}

public Function<Dynamic<?>, Dynamic<?>> unfolder() {
return unfolder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package net.modificationstation.stationapi.api.util.context;

import net.modificationstation.stationapi.api.util.Identifier;
import org.jetbrains.annotations.Nullable;

import java.util.Optional;

import static net.modificationstation.stationapi.api.StationAPI.NAMESPACE;

/**
* A data source that contains keys and their associated values.
* <p>
* 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 delegate interface for creating zero-allocation projections.
*/
@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);
}
}

@SuppressWarnings("unused")
record Key<VALUE>(Identifier id) {}

/**
* A key used to mark a context as explicitly empty/inert.
* Composing with an empty context will yield the other context unchanged.
*/
Key<Boolean> EMPTY_KEY = new Key<>(NAMESPACE.id("empty"));

/**
* An empty context that acts as an identity element for composition.
* <p>
* 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}
*/
static <VALUE> Context of(Key<VALUE> key, VALUE value) {
Identifier id = key.id();
return k -> id == k ? value : null;
}

/**
* {@return the raw value associated with the given identifier, or {@code null} if not
* present}
* <p>
* This is the primary abstract method for implementations.
*/
@Nullable Object getRaw(Identifier id);

/**
* {@return the value associated with the given key, or {@code null} if not
* present}
*/
@SuppressWarnings("unchecked")
default <VALUE> @Nullable VALUE get(Key<VALUE> key) {
return (VALUE) getRaw(key.id());
}

/**
* {@return whether this context contains a value associated with the given identifier}
*/
default boolean contains(Identifier id) {
return getRaw(id) != null;
}

/**
* {@return whether this context contains the given key}
*/
default boolean contains(Key<?> key) {
return contains(key.id());
}

/**
* {@return an optional containing the value associated with the given identifier}
*/
default Optional<Object> getOptional(Identifier id) {
return Optional.ofNullable(getRaw(id));
}

/**
* {@return an optional containing the value associated with the given key}
*/
default <VALUE> Optional<VALUE> getOptional(Key<VALUE> key) {
return Optional.ofNullable(get(key));
}

/**
* {@return the unboxed integer associated with the given key, or {@code defaultValue} if not present}
*/
default int getInt(Key<Integer> key, int defaultValue) {
return getIntRaw(key.id(), defaultValue);
}

/**
* {@return the unboxed integer associated with the given identifier, or {@code defaultValue} if not present}
*/
default int getIntRaw(Identifier id, int defaultValue) {
Object raw = getRaw(id);
return raw instanceof Integer i ? i : defaultValue;
}

/**
* {@return a new context that includes the given key-value pair as an override}
*/
default <VALUE> Context with(Key<VALUE> key, VALUE value) {
Identifier id = key.id();
return with(k -> id == k ? value : null);
}

/**
* {@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 == 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) {
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;
}

@Override
public @Nullable Object getRaw(Identifier id) {
Context ctx = find(id);
return ctx == null ? null : ctx.getRaw(id);
}

@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 addition instanceof Composite composite
? append(append(base, composite.next), composite.head)
: new Composite(addition, base);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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 java.lang.invoke.MethodHandles;
import java.util.regex.Pattern;

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
.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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
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 org.spongepowered.asm.mixin.Mixin;
Expand Down Expand Up @@ -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, BlockTagContext.of(world, x, y - 1, z)
) ? 1 : 0;
}
}
Loading