diff --git a/src/main/java/ch/njol/skript/expressions/ExprArmorstandPose.java b/src/main/java/ch/njol/skript/expressions/ExprArmorstandPose.java new file mode 100644 index 00000000000..11beb8921e9 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprArmorstandPose.java @@ -0,0 +1,237 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +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.SkriptParser; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.Direction; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.bukkit.util.EulerAngle; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; + +@Name("Armor stand pose") +@Description("The pose of a specific part of an armor stand.") +@Examples( { + "spawn an armor stand at player:", + " set the left arm pose of the armor stand to east", +}) +@Since("INSERT VERSION") +public class ExprArmorstandPose extends SimpleExpression { + + static { + Skript.registerExpression(ExprArmorstandPose.class, Direction.class, ExpressionType.PROPERTY, + "(0¦left arm|1¦right arm|2¦left leg|3¦right leg|4¦body|5¦head) pose of %livingentities%"); + } + + private Expression entity; + + private ArmorStandPose pose; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { + entity = (Expression) exprs[0]; + pose = ArmorStandPose.getById(parseResult.mark); + return true; + } + + @Override + protected @Nullable Direction[] get(Event event) { + for (Entity entity : this.entity.getArray(event)) { + if(!(entity instanceof ArmorStand)) + continue; + ArmorStand armorStand = (ArmorStand) entity; + EulerAngle eulerAngle = null; + switch (pose) { + case LEFT_ARM: + eulerAngle = armorStand.getLeftArmPose(); + break; + case RIGHT_ARM: + eulerAngle = armorStand.getRightArmPose(); + break; + case LEFT_LEG: + eulerAngle = armorStand.getLeftLegPose(); + break; + case RIGHT_LEG: + eulerAngle = armorStand.getRightLegPose(); + break; + case BODY: + eulerAngle = armorStand.getBodyPose(); + break; + case HEAD: + eulerAngle = armorStand.getHeadPose(); + break; + } + + if(eulerAngle == null) + continue; + return CollectionUtils.array(convertEulerAngleToDirection(eulerAngle, getYawOffset(armorStand))); + } + return null; + } + + @Override + public @Nullable Class[] acceptChange(Changer.ChangeMode mode) { + if(mode == Changer.ChangeMode.SET) { + return CollectionUtils.array(Direction.class, Vector.class); + } + return null; + } + + @Override + public void change(Event event, @Nullable Object[] delta, Changer.ChangeMode mode) { + if(delta[0] == null || (!(delta[0] instanceof Direction) && !(delta[0] instanceof Vector))) + return; + for (Entity entity : entity.getArray(event)) { + if(!(entity instanceof ArmorStand)) + continue; + ArmorStand armorStand = (ArmorStand) entity; + if(delta[0] instanceof Direction) + setPose(armorStand, (Direction) delta[0]); + else if(delta[0] instanceof Vector) + setPose(armorStand, (Vector) delta[0]); + } + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return Direction.class; + } + + private void setPose(ArmorStand armorStand, Vector vector) { + switch (pose) { + case LEFT_ARM: + armorStand.setLeftArmPose(convertDirectionToEulerAngle(vector, getYawOffset(armorStand))); + break; + case RIGHT_ARM: + armorStand.setRightArmPose(convertDirectionToEulerAngle(vector, getYawOffset(armorStand))); + break; + case LEFT_LEG: + armorStand.setLeftLegPose(convertDirectionToEulerAngle(vector, getYawOffset(armorStand))); + break; + case RIGHT_LEG: + armorStand.setRightLegPose(convertDirectionToEulerAngle(vector, getYawOffset(armorStand))); + break; + case BODY: + armorStand.setBodyPose(convertDirectionToEulerAngle(vector, getYawOffset(armorStand))); + break; + case HEAD: + armorStand.setHeadPose(convertDirectionToEulerAngle(vector, getYawOffset(armorStand))); + break; + } + } + + private void setPose(ArmorStand armorStand, Direction direction) { + setPose(armorStand, direction.getDirection()); + } + + /** + * @param vector Direction to convert to EulerAngle + * @param yawOffset The yaw adjustment (in degrees) to correct for the armor stand rotation. + * @return The euler angle representing the direction + */ + private @NotNull EulerAngle convertDirectionToEulerAngle(Vector vector, double yawOffset) { + double x = vector.getX(); + double z = vector.getZ(); + double y = vector.getY(); + + double yaw = Math.atan2(-x, z); + double pitch = Math.atan2(y, Math.sqrt(x * x + z * z)); + + return new EulerAngle(yaw + Math.toRadians(yawOffset), 0 , pitch + Math.toRadians(90)); + } + + /** + * @param eulerAngle EulerAngle to convert to Direction + * @param yawOffset The yaw adjustment (in degrees) to correct for the armor stand rotation. + * @return The direction (from normalized vector) representing the EulerAngle + */ + private @NotNull Direction convertEulerAngleToDirection(EulerAngle eulerAngle, double yawOffset) { + double yaw = eulerAngle.getX(); + double pitch = eulerAngle.getZ(); + + //Remove offset + yaw -= Math.toRadians(yawOffset); + pitch -= Math.toRadians(90); + + double x = -Math.sin(yaw) * Math.cos(pitch); + double y = Math.sin(pitch); + double z = Math.cos(yaw) * Math.cos(pitch); + + return new Direction(new Vector(x, y, z)); + } + + /** + * @param armorStand The armor stand to get the yaw offset from + * @return The yaw adjustment (in degrees) to correct for the armor stand rotation. + */ + private double getYawOffset(ArmorStand armorStand) { + double yawOffset = Math.round(armorStand.getLocation().getYaw() / 45) * 45; + boolean invert = yawOffset < 135; + if(yawOffset > 135) + yawOffset -= 90; + else if(yawOffset < 135) + yawOffset += 90; + return invert ? -yawOffset : yawOffset; + } + + private enum ArmorStandPose { + LEFT_ARM(0), RIGHT_ARM(1), LEFT_LEG(2), RIGHT_LEG(3), BODY(4), HEAD(5); + + private final int id; + + ArmorStandPose(int id) { + this.id = id; + } + + public static @NotNull ArmorStandPose getById(int id) { + for (ArmorStandPose pose : values()) { + if (pose.id == id) { + return pose; + } + } + //Default to left arm if no valid id is found + return ArmorStandPose.LEFT_ARM; + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "armor stand pose expression: " + pose + " of " + entity.toString(event, debug); + } + +} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprArmorstandPose.sk b/src/test/skript/tests/syntaxes/expressions/ExprArmorstandPose.sk new file mode 100644 index 00000000000..758056fe29d --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprArmorstandPose.sk @@ -0,0 +1,93 @@ +test "armorstand pose": + summon armor stand at spawn of world "world": + set {_armorstand} to event-armor stand + + set {_dir} to east + set left arm pose of {_armorstand} to {_dir} + set {_dir2} to left arm pose of {_armorstand} + + assert normalized vector from {_dir} is vector from {_dir2} with "Armor stand left arm pose doesn't match (set with direction))" + + set {_dir} to vector 1,0,1 + set right arm pose of {_armorstand} to {_dir} + set {_dir2} to right arm pose of {_armorstand} + + assert normalized {_dir} is vector from {_dir2} with "Armor stand right arm pose doesn't match (set with vector)" + + set {_dir} to west + set left leg pose of {_armorstand} to {_dir} + set {_dir2} to left leg pose of {_armorstand} + + assert normalized vector from {_dir} is vector from {_dir2} with "Armor stand left leg pose doesn't match (set with direction))" + + set {_dir} to north west + set right leg pose of {_armorstand} to {_dir} + set {_dir2} to right leg pose of {_armorstand} + + assert normalized vector from {_dir} is vector from {_dir2} with "Armor stand right leg pose doesn't match (set with direction))" + + set {_dir} to south east + set body pose of {_armorstand} to {_dir} + set {_dir2} to body pose of {_armorstand} + + assert normalized vector from {_dir} is vector from {_dir2} with "Armor stand body pose doesn't match (set with direction))" + + set {_dir} to up + set head pose of {_armorstand} to {_dir} + set {_dir2} to head pose of {_armorstand} + + assert normalized vector from {_dir} is vector from {_dir2} with "Armor stand head pose doesn't match (set with direction))" + + kill {_armorstand} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + summon armor stand at spawn of world "world": + set {_armorstand} to event-armor stand + + set {_dir} to east + set left arm pose of {_armorstand} to {_dir} + set {_dir2} to left arm pose of {_armorstand} + + assert normalized vector from {_dir} is vector from {_dir2} with "Armor stand left arm pose doesn't match (set with direction))" + + set {_dir} to vector 1,0,1 + set right arm pose of {_armorstand} to {_dir} + set {_dir2} to right arm pose of {_armorstand} + + # clean up first in case assert fails + kill {_armorstand} + + assert normalized {_dir} is vector from {_dir2} with "Armor stand right arm pose doesn't match (set with vector)" + + +