diff --git a/common/src/main/java/org/figuramc/figura/lua/api/RaycastAPI.java b/common/src/main/java/org/figuramc/figura/lua/api/RaycastAPI.java index 10788bb10..06c331c6a 100644 --- a/common/src/main/java/org/figuramc/figura/lua/api/RaycastAPI.java +++ b/common/src/main/java/org/figuramc/figura/lua/api/RaycastAPI.java @@ -1,5 +1,8 @@ package org.figuramc.figura.lua.api; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Optional; import java.util.function.Predicate; import org.figuramc.figura.avatar.Avatar; @@ -10,13 +13,17 @@ import org.figuramc.figura.lua.docs.LuaMethodOverload; import org.figuramc.figura.lua.docs.LuaTypeDoc; import org.figuramc.figura.math.vector.FiguraVec3; +import org.figuramc.figura.mixin.AABBInvoker; import org.figuramc.figura.utils.LuaUtils; import org.luaj.vm2.LuaError; import org.luaj.vm2.LuaFunction; +import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaValue; import com.mojang.datafixers.util.Pair; +import kroppeb.stareval.function.Type.Int; +import net.minecraft.core.Direction; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.Marker; @@ -25,6 +32,7 @@ import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.Vec3; @LuaWhitelist @LuaTypeDoc( @@ -44,31 +52,38 @@ public RaycastAPI(Avatar owner) { @LuaMethodDoc( overloads = { @LuaMethodOverload( - argumentTypes = {String.class, String.class, FiguraVec3.class, FiguraVec3.class}, - argumentNames = {"blockCastType", "fluidCastType", "start", "end"} + argumentTypes = {FiguraVec3.class, FiguraVec3.class, String.class, String.class}, + argumentNames = {"start", "end", "blockCastType", "fluidCastType"} ), @LuaMethodOverload( - argumentTypes = {String.class, String.class, Double.class, Double.class, Double.class, FiguraVec3.class}, - argumentNames = {"blockCastType", "fluidCastType", "startX", "startY", "startZ", "end"} + argumentTypes = {Double.class, Double.class, Double.class, FiguraVec3.class, String.class, String.class}, + argumentNames = {"startX", "startY", "startZ", "end", "blockCastType", "fluidCastType"} ), @LuaMethodOverload( - argumentTypes = {String.class, String.class, FiguraVec3.class, Double.class, Double.class, Double.class}, - argumentNames = {"blockCastType", "fluidCastType", "start", "endX", "endY", "endZ"} + argumentTypes = {FiguraVec3.class, Double.class, Double.class, Double.class, String.class, String.class}, + argumentNames = {"start", "endX", "endY", "endZ", "blockCastType", "fluidCastType"} ), @LuaMethodOverload( - argumentTypes = {String.class, String.class, Double.class, Double.class, Double.class, Double.class, Double.class, Double.class}, - argumentNames = {"blockCastType", "fluidCastType", "startX", "startY", "startZ", "endX", "endY", "endZ"} + argumentTypes = {Double.class, Double.class, Double.class, Double.class, Double.class, Double.class, String.class, String.class}, + argumentNames = {"startX", "startY", "startZ", "endX", "endY", "endZ", "blockCastType", "fluidCastType"} ) } , value = "raycast.block" ) - public Object[] block(String blockCastType, String fluidCastType, Object x, Object y, Double z, Object w, Double t, Double h) { + public Object[] block(Object x, Object y, Object z, Object w, Object t, Object h, String blockCastType, String fluidCastType) { FiguraVec3 start, end; + Pair, Object[]> parseResult = LuaUtils.parse2Vec3( + "block", + new Class[]{String.class, String.class}, + x, y, z, w, t, h, blockCastType, fluidCastType + ); - Pair pair = LuaUtils.parse2Vec3("block", x, y, z, w, t, h,1); - start = pair.getFirst(); - end = pair.getSecond(); + start = parseResult.getFirst().getFirst(); + end = parseResult.getFirst().getSecond(); + + blockCastType = (String)parseResult.getSecond()[0]; + fluidCastType = (String)parseResult.getSecond()[1]; ClipContext.Block blockContext; try{ @@ -94,35 +109,41 @@ public Object[] block(String blockCastType, String fluidCastType, Object x, Obje @LuaMethodDoc( overloads = { @LuaMethodOverload( - argumentTypes = {LuaFunction.class, FiguraVec3.class, FiguraVec3.class}, - argumentNames = {"predicate", "start", "end"} + argumentTypes = {FiguraVec3.class, FiguraVec3.class, LuaFunction.class}, + argumentNames = {"start", "end", "predicate"} ), @LuaMethodOverload( - argumentTypes = {LuaFunction.class, Double.class, Double.class, Double.class, FiguraVec3.class}, - argumentNames = {"predicate", "startX", "startY", "startZ", "end"} + argumentTypes = {Double.class, Double.class, Double.class, FiguraVec3.class, LuaFunction.class}, + argumentNames = {"startX", "startY", "startZ", "end", "predicate"} ), @LuaMethodOverload( - argumentTypes = {LuaFunction.class, FiguraVec3.class, Double.class, Double.class, Double.class}, - argumentNames = {"predicate", "start", "endX", "endY", "endZ"} + argumentTypes = {FiguraVec3.class, Double.class, Double.class, Double.class, LuaFunction.class}, + argumentNames = {"start", "endX", "endY", "endZ", "predicate"} ), @LuaMethodOverload( - argumentTypes = {LuaFunction.class, Double.class, Double.class, Double.class, Double.class, Double.class, Double.class}, - argumentNames = {"predicate", "startX", "startY", "startZ", "endX", "endY", "endZ"} + argumentTypes = {Double.class, Double.class, Double.class, Double.class, Double.class, Double.class, LuaFunction.class}, + argumentNames = {"startX", "startY", "startZ", "endX", "endY", "endZ", "predicate"} ) } , value = "raycast.entity" ) - public Object[] entity(LuaFunction predicate, Object x, Object y, Double z, Object w, Double t, Double h) { + public Object[] entity(Object x, Object y, Object z, Object w, Object t, Double h, LuaFunction predicate) { FiguraVec3 start, end; - Pair pair = LuaUtils.parse2Vec3("entity", x, y, z, w, t, h, 1); - start = pair.getFirst(); - end = pair.getSecond(); + Pair, Object[]> pair = LuaUtils.parse2Vec3( + "entity", + new Class[]{LuaFunction.class}, + x, y, z, w, t, h, predicate); + + start = pair.getFirst().getFirst(); + end = pair.getFirst().getSecond(); + + final LuaFunction fn = (LuaFunction)pair.getSecond()[0]; Predicate entityPredicate = (entity) -> { - if (predicate == null) return true; - LuaValue result = predicate.invoke(this.owner.luaRuntime.typeManager.javaToLua(EntityAPI.wrap(entity))).arg1(); + if (fn == null) return true; + LuaValue result = fn.invoke(this.owner.luaRuntime.typeManager.javaToLua(EntityAPI.wrap(entity))).arg1(); if ((result.isboolean() && result.checkboolean() == false) || result.isnil()) return false; return true; @@ -135,4 +156,118 @@ public Object[] entity(LuaFunction predicate, Object x, Object y, Double z, Obje return null; } + + @LuaWhitelist + @LuaMethodDoc( + overloads = { + @LuaMethodOverload( + argumentTypes = {FiguraVec3.class, FiguraVec3.class, LuaTable.class}, + argumentNames = {"start", "end", "aabbs"} + ), + @LuaMethodOverload( + argumentTypes = {Double.class, Double.class, Double.class, FiguraVec3.class, LuaTable.class}, + argumentNames = {"startX", "startY", "startZ", "end", "aabbs"} + ), + @LuaMethodOverload( + argumentTypes = {FiguraVec3.class, Double.class, Double.class, Double.class, LuaTable.class}, + argumentNames = {"start", "endX", "endY", "endZ", "aabbs"} + ), + @LuaMethodOverload( + argumentTypes = {Double.class, Double.class, Double.class, Double.class, Double.class, Double.class, LuaTable.class}, + argumentNames = {"startX", "startY", "startZ", "endX", "endY", "endZ", "aabbs"} + ) + } + , + value = "raycast.aabb" + ) + public Object[] aabb(Object x, Object y, Object z, Object w, Object t, Object h, LuaTable aabbs) { + Vec3 start, end; + + Pair, Object[]> pair = LuaUtils.parse2Vec3( + "aabb", + new Class[]{LuaTable.class}, + x, y, z, w, t, h, aabbs + ); + + start = pair.getFirst().getFirst().asVec3(); + end = pair.getFirst().getSecond().asVec3(); + + aabbs = (LuaTable)pair.getSecond()[0]; + if (aabbs == null) + throw new LuaError("Illegal argument to aabb(): Expected LuaTable, recieved nil"); + + ArrayList aabbList = new ArrayList(); + for (int i=1;i<=aabbs.length();i++){ + LuaValue arg = aabbs.get(i); + if (!arg.istable()) + throw new LuaError("Illegal argument at array index " + i + ": Expected table, recieved " + arg.typename() + " ("+arg.toString()+")"); + + LuaValue min = arg.get(1); + if (!min.isuserdata(FiguraVec3.class)) + throw new LuaError("Illegal argument to AABB at array index "+ i +" at index 1: Expected Vector3, recieved " + min.typename() + " ("+min.toString()+")"); + + LuaValue max = arg.get(2); + if (!max.isuserdata(FiguraVec3.class)) + throw new LuaError("Illegal argument to AABB at array index "+ i +" at index 2: Expected Vector3, recieved " + max.typename() + " ("+max.toString()+")"); + + aabbList.add(new AABB( + ((FiguraVec3)min.checkuserdata(FiguraVec3.class)).asVec3(), + ((FiguraVec3)max.checkuserdata(FiguraVec3.class)).asVec3() + )); + } + + // Modified from ProjectileUtil.getEntityHitResult to utilize arbitrary AABBs, and the custom clipAABB function + // I was unable to figure out how the BlockState clipping worked, which would have been better. + { + double d = Double.MAX_VALUE; + int index = -1; + Pair result = null; + + for(int i = 0; i < aabbList.size(); i++) { + AABB box = aabbList.get(i); + Optional> optional = clipAABB(box, start, end); + if (box.contains(start)) { + if (d >= 0.0) { + index = i+1; + result = optional.orElse(Pair.of(start, null)); + d = 0.0; + } + } else if (optional.isPresent()) { + Vec3 position = optional.get().getFirst(); + double e = start.distanceToSqr(position); + if (e < d || d == 0.0) { + index = i+1; + result = optional.get(); + d = e; + } + } + } + + if (index == -1) { + return null; + } + + return new Object[]{ + aabbs.get(index), + FiguraVec3.fromVec3(result.getFirst()), + result.getSecond()!=null ? result.getSecond().getName() : null, + index + }; + } + } + + // Modified from AABB.clip(Vec3 min, Vec3 max) to also return the side hit + public Optional> clipAABB(AABB aabb, Vec3 min, Vec3 max) { + double[] ds = new double[]{1.0}; + double d = max.x - min.x; + double e = max.y - min.y; + double f = max.z - min.z; + Direction direction = AABBInvoker.getDirection(aabb, min, ds, (Direction)null, d, e, f); + if (direction == null) { + return Optional.empty(); + } else { + double g = ds[0]; + return Optional.of(Pair.of(min.add(g * d, g * e, g * f), direction)); + } + } } diff --git a/common/src/main/java/org/figuramc/figura/mixin/AABBInvoker.java b/common/src/main/java/org/figuramc/figura/mixin/AABBInvoker.java new file mode 100644 index 000000000..a12263d72 --- /dev/null +++ b/common/src/main/java/org/figuramc/figura/mixin/AABBInvoker.java @@ -0,0 +1,19 @@ +package org.figuramc.figura.mixin; + +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Intrinsic; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +import net.minecraft.core.Direction; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; + +@Mixin(AABB.class) +public interface AABBInvoker { + @Intrinsic + @Invoker("getDirection") + public static Direction getDirection(AABB box, Vec3 intersectingVector, double[] traceDistanceResult, @Nullable Direction approachDirection, double deltaX, double deltaY, double deltaZ) { + throw new AssertionError(); + } +} diff --git a/common/src/main/java/org/figuramc/figura/utils/LuaUtils.java b/common/src/main/java/org/figuramc/figura/utils/LuaUtils.java index 9c489a257..346278e52 100644 --- a/common/src/main/java/org/figuramc/figura/utils/LuaUtils.java +++ b/common/src/main/java/org/figuramc/figura/utils/LuaUtils.java @@ -16,6 +16,10 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; + +import java.util.ArrayList; +import java.util.Arrays; + import org.figuramc.figura.lua.api.json.FiguraJsonSerializer; import org.figuramc.figura.lua.api.world.BlockStateAPI; import org.figuramc.figura.lua.api.world.ItemStackAPI; @@ -23,6 +27,7 @@ import org.figuramc.figura.math.vector.FiguraVec2; import org.figuramc.figura.math.vector.FiguraVec3; import org.figuramc.figura.math.vector.FiguraVec4; +import org.figuramc.figura.math.vector.FiguraVector; import org.luaj.vm2.LuaError; import org.luaj.vm2.LuaString; import org.luaj.vm2.LuaTable; @@ -30,21 +35,124 @@ public class LuaUtils { - public static FiguraVec4 parseVec4(String methodName, Object x, Number y, Number z, Number w, double defaultX, double defaultY, double defaultZ, double defaultW) { + /** + * This is a generic vector parsing function that also parses the arguments after the vectors, allowing vectors to be at the beginning of the function signature + * @param methodName The name of the function that is calling this function. Used for readable errors. + * @param vectorSizes The sizes of Vectors to parse. The number of vectors is determined by the size of the array. + * @param defaultValues When a Vector or a Vector argument is nil, it will be filled in with the value in this array at the correct index. + * @param expectedReturns An array of Classes for what the extra arguments are supposed to be. Used for readable errors. + * @param args The arguments of the function, passed in as varargs. + * @return The new args list with multi-number-argument Vectors being returned as real Vectors. + */ + public static Object[] parseVec(String methodName, int[] vectorSizes, double[] defaultValues, Class[] expectedReturns, Object ...args) { + ArrayList ret = new ArrayList(args.length); + int i=0; + for(int size : vectorSizes) { + if (args[i] instanceof FiguraVector vec){ + if(vec.size()!=size) + throw new LuaError("Illegal argument at position " + (i + 1) + " to " + methodName + "(): Expected Vector" + size + ", recieved Vector" + vec.size()); + ret.add(vec); + i += 1; + } + else if (args[i]==null || args[i] instanceof Number) { + double[] vec = new double[size]; + for (int o=0;oFiguraVec2.of(vec[0], vec[1]); + case 3->FiguraVec3.of(vec[0], vec[1], vec[2]); + case 4->FiguraVec4.of(vec[0], vec[1], vec[2], vec[3]); + default->throw new IllegalArgumentException("Illegal vector size: " + size); + } + ); + i += size; + } + else if(args[i]==null) { + ret.add( + switch(size){ + case 2->FiguraVec2.of(defaultValues[0], defaultValues[1]); + case 3->FiguraVec3.of(defaultValues[0], defaultValues[1], defaultValues[2]); + case 4->FiguraVec4.of(defaultValues[0], defaultValues[1], defaultValues[2], defaultValues[3]); + default->throw new IllegalArgumentException("Illegal vector size: " + size); + } + ); + i += 1; + } + else + throw new LuaError("Illegal argument at position " + (i + 1) + " to " + methodName + "():" + + " Expected Vector" + size + " or Number, recieved " + args[i].getClass().getSimpleName() + " (" + args[i] + ")" + ); + } + for(int o = i; o < args.length; o++) { + if(args[o] != null && (o-i) < expectedReturns.length && !expectedReturns[o-i].isAssignableFrom(args[o].getClass())) + throw new LuaError("Illegal argument at position " + (o + 1) + " to " + methodName + "():" + + " Expected " + expectedReturns[o-i].getSimpleName() + ", recieved " + args[o].getClass().getSimpleName() + " (" + args[o] + ")" + ); + ret.add(args[o]); + } + return ret.toArray(); + } + + public static Object[] parseVec(String methodName, int[] vectorSizes, Class[] expectedReturns, Object ...args) { + return parseVec(methodName, vectorSizes, new double[]{0,0,0,0}, expectedReturns, args); + } + + public static FiguraVec2 parseVec2(String methodName, Object x, Number y) { + return parseVec2(methodName, x, y, 0, 0); + } + + public static FiguraVec2 parseVec2(String methodName, Object x, Number y, double defaultX, double defaultY) { + if (x instanceof FiguraVec2 vec) + return vec.copy(); + if (x == null || x instanceof Number) { + if (x == null) x = defaultX; + if (y == null) y = defaultY; + return FiguraVec2.of(((Number) x).doubleValue(), y.doubleValue()); + } + throw new LuaError("Illegal argument to " + methodName + "(): " + x.getClass().getSimpleName()); + } + + /** + * This code gets repeated SO MUCH that I decided to put it in the utils class. + * @param x Either the x coordinate of a vector, or a vector itself. + * @param y The y coordinate of a vector, used if the first parameter was a number. + * @param z The z coordinate of a vector, used if the first parameter was a number. + * @return A FiguraVec3 representing the data passed in. + */ + public static FiguraVec3 parseVec3(String methodName, Object x, Number y, Number z) { + return parseVec3(methodName, x, y, z, 0, 0, 0); + } + + public static FiguraVec3 parseVec3(String methodName, Object x, Number y, Number z, double defaultX, double defaultY, double defaultZ) { if (x instanceof FiguraVec3 vec) - return FiguraVec4.of(vec.x, vec.y, vec.z, defaultW); - if (x instanceof FiguraVec4 vec) return vec.copy(); if (x == null || x instanceof Number) { if (x == null) x = defaultX; if (y == null) y = defaultY; if (z == null) z = defaultZ; - if (w == null) w = defaultW; - return FiguraVec4.of(((Number) x).doubleValue(), y.doubleValue(), z.doubleValue(), w.doubleValue()); + return FiguraVec3.of(((Number) x).doubleValue(), y.doubleValue(), z.doubleValue()); } throw new LuaError("Illegal argument to " + methodName + "(): " + x.getClass().getSimpleName()); } + public static FiguraVec3 parseOneArgVec(String methodName, Object x, Number y, Number z, double defaultArg) { + double d = x instanceof Number n ? n.doubleValue() : defaultArg; + return parseVec3(methodName, x, y, z, d, d, d); + } + + public static FiguraVec3 nullableVec3(String methodName, Object x, Number y, Number z) { + return x == null ? null : parseVec3(methodName, x, y, z); + } + public static Pair parse2Vec3(String methodName, Object x, Object y, Number z, Object w, Number t, Number h, int xIndex) { FiguraVec3 a, b; @@ -77,49 +185,31 @@ public static Pair parse2Vec3(String methodName, Object return Pair.of(a, b); } - /** - * This code gets repeated SO MUCH that I decided to put it in the utils class. - * @param x Either the x coordinate of a vector, or a vector itself. - * @param y The y coordinate of a vector, used if the first parameter was a number. - * @param z The z coordinate of a vector, used if the first parameter was a number. - * @return A FiguraVec3 representing the data passed in. - */ - public static FiguraVec3 parseVec3(String methodName, Object x, Number y, Number z) { - return parseVec3(methodName, x, y, z, 0, 0, 0); + // These functions allow having vector parsing at the beggining of the function, taking into account other arguments. + public static Pair parseVec3(String methodName, Class[] expectedReturns, Object ...args) { + Object[] parsed = parseVec(methodName, new int[]{3}, expectedReturns, args); + return Pair.of((FiguraVec3)parsed[0], Arrays.copyOfRange(parsed, 1, parsed.length)); } - public static FiguraVec3 parseVec3(String methodName, Object x, Number y, Number z, double defaultX, double defaultY, double defaultZ) { - if (x instanceof FiguraVec3 vec) - return vec.copy(); - if (x == null || x instanceof Number) { - if (x == null) x = defaultX; - if (y == null) y = defaultY; - if (z == null) z = defaultZ; - return FiguraVec3.of(((Number) x).doubleValue(), y.doubleValue(), z.doubleValue()); - } - throw new LuaError("Illegal argument to " + methodName + "(): " + x.getClass().getSimpleName()); - } - - public static FiguraVec3 parseOneArgVec(String methodName, Object x, Number y, Number z, double defaultArg) { - double d = x instanceof Number n ? n.doubleValue() : defaultArg; - return parseVec3(methodName, x, y, z, d, d, d); - } - - public static FiguraVec3 nullableVec3(String methodName, Object x, Number y, Number z) { - return x == null ? null : parseVec3(methodName, x, y, z); - } - - public static FiguraVec2 parseVec2(String methodName, Object x, Number y) { - return parseVec2(methodName, x, y, 0, 0); + public static Pair, Object[]> parse2Vec3(String methodName, Class[] expectedReturns, Object ...args) { + Object[] parsed = parseVec(methodName, new int[]{3,3}, expectedReturns, args); + return Pair.of( + Pair.of((FiguraVec3)parsed[0], (FiguraVec3)parsed[1]), + Arrays.copyOfRange(parsed, 2, parsed.length) + ); } - public static FiguraVec2 parseVec2(String methodName, Object x, Number y, double defaultX, double defaultY) { - if (x instanceof FiguraVec2 vec) + public static FiguraVec4 parseVec4(String methodName, Object x, Number y, Number z, Number w, double defaultX, double defaultY, double defaultZ, double defaultW) { + if (x instanceof FiguraVec3 vec) + return FiguraVec4.of(vec.x, vec.y, vec.z, defaultW); + if (x instanceof FiguraVec4 vec) return vec.copy(); if (x == null || x instanceof Number) { if (x == null) x = defaultX; if (y == null) y = defaultY; - return FiguraVec2.of(((Number) x).doubleValue(), y.doubleValue()); + if (z == null) z = defaultZ; + if (w == null) w = defaultW; + return FiguraVec4.of(((Number) x).doubleValue(), y.doubleValue(), z.doubleValue(), w.doubleValue()); } throw new LuaError("Illegal argument to " + methodName + "(): " + x.getClass().getSimpleName()); } diff --git a/common/src/main/resources/assets/figura/lang/en_us.json b/common/src/main/resources/assets/figura/lang/en_us.json index fa7a9c184..198fbf5ba 100644 --- a/common/src/main/resources/assets/figura/lang/en_us.json +++ b/common/src/main/resources/assets/figura/lang/en_us.json @@ -1335,6 +1335,7 @@ "figura.docs.pings": "A global API dedicated to register and call pings", "figura.docs.ping_function": "A custom function wrapped with networking data", "figura.docs.raycast": "A global API which provides functions for raycasting", + "figura.docs.raycast.aabb": "Raycasts based on a start position, an end position, and an array of Axis Aligned Bounding Boxes defined by the player.\nAABBs are encoded as a table with indicies 1 and 2 being a Vector3.\n`{vec(0,0,0),vec(1,0.5,1)}` is a valid AABB, with `{ {vec(0,0,0),vec(1,0.5,1)}, {vec(0,0.5,0.5),vec(1,1,1)} }` being a valid AABB array.\nThis function returns the AABB table that was hit, the exact position hit as a Vector3, the side of the AABB hit as a string or nil if inside an AABB, and the index of the AABB that was hit in the array", "figura.docs.raycast.block": "Raycasts a Block in the world.\nIf successful, returns the BlockState hit, the exact world position hit as a Vector3, and the side of the block that was hit.\nWhen unsuccessful, returns nil.\nblockCastType and fluidCastType determine how the raycast handles block shapes and fluids.\nWill default to \"COLLIDER\" and \"NONE\" when nil", "figura.docs.raycast.entity": "Raycasts an Entity in the world\nIf successful, returns the EntityAPI hit and the exact world position hit as a Vector3.\nWhen unsuccessful, returns nil.\npredicate is a function that prevents specific entities from being raycasted.\nTakes in a single EntityAPI object. Return true for valid entities, false for invalid.\nMarks all entities as valid when nil", "figura.docs.render_task": "Represents a rendering task for Figura to complete each frame\nAn abstract superclass of ItemTask, BlockTask, and TextTask", diff --git a/common/src/main/resources/figura-common.mixins.json b/common/src/main/resources/figura-common.mixins.json index 8c5372249..284db688f 100644 --- a/common/src/main/resources/figura-common.mixins.json +++ b/common/src/main/resources/figura-common.mixins.json @@ -5,6 +5,7 @@ "compatibilityLevel": "JAVA_17", "mixins": [], "client": [ + "AABBInvoker", "BiomeAccessor", "BlockBehaviourAccessor", "ClientCommandSourceMixin",