diff --git a/src/main/java/ch/njol/skript/expressions/ExprLocationVectorOffset.java b/src/main/java/ch/njol/skript/expressions/ExprLocationVectorOffset.java index 2139551122a..b0791654a73 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLocationVectorOffset.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLocationVectorOffset.java @@ -1,64 +1,71 @@ package ch.njol.skript.expressions; -import ch.njol.skript.lang.Literal; -import org.bukkit.Location; -import org.bukkit.event.Event; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Example; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.*; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.simplification.SimplifiedLiteral; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; -import ch.njol.skript.lang.simplification.SimplifiedLiteral; - +import org.bukkit.Location; +import org.bukkit.event.Event; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; -/** - * @author bi0qaw - */ @Name("Vectors - Location Vector Offset") -@Description("Returns the location offset by vectors.") -@Examples({"set {_loc} to {_loc} ~ {_v}"}) -@Since("2.2-dev28") +@Description("Returns the location offset by vectors. Supports both global and local axes. " + + "When using local axes, the vector is applied relative to the direction the location is facing.") +@Example("set {_loc} to {_loc} ~ {_v}") +@Example(""" + # spawn a tnt 5 blocks in front of player + set {_l} to player's location offset by vector(0, 1, 5) using local axes + spawn tnt at {_l} + """) +@Since("2.2-dev28, INSERT VERSION (local axes)") public class ExprLocationVectorOffset extends SimpleExpression { static { Skript.registerExpression(ExprLocationVectorOffset.class, Location.class, ExpressionType.PROPERTY, - "%location% offset by [[the] vectors] %vectors%", + "%location% offset by [[the] vectors] %vectors% [facingrelative:using local axes]", "%location%[ ]~[~][ ]%vectors%"); } - @SuppressWarnings("null") private Expression location; - - @SuppressWarnings("null") private Expression vectors; + private boolean usingLocalAxes; + @Override - @SuppressWarnings({"unchecked", "null"}) public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + // noinspection unchecked location = (Expression) exprs[0]; + // noinspection unchecked vectors = (Expression) exprs[1]; + usingLocalAxes = parseResult.hasTag("facingrelative"); return true; } - @SuppressWarnings("null") @Override - protected Location[] get(Event e) { - Location l = location.getSingle(e); - if (l == null) + protected Location[] get(Event event) { + Location location = this.location.getSingle(event); + if (location == null) return null; - Location clone = l.clone(); - for (Vector v : vectors.getArray(e)) - clone.add(v); - return CollectionUtils.array(clone); + + Location clone = location.clone(); + + for (Vector vector : vectors.getArray(event)) { + if (usingLocalAxes) { + clone = getFacingRelativeOffset(clone, vector); + } else { + clone.add(vector); + } + } + return new Location[]{ clone }; } @Override @@ -70,8 +77,7 @@ public boolean isSingle() { public Class getReturnType() { return Location.class; } - - @Override + public Expression simplify() { if (location instanceof Literal && vectors instanceof Literal) return SimplifiedLiteral.fromExpression(this); @@ -79,8 +85,36 @@ public Expression simplify() { } @Override - public String toString(@Nullable Event e, boolean debug) { - return location.toString() + " offset by " + vectors.toString(); + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append(location) + .append("offset by") + .append(vectors) + .append(usingLocalAxes ? "using local axes" : "") + .toString(); + } + + /** + * Returns a location offset from the given location, adjusted for the location's rotation. + *

+ * This behaves similarly to Minecraft's {@code /summon zombie ^ ^ ^1} command, + * where the offset is applied relative to the entity's facing direction. + * + * @see Local Coordinates. + * @param loc The location + * @param offset The offset + * @return The offset location + */ + private static Location getFacingRelativeOffset(Location loc, Vector offset) { + float yawRad = (float) Math.toRadians(-loc.getYaw()); + float pitchRad = (float) Math.toRadians(loc.getPitch()); + float rollRad = 0f; + + Quaternionf rotation = new Quaternionf().rotateYXZ(yawRad, pitchRad, rollRad); + Vector3f localOffset = offset.toVector3f(); + rotation.transform(localOffset); + + return loc.add(localOffset.x, localOffset.y, localOffset.z); } } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprLocationVectorOffset.sk b/src/test/skript/tests/syntaxes/expressions/ExprLocationVectorOffset.sk index 4512b7dc0d7..a2346ad575a 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprLocationVectorOffset.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprLocationVectorOffset.sk @@ -12,3 +12,29 @@ test "location vector offset": set {_offset} to {_location} ~ vector({_x}, {_y}, {_z}) set {_length} to the normal length of vector({_x}, {_y}, {_z}) assert the distance between {_offset} and {_location} is {_length} with "randomly-created vector offset failed (expected %{_length}%, got %distance between {_offset} and {_location}%)" + +test "vector offset using local axes": + set {_test.loc} to location(-19.40051951171598, 45.37940054464023, -2.293542970995297, world("world"), -72.04071, -82.03418) + set {_expected.loc} to location(-14.934356830136391, 49.420007997032805, -6.763092626909604, world("world"), -72.04071, -82.03418) + set {_v} to vector(5.628876967791593, -2.2827525504406823, 4.399407332234864) + assert ({_test.loc} offset by {_v} using local axes) is {_expected.loc} with "returned wrong location - 1" + + set {_test.loc} to location(13.30406555773089, 67.5819873186199, -6.298727546251565, world("world"), 103.484215, 19.238579) + set {_expected.loc} to location(10.647636743796564, 60.574954924210715, -6.606756333626062, world("world"), 103.484215, 19.238579) + set {_v} to vector(0.31988220626119995, -5.740885067612596, 4.815590723325663) + assert ({_test.loc} offset by {_v} using local axes) is {_expected.loc} with "returned wrong location - 2" + + set {_test.loc} to location(3.3869350328876977, 44.792269367551405, -20.474066984956654, world("world"), 157.17847, 75.89636) + set {_expected.loc} to location(7.324176348729372, 38.611687797879775, -22.070485604112537, world("world"), 157.17847, 75.89636) + set {_v} to vector(-4.248213705545501, -1.560043345242382, 5.980714428308739) + assert ({_test.loc} offset by {_v} using local axes) is {_expected.loc} with "returned wrong location - 3" + + set {_test.loc} to location(20.245140591456668, 39.23482347167744, 7.434704275414153, world("world"), 86.558136, -45.623573) + set {_expected.loc} to location(23.040052452876346, 39.29507158212079, 14.394218416496916, world("world"), 86.558136, -45.623573) + set {_v} to vector(7.114755290046221, 1.737584079576708, -1.6158770773672382) + assert ({_test.loc} offset by {_v} using local axes) is {_expected.loc} with "returned wrong location - 4" + + set {_test.loc} to location(-19.67380126817804, 54.49320309887087, 1.4868926984678503, world("world"), 172.25856, -62.71486) + set {_expected.loc} to location(-13.3405063997088, 58.03254001865542, -0.41375398808610475, world("world"), 172.25856, -62.71486) + set {_v} to vector(-6.5315963180124434, 0.7069113825034361, 3.617806771486472) + assert ({_test.loc} offset by {_v} using local axes) is {_expected.loc} with "returned wrong location - 5"