package carpet.script.annotation; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.reflect.AnnotatedType; import java.util.Iterator; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.state.BlockState; import com.google.common.collect.Lists; import carpet.script.CarpetContext; import carpet.script.Context; import carpet.script.argument.Argument; import carpet.script.argument.BlockArgument; import carpet.script.argument.FunctionArgument; import carpet.script.argument.Vector3Argument; import carpet.script.Module; import carpet.script.value.BlockValue; import carpet.script.value.FunctionValue; import carpet.script.value.Value; import org.jspecify.annotations.Nullable; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** *

Class that holds the annotations for {@link Argument} locators, in order for them to be used in Scarpet functions.

*/ public interface Locator { /** *

Represents that the annotated argument must be gotten by passing the arguments in there into a {@link BlockArgument} locator.

* *

Must be used in either {@link BlockArgument}, {@link BlockValue}, {@link BlockPos} or {@link BlockState} parameters

*/ @Documented @Retention(RUNTIME) @Target({ PARAMETER, TYPE_USE }) @interface Block { /** *

Whether or not should the locator accept a single {@link String} as the parameter and let parsing to {@link BlockValue}.

*/ boolean acceptString() default false; /** *

Whether or not should the {@link BlockValue} argument be optional.

Requires the annotation to be present in a * {@link BlockArgument} type, since it may return that the {@link BlockValue} is {@code null}, which would be considered as an incorrect * type.

*/ boolean optional() default false; /** *

Whether or not should the {@link BlockArgument} locator accept any string as the argument.

Requires the annotation to be present * in a {@link BlockArgument} type, since it may just return a {@link String}

*/ boolean anyString() default false; } /** *

Represents that the annotated argument must be gotten by passing the arguments in there into a {@link Vector3Argument} locator.

* *

Must be used in either a {@link Vector3Argument} or a {@link net.minecraft.world.phys.Vec3 Vec3d} parameter.

*/ @Documented @Retention(RUNTIME) @Target({ PARAMETER, TYPE_USE }) @interface Vec3d { /** *

Whether or not should the {@link Vector3Argument} locator accept an optional direction aside from the * {@link net.minecraft.world.phys.Vec3}

This parameter can only be used in a {@link Vector3Argument} type, since else there is no way * to get the direction too.

*/ boolean optionalDirection() default false; /** *

Whether or not should the {@link Vector3Argument} locator accept an entity aside to get the {@link Vec3d} from and return that entity * too

Note that you will only be able to get that entity if the annotation is present in a {@link Vector3Argument}

*/ boolean optionalEntity() default false; } /** *

Represents that the annotated argument must be gotten by passing the arguments in this annotation into a {@link FunctionArgument} * locator

* *

Can be used in both {@link FunctionArgument} and {@link FunctionValue} types, but the last won't have access to arguments provided to the * function, even though they will still be consumed from the arguments the function was called with.

* *

This will consume any remaining parameters passed to the function, therefore any other parameter after this will throw.

*/ @Documented @Retention(RUNTIME) @Target({ PARAMETER, TYPE_USE }) @interface Function { /** *

Whether this Locator should allow no function to be passed.

This is not compatible with {@link FunctionValue} type, since a * converter returning {@code null} will throw as if the passed argument was incorrect. You can still use it when targeting * {@link FunctionArgument}

*/ boolean allowNone() default false; /** *

Whether the locator should check that the number of arguments passed along with the function matches the number of arguments that the * located function requires. Note that FunctionLocators consume all remaining arguments even if this is set to {@code false}.

*/ boolean checkArgs(); } /** *

Class that holds locators and methods to get them

* *

Not part of the public API, just that interfaces must have all members public

*/ final class Locators { private Locators() { super(); } static ValueConverter fromAnnotatedType(AnnotatedType annoType, Class type) { if (annoType.isAnnotationPresent(Block.class)) { return new BlockLocator<>(annoType.getAnnotation(Block.class), type); } if (annoType.isAnnotationPresent(Function.class)) { return new FunctionLocator<>(annoType.getAnnotation(Function.class), type); } if (annoType.isAnnotationPresent(Vec3d.class)) { return new Vec3dLocator<>(annoType.getAnnotation(Vec3d.class), type); } throw new IllegalStateException("Locator#fromAnnotatedType got called with an incompatible AnnotatedType"); } private static class BlockLocator extends AbstractLocator { private final java.util.function.Function returnFunction; private final boolean acceptString; private final boolean anyString; private final boolean optional; public BlockLocator(Block annotation, Class type) { super(); this.acceptString = annotation.acceptString(); this.anyString = annotation.anyString(); this.optional = annotation.optional(); if (type != BlockArgument.class && (anyString || optional)) { throw new IllegalArgumentException("Can only use anyString or optional parameters of Locator.Block if targeting a BlockArgument"); } this.returnFunction = getReturnFunction(type); if (returnFunction == null) { throw new IllegalArgumentException("Locator.Block can only be used against BlockArgument, BlockValue, BlockPos or BlockState types!"); } } @SuppressWarnings("unchecked") private static java.util.function.@Nullable Function getReturnFunction(Class type) { if (type == BlockArgument.class) { return r -> (R) r; } if (type == BlockValue.class) { return r -> (R) r.block; } if (type == BlockPos.class) { return r -> (R) r.block.getPos(); } if (type == BlockState.class) { return r -> (R) r.block.getBlockState(); } return null; } @Override public String getTypeName() { return "block"; } @Override public R checkAndConvert(Iterator valueIterator, Context context, Context.Type theLazyT) { BlockArgument locator = BlockArgument.findIn((CarpetContext) context, valueIterator, 0, acceptString, optional, anyString); return returnFunction.apply(locator); } } private static class Vec3dLocator extends AbstractLocator { private final boolean optionalDirection; private final boolean optionalEntity; private final boolean returnVec3d; public Vec3dLocator(Vec3d annotation, Class type) { this.optionalDirection = annotation.optionalDirection(); this.optionalEntity = annotation.optionalEntity(); this.returnVec3d = type == net.minecraft.world.phys.Vec3.class; // Because of the locator if (returnVec3d && optionalDirection) { throw new IllegalArgumentException("optionalDirection Locator.Vec3d cannot be used for Vec3d type, use Vector3Argument instead"); } if (!returnVec3d && type != Vector3Argument.class) { throw new IllegalArgumentException("Locator.Vec3d can only be used in Vector3Argument or Vec3d types"); } } @Override public String getTypeName() { return "position"; } @Override public R checkAndConvert(Iterator valueIterator, Context context, Context.Type theLazyT) { Vector3Argument locator = Vector3Argument.findIn(valueIterator, 0, optionalDirection, optionalEntity); @SuppressWarnings("unchecked") R ret = (R) (returnVec3d ? locator.vec : locator); return ret; } } private static class FunctionLocator extends AbstractLocator { private final boolean returnFunctionValue; private final boolean allowNone; private final boolean checkArgs; FunctionLocator(Function annotation, Class type) { super(); this.returnFunctionValue = type == FunctionValue.class; if (!returnFunctionValue && type != FunctionArgument.class) { throw new IllegalArgumentException("Params annotated with Locator.Function must be of either FunctionArgument or FunctionValue type"); } this.allowNone = annotation.allowNone(); this.checkArgs = annotation.checkArgs(); if (returnFunctionValue && allowNone) { throw new IllegalArgumentException("Cannot use allowNone of Locator.Function in FunctionValue types, use FunctionArgument"); } } @Override public R checkAndConvert(Iterator valueIterator, Context context, Context.Type theLazyT) { Module module = context.host.main; FunctionArgument locator = FunctionArgument.findIn(context, module, Lists.newArrayList(valueIterator), 0, allowNone, checkArgs); @SuppressWarnings("unchecked") R ret = (R) (returnFunctionValue ? locator.function : locator); return ret; } @Override public String getTypeName() { return "function"; } } private abstract static class AbstractLocator implements ValueConverter, Locator { @Override public R convert(Value value, @Nullable Context context) { throw new UnsupportedOperationException("Cannot call a locator in a parameter that doesn't contain a context!"); } @Override public boolean consumesVariableArgs() { return true; } @Override public int valueConsumption() { return 1; } @Override public abstract R checkAndConvert(Iterator valueIterator, Context context, Context.Type theLazyT); } } }