diff --git a/src/main/java/ch/njol/skript/classes/ClassInfo.java b/src/main/java/ch/njol/skript/classes/ClassInfo.java index da8dfd1c50e..2852a33ac26 100644 --- a/src/main/java/ch/njol/skript/classes/ClassInfo.java +++ b/src/main/java/ch/njol/skript/classes/ClassInfo.java @@ -17,7 +17,7 @@ import org.skriptlang.skript.addon.SkriptAddon; import org.skriptlang.skript.lang.properties.Property; import org.skriptlang.skript.lang.properties.Property.PropertyInfo; -import org.skriptlang.skript.lang.properties.PropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.PropertyHandler; import java.util.*; import java.util.function.Supplier; diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java index 9794bfc2e22..551f2935b17 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -50,12 +50,11 @@ import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.util.CachedServerIcon; -import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.bukkit.base.types.*; import org.skriptlang.skript.bukkit.base.types.EntityClassInfo.EntityChanger; import org.skriptlang.skript.lang.properties.Property; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; import java.io.StreamCorruptedException; import java.util.Arrays; @@ -164,172 +163,8 @@ protected boolean canBeInstantiated() { } }).cloner(BlockData::clone)); - Classes.registerClass(new ClassInfo<>(Location.class, "location") - .user("locations?") - .name("Location") - .description("A location in a world. Locations are world-specific and even store a direction, " + - "e.g. if you save a location and later teleport to it you will face the exact same direction you did when you saved the location.") - .usage("") - .examples("teleport player to location at 0, 69, 0", - "set {home::%uuid of player%} to location of the player") - .since("1.0") - .defaultExpression(new EventValueExpression<>(Location.class)) - .parser(new Parser() { - @Override - @Nullable - public Location parse(final String s, final ParseContext context) { - return null; - } - - @Override - public boolean canParse(final ParseContext context) { - return false; - } - - @Override - public String toString(final Location l, final int flags) { - String worldPart = l.getWorld() == null ? "" : " in '" + l.getWorld().getName() + "'"; // Safety: getWorld is marked as Nullable by spigot - return "x: " + Skript.toString(l.getX()) + ", y: " + Skript.toString(l.getY()) + ", z: " + Skript.toString(l.getZ()) + ", yaw: " + Skript.toString(l.getYaw()) + ", pitch: " + Skript.toString(l.getPitch()) + worldPart; - } - - @Override - public String toVariableNameString(final Location l) { - return l.getWorld().getName() + ":" + l.getX() + "," + l.getY() + "," + l.getZ(); - } - - @Override - public String getDebugMessage(final Location l) { - return "(" + l.getWorld().getName() + ":" + l.getX() + "," + l.getY() + "," + l.getZ() + "|yaw=" + l.getYaw() + "/pitch=" + l.getPitch() + ")"; - } - }).serializer(new Serializer() { - @Override - public Fields serialize(Location location) { - Fields fields = new Fields(); - World world = null; - try { - world = location.getWorld(); - } catch (IllegalArgumentException exception) { - Skript.warning("A location failed to serialize with its defined world, as the world was unloaded."); - } - fields.putObject("world", world); - fields.putPrimitive("x", location.getX()); - fields.putPrimitive("y", location.getY()); - fields.putPrimitive("z", location.getZ()); - fields.putPrimitive("yaw", location.getYaw()); - fields.putPrimitive("pitch", location.getPitch()); - return fields; - } - - @Override - public void deserialize(final Location o, final Fields f) { - assert false; - } - - @Override - public Location deserialize(final Fields f) throws StreamCorruptedException { - return new Location(f.getObject("world", World.class), - f.getPrimitive("x", double.class), f.getPrimitive("y", double.class), f.getPrimitive("z", double.class), - f.getPrimitive("yaw", float.class), f.getPrimitive("pitch", float.class)); - } - - @Override - public boolean canBeInstantiated() { - return false; // no nullary constructor - also, saving the location manually prevents errors should Location ever be changed - } - - @Override - public boolean mustSyncDeserialization() { - return true; - } - - // return l.getWorld().getName() + ":" + l.getX() + "," + l.getY() + "," + l.getZ() + "|" + l.getYaw() + "/" + l.getPitch(); - @Override - @Nullable - public Location deserialize(final String s) { - final String[] split = s.split("[:,|/]"); - if (split.length != 6) - return null; - final World w = Bukkit.getWorld(split[0]); - if (w == null) - return null; - try { - final double[] l = new double[5]; - for (int i = 0; i < 5; i++) - l[i] = Double.parseDouble(split[i + 1]); - return new Location(w, l[0], l[1], l[2], (float) l[3], (float) l[4]); - } catch (final NumberFormatException e) { - return null; - } - } - }) - .cloner(Location::clone)); - - Classes.registerClass(new ClassInfo<>(Vector.class, "vector") - .user("vectors?") - .name("Vector") - .description("Vector is a collection of numbers. In Minecraft, 3D vectors are used to express velocities of entities.") - .usage("vector(x, y, z)") - .examples("") - .since("2.2-dev23") - .defaultExpression(new EventValueExpression<>(Vector.class)) - .parser(new Parser() { - @Override - @Nullable - public Vector parse(final String s, final ParseContext context) { - return null; - } - - @Override - public boolean canParse(final ParseContext context) { - return false; - } - - @Override - public String toString(final Vector vec, final int flags) { - return "x: " + Skript.toString(vec.getX()) + ", y: " + Skript.toString(vec.getY()) + ", z: " + Skript.toString(vec.getZ()); - } - - @Override - public String toVariableNameString(final Vector vec) { - return "vector:" + vec.getX() + "," + vec.getY() + "," + vec.getZ(); - } - - @Override - public String getDebugMessage(final Vector vec) { - return "(" + vec.getX() + "," + vec.getY() + "," + vec.getZ() + ")"; - } - }) - .serializer(new Serializer() { - @Override - public Fields serialize(Vector o) { - Fields f = new Fields(); - f.putPrimitive("x", o.getX()); - f.putPrimitive("y", o.getY()); - f.putPrimitive("z", o.getZ()); - return f; - } - - @Override - public void deserialize(Vector o, Fields f) { - assert false; - } - - @Override - public Vector deserialize(final Fields f) throws StreamCorruptedException { - return new Vector(f.getPrimitive("x", double.class), f.getPrimitive("y", double.class), f.getPrimitive("z", double.class)); - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - - @Override - protected boolean canBeInstantiated() { - return false; - } - }) - .cloner(Vector::clone)); + Classes.registerClass(new LocationClassInfo()); + Classes.registerClass(new VectorClassInfo()); Classes.registerClass(new ClassInfo<>(World.class, "world") .user("worlds?") diff --git a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java index 63f3e5accf6..d97924fdba1 100644 --- a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java @@ -5,7 +5,6 @@ import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.classes.Parser; import ch.njol.skript.classes.Serializer; -import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.lang.VariableString; import ch.njol.skript.lang.util.SimpleLiteral; @@ -17,10 +16,10 @@ import ch.njol.yggdrasil.Fields; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; -import org.joml.Quaternionf; +import org.skriptlang.skript.common.types.QuaternionClassInfo; import org.skriptlang.skript.lang.properties.Property; -import org.skriptlang.skript.lang.properties.PropertyHandler.ConditionPropertyHandler; -import org.skriptlang.skript.lang.properties.PropertyHandler.ContainsHandler; +import org.skriptlang.skript.lang.properties.handlers.ContainsHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ConditionPropertyHandler; import java.io.StreamCorruptedException; import java.util.UUID; @@ -326,37 +325,8 @@ public Class[] elementTypes() { ConditionPropertyHandler.of(String::isEmpty))); // joml type - for display entities - if (Skript.classExists("org.joml.Quaternionf")) - Classes.registerClass(new ClassInfo<>(Quaternionf.class, "quaternion") - .user("quaternionf?s?") - .name("Quaternion") - .description("Quaternions are four dimensional vectors, often used for representing rotations.") - .since("2.10") - .parser(new Parser<>() { - public boolean canParse(ParseContext context) { - return false; - } - - @Override - public String toString(Quaternionf quaternion, int flags) { - return "w:" + Skript.toString(quaternion.w()) + ", x:" + Skript.toString(quaternion.x()) + ", y:" + Skript.toString(quaternion.y()) + ", z:" + Skript.toString(quaternion.z()); - } + Classes.registerClass(new QuaternionClassInfo()); - @Override - public String toVariableNameString(Quaternionf quaternion) { - return quaternion.w() + "," + quaternion.x() + "," + quaternion.y() + "," + quaternion.z(); - } - }) - .defaultExpression(new EventValueExpression<>(Quaternionf.class)) - .cloner(quaternion -> { - try { - // Implements cloneable, but doesn't return a Quaternionf. - // org.joml improper override. Returns Object. - return (Quaternionf) quaternion.clone(); - } catch (CloneNotSupportedException e) { - return null; - } - })); Classes.registerClass(new ClassInfo<>(UUID.class, "uuid") .user("uuids?") diff --git a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java index 87df08749ad..cdcfa97e3d1 100644 --- a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java @@ -28,10 +28,10 @@ import org.skriptlang.skript.common.types.QueueClassInfo; import org.skriptlang.skript.common.types.ScriptClassInfo; import org.skriptlang.skript.lang.properties.Property; -import org.skriptlang.skript.lang.properties.PropertyHandler.ConditionPropertyHandler; -import org.skriptlang.skript.lang.properties.PropertyHandler.ContainsHandler; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; -import org.skriptlang.skript.lang.properties.PropertyHandler.TypedValuePropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.ContainsHandler; +import org.skriptlang.skript.lang.properties.handlers.TypedValueHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ConditionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; import org.skriptlang.skript.util.Executable; import java.io.File; @@ -583,7 +583,7 @@ public String toVariableNameString(Node node) { .property(Property.TYPED_VALUE, "The value of the node, if it is an entry node, as text.", Skript.instance(), - new TypedValuePropertyHandler() { + new TypedValueHandler() { @Override public @Nullable String convert(Node propertyHolder) { @@ -751,7 +751,7 @@ public void change(AnyAmount named, Object @Nullable [] delta, ChangeMode mode) .property(Property.TYPED_VALUE, "The value of something. Can be set.", Skript.instance(), - new TypedValuePropertyHandler() { + new TypedValueHandler() { @Override public Object convert(AnyValued propertyHolder) { return propertyHolder.value(); diff --git a/src/main/java/ch/njol/skript/expressions/ExprCoordinate.java b/src/main/java/ch/njol/skript/expressions/ExprCoordinate.java index 9f4380e0a7a..63f58e7469d 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprCoordinate.java +++ b/src/main/java/ch/njol/skript/expressions/ExprCoordinate.java @@ -1,5 +1,6 @@ package ch.njol.skript.expressions; +import ch.njol.skript.SkriptConfig; import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; @@ -27,7 +28,8 @@ public class ExprCoordinate extends SimplePropertyExpression { static { - register(ExprCoordinate.class, Number.class, "(0¦x|1¦y|2¦z)(-| )(coord[inate]|pos[ition]|loc[ation])[s]", "locations"); + if (!SkriptConfig.useTypeProperties.value()) + register(ExprCoordinate.class, Number.class, "(0¦x|1¦y|2¦z)(-| )(coord[inate]|pos[ition]|loc[ation])[s]", "locations"); } private final static char[] axes = {'x', 'y', 'z'}; diff --git a/src/main/java/ch/njol/skript/expressions/ExprXYZComponent.java b/src/main/java/ch/njol/skript/expressions/ExprXYZComponent.java index 30930124c05..f6ec47c964b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprXYZComponent.java +++ b/src/main/java/ch/njol/skript/expressions/ExprXYZComponent.java @@ -1,6 +1,7 @@ package ch.njol.skript.expressions; import ch.njol.skript.Skript; +import ch.njol.skript.SkriptConfig; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.Changer.ChangerUtils; import ch.njol.skript.doc.Description; @@ -48,7 +49,8 @@ public class ExprXYZComponent extends SimplePropertyExpression { String types = "vectors"; if (IS_RUNNING_1194) types += "/quaternions"; - register(ExprXYZComponent.class, Number.class, "[vector|quaternion] (:w|:x|:y|:z) [component[s]]", types); + if (!SkriptConfig.useTypeProperties.value()) + register(ExprXYZComponent.class, Number.class, "[vector|quaternion] (:w|:x|:y|:z) [component[s]]", types); } private enum Axis { diff --git a/src/main/java/org/skriptlang/skript/bukkit/base/types/BlockClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/base/types/BlockClassInfo.java index 378013a9f2e..6f7429288f9 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/base/types/BlockClassInfo.java +++ b/src/main/java/org/skriptlang/skript/bukkit/base/types/BlockClassInfo.java @@ -25,7 +25,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.properties.Property; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; import java.io.StreamCorruptedException; import java.util.Arrays; diff --git a/src/main/java/org/skriptlang/skript/bukkit/base/types/EntityClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/base/types/EntityClassInfo.java index 0374b9b35ec..c2b948119bd 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/base/types/EntityClassInfo.java +++ b/src/main/java/org/skriptlang/skript/bukkit/base/types/EntityClassInfo.java @@ -25,7 +25,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.properties.Property; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; import java.util.Locale; import java.util.UUID; diff --git a/src/main/java/org/skriptlang/skript/bukkit/base/types/InventoryClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/base/types/InventoryClassInfo.java index 7e5134bd49c..0c70ee03681 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/base/types/InventoryClassInfo.java +++ b/src/main/java/org/skriptlang/skript/bukkit/base/types/InventoryClassInfo.java @@ -30,9 +30,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.properties.Property; -import org.skriptlang.skript.lang.properties.PropertyHandler; -import org.skriptlang.skript.lang.properties.PropertyHandler.ConditionPropertyHandler; -import org.skriptlang.skript.lang.properties.PropertyHandler.ContainsHandler; +import org.skriptlang.skript.lang.properties.handlers.ContainsHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ConditionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; import java.util.ArrayList; import java.util.List; @@ -236,7 +236,7 @@ public Class[] elementTypes() { // } - private static class InventoryNameHandler implements PropertyHandler.ExpressionPropertyHandler { + private static class InventoryNameHandler implements ExpressionPropertyHandler { // private static @Nullable BungeeComponentSerializer serializer = null; diff --git a/src/main/java/org/skriptlang/skript/bukkit/base/types/ItemStackClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/base/types/ItemStackClassInfo.java index 12497ecb1b0..9dcbad84a4d 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/base/types/ItemStackClassInfo.java +++ b/src/main/java/org/skriptlang/skript/bukkit/base/types/ItemStackClassInfo.java @@ -16,7 +16,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.properties.Property; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; import java.util.Arrays; import java.util.Map; diff --git a/src/main/java/org/skriptlang/skript/bukkit/base/types/ItemTypeClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/base/types/ItemTypeClassInfo.java index 0bec4d37bf0..2ef5d5eebd4 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/base/types/ItemTypeClassInfo.java +++ b/src/main/java/org/skriptlang/skript/bukkit/base/types/ItemTypeClassInfo.java @@ -16,7 +16,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.properties.Property; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; import java.util.Arrays; diff --git a/src/main/java/org/skriptlang/skript/bukkit/base/types/LocationClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/base/types/LocationClassInfo.java new file mode 100644 index 00000000000..d61bc7e8960 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/base/types/LocationClassInfo.java @@ -0,0 +1,209 @@ +package org.skriptlang.skript.bukkit.base.types; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.classes.Serializer; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.ParseContext; +import ch.njol.yggdrasil.Fields; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.properties.Property; +import org.skriptlang.skript.lang.properties.handlers.WXYZHandler; +import org.skriptlang.skript.lang.properties.handlers.base.PropertyHandler; + +import java.io.StreamCorruptedException; + +@ApiStatus.Internal +public class LocationClassInfo extends ClassInfo { + + public LocationClassInfo() { + super(Location.class, "location"); + this.user("locations?") + .name("Location") + .description("A location in a world. Locations are world-specific and even store a direction, " + + "e.g. if you save a location and later teleport to it you will face the exact same direction you did when you saved the location.") + .usage("") + .examples("teleport player to location at 0, 69, 0", + "set {home::%uuid of player%} to location of the player") + .since("1.0") + .defaultExpression(new EventValueExpression<>(Location.class)) + .parser(new LocationParser()) + .serializer(new LocationSerializer()) + .cloner(Location::clone) + .property(Property.WXYZ, + "The X, Y, or Z coordinate of the location.", + Skript.instance(), + new LocationWXYZHandler()); + + } + + private static class LocationWXYZHandler extends WXYZHandler { + // + @Override + public PropertyHandler newInstance() { + var instance = new LocationWXYZHandler(); + instance.axis(axis); + return instance; + } + + @Override + public @Nullable Double convert(Location propertyHolder) { + return switch (axis) { + case X -> propertyHolder.getX(); + case Y -> propertyHolder.getY(); + case Z -> propertyHolder.getZ(); + default -> null; + }; + } + + @Override + public boolean supportsAxis(Axis axis) { + return axis != Axis.W; + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case ADD, SET, REMOVE -> new Class[]{Float.class}; + default -> null; + }; + } + + @Override + public void change(Location location, Object @Nullable [] delta, ChangeMode mode) { + assert delta != null; + float value = ((Float) delta[0]); + if (axis == Axis.W) + return; + switch (mode) { + case REMOVE: + value = -value; + //$FALL-THROUGH$ + case ADD: + switch (axis) { + case X -> location.setX(location.getX() + value); + case Y -> location.setY(location.getY() + value); + case Z -> location.setZ(location.getZ() + value); + } + break; + case SET: + switch (axis) { + case X -> location.setX(value); + case Y -> location.setY(value); + case Z -> location.setZ(value); + } + break; + default: + assert false; + } + } + + @Override + public @NotNull Class returnType() { + return Double.class; + } + + @Override + public boolean requiresSourceExprChange() { + return true; + } + // + } + + private static class LocationParser extends Parser { + // + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(Location loc, int flags) { + String worldPart = loc.getWorld() == null ? "" : " in '" + loc.getWorld().getName() + "'"; // Safety: getWorld is marked as Nullable by spigot + return "x: " + Skript.toString(loc.getX()) + ", y: " + Skript.toString(loc.getY()) + ", z: " + Skript.toString(loc.getZ()) + ", yaw: " + Skript.toString(loc.getYaw()) + ", pitch: " + Skript.toString(loc.getPitch()) + worldPart; + } + + @Override + public String toVariableNameString(Location loc) { + return loc.getWorld().getName() + ":" + loc.getX() + "," + loc.getY() + "," + loc.getZ(); + } + + @Override + public String getDebugMessage(Location loc) { + return "(" + loc.getWorld().getName() + ":" + loc.getX() + "," + loc.getY() + "," + loc.getZ() + "|yaw=" + loc.getYaw() + "/pitch=" + loc.getPitch() + ")"; + } + // + } + + private static class LocationSerializer extends Serializer { + // + @Override + public Fields serialize(Location location) { + Fields fields = new Fields(); + World world = null; + try { + world = location.getWorld(); + } catch (IllegalArgumentException exception) { + Skript.warning("A location failed to serialize with its defined world, as the world was unloaded."); + } + fields.putObject("world", world); + fields.putPrimitive("x", location.getX()); + fields.putPrimitive("y", location.getY()); + fields.putPrimitive("z", location.getZ()); + fields.putPrimitive("yaw", location.getYaw()); + fields.putPrimitive("pitch", location.getPitch()); + return fields; + } + + @Override + public void deserialize(Location o, Fields f) { + assert false; + } + + @Override + public Location deserialize(Fields f) throws StreamCorruptedException { + return new Location(f.getObject("world", World.class), + f.getPrimitive("x", double.class), f.getPrimitive("y", double.class), f.getPrimitive("z", double.class), + f.getPrimitive("yaw", float.class), f.getPrimitive("pitch", float.class)); + } + + @Override + public boolean canBeInstantiated() { + return false; // no nullary constructor - also, saving the location manually prevents errors should Location ever be changed + } + + @Override + public boolean mustSyncDeserialization() { + return true; + } + + // return l.getWorld().getName() + ":" + l.getX() + "," + l.getY() + "," + l.getZ() + "|" + l.getYaw() + "/" + l.getPitch(); + @Override + @Nullable + public Location deserialize(String input) { + final String[] split = input.split("[:,|/]"); + if (split.length != 6) + return null; + final World w = Bukkit.getWorld(split[0]); + if (w == null) + return null; + try { + final double[] l = new double[5]; + for (int i = 0; i < 5; i++) + l[i] = Double.parseDouble(split[i + 1]); + return new Location(w, l[0], l[1], l[2], (float) l[3], (float) l[4]); + } catch (final NumberFormatException e) { + return null; + } + } + // + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/base/types/NameableClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/base/types/NameableClassInfo.java index dafeb42a602..31f67295ccc 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/base/types/NameableClassInfo.java +++ b/src/main/java/org/skriptlang/skript/bukkit/base/types/NameableClassInfo.java @@ -10,7 +10,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.properties.Property; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; @ApiStatus.Internal public class NameableClassInfo extends ClassInfo { diff --git a/src/main/java/org/skriptlang/skript/bukkit/base/types/OfflinePlayerClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/base/types/OfflinePlayerClassInfo.java index 00256c679d7..229c0dc8b75 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/base/types/OfflinePlayerClassInfo.java +++ b/src/main/java/org/skriptlang/skript/bukkit/base/types/OfflinePlayerClassInfo.java @@ -15,7 +15,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.properties.Property; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; import java.io.StreamCorruptedException; import java.util.UUID; diff --git a/src/main/java/org/skriptlang/skript/bukkit/base/types/PlayerClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/base/types/PlayerClassInfo.java index 000e9fcf9fd..958fa030f22 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/base/types/PlayerClassInfo.java +++ b/src/main/java/org/skriptlang/skript/bukkit/base/types/PlayerClassInfo.java @@ -21,7 +21,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.properties.Property; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/org/skriptlang/skript/bukkit/base/types/SlotClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/base/types/SlotClassInfo.java index ed6f5c72caf..3ed86d3b6fb 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/base/types/SlotClassInfo.java +++ b/src/main/java/org/skriptlang/skript/bukkit/base/types/SlotClassInfo.java @@ -18,8 +18,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.properties.Property; -import org.skriptlang.skript.lang.properties.PropertyHandler.ConditionPropertyHandler; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ConditionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; @ApiStatus.Internal public class SlotClassInfo extends ClassInfo { diff --git a/src/main/java/org/skriptlang/skript/bukkit/base/types/VectorClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/base/types/VectorClassInfo.java new file mode 100644 index 00000000000..74ca060ce7f --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/base/types/VectorClassInfo.java @@ -0,0 +1,173 @@ +package org.skriptlang.skript.bukkit.base.types; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.classes.Serializer; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.ParseContext; +import ch.njol.yggdrasil.Fields; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.properties.Property; +import org.skriptlang.skript.lang.properties.handlers.WXYZHandler; +import org.skriptlang.skript.lang.properties.handlers.base.PropertyHandler; + +import java.io.StreamCorruptedException; + +import static ch.njol.skript.classes.Changer.*; + +@ApiStatus.Internal +public class VectorClassInfo extends ClassInfo { + + public VectorClassInfo() { + super(Vector.class, "vector"); + this.user("vectors?") + .name("Vector") + .description("Vector is a collection of numbers. In Minecraft, 3D vectors are used to express velocities of entities.") + .usage("vector(x, y, z)") + .examples("") + .since("2.2-dev23") + .defaultExpression(new EventValueExpression<>(Vector.class)) + .parser(new VectorParser()) + .serializer(new VectorSerializer()) + .cloner(Vector::clone) + .property(Property.WXYZ, + "X, Y, or Z component of the vector.", + Skript.instance(), + new VectorWXYZHandler()); + } + + private static class VectorWXYZHandler extends WXYZHandler { + // + @Override + public PropertyHandler newInstance() { + var instance = new VectorWXYZHandler(); + instance.axis(axis); + return instance; + } + + @Override + public @Nullable Double convert(Vector propertyHolder) { + return switch (axis) { + case X -> propertyHolder.getX(); + case Y -> propertyHolder.getY(); + case Z -> propertyHolder.getZ(); + default -> null; + }; + } + + @Override + public boolean supportsAxis(Axis axis) { + return axis != Axis.W; + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case ADD, SET, REMOVE -> new Class[]{Float.class}; + default -> null; + }; + } + + @Override + public void change(Vector vector, Object @Nullable [] delta, ChangeMode mode) { + assert delta != null; + float value = ((Float) delta[0]); + if (axis == Axis.W) + return; + switch (mode) { + case REMOVE: + value = -value; + //$FALL-THROUGH$ + case ADD: + switch (axis) { + case X -> vector.setX(vector.getX() + value); + case Y -> vector.setY(vector.getY() + value); + case Z -> vector.setZ(vector.getZ() + value); + } + break; + case SET: + switch (axis) { + case X -> vector.setX(value); + case Y -> vector.setY(value); + case Z -> vector.setZ(value); + } + break; + default: + assert false; + } + } + + @Override + public @NotNull Class returnType() { + return Double.class; + } + + @Override + public boolean requiresSourceExprChange() { + return true; + } + // + } + + private static class VectorParser extends Parser { + // + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(Vector vec, int flags) { + return "x: " + Skript.toString(vec.getX()) + ", y: " + Skript.toString(vec.getY()) + ", z: " + Skript.toString(vec.getZ()); + } + + @Override + public String toVariableNameString(Vector vec) { + return "vector:" + vec.getX() + "," + vec.getY() + "," + vec.getZ(); + } + + @Override + public String getDebugMessage(Vector vec) { + return "(" + vec.getX() + "," + vec.getY() + "," + vec.getZ() + ")"; + } + // + } + + private static class VectorSerializer extends Serializer { + // + @Override + public Fields serialize(Vector o) { + Fields f = new Fields(); + f.putPrimitive("x", o.getX()); + f.putPrimitive("y", o.getY()); + f.putPrimitive("z", o.getZ()); + return f; + } + + @Override + public void deserialize(Vector o, Fields f) { + assert false; + } + + @Override + public Vector deserialize(Fields f) throws StreamCorruptedException { + return new Vector(f.getPrimitive("x", double.class), f.getPrimitive("y", double.class), f.getPrimitive("z", double.class)); + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } + + @Override + protected boolean canBeInstantiated() { + return false; + } + // + } + +} diff --git a/src/main/java/org/skriptlang/skript/common/properties/conditions/PropCondContains.java b/src/main/java/org/skriptlang/skript/common/properties/conditions/PropCondContains.java index 398ee252016..72ec7c8b5e6 100644 --- a/src/main/java/org/skriptlang/skript/common/properties/conditions/PropCondContains.java +++ b/src/main/java/org/skriptlang/skript/common/properties/conditions/PropCondContains.java @@ -22,8 +22,8 @@ import org.skriptlang.skript.lang.converter.Converters; import org.skriptlang.skript.lang.properties.Property; import org.skriptlang.skript.lang.properties.PropertyBaseSyntax; -import org.skriptlang.skript.lang.properties.PropertyHandler.ContainsHandler; import org.skriptlang.skript.lang.properties.PropertyMap; +import org.skriptlang.skript.lang.properties.handlers.ContainsHandler; import java.util.Arrays; import java.util.Map; diff --git a/src/main/java/org/skriptlang/skript/common/properties/conditions/PropCondIsEmpty.java b/src/main/java/org/skriptlang/skript/common/properties/conditions/PropCondIsEmpty.java index 3f89b499679..b7e88101766 100644 --- a/src/main/java/org/skriptlang/skript/common/properties/conditions/PropCondIsEmpty.java +++ b/src/main/java/org/skriptlang/skript/common/properties/conditions/PropCondIsEmpty.java @@ -4,7 +4,7 @@ import org.jetbrains.annotations.NotNull; import org.skriptlang.skript.lang.properties.Property; import org.skriptlang.skript.lang.properties.PropertyBaseCondition; -import org.skriptlang.skript.lang.properties.PropertyHandler.ConditionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ConditionPropertyHandler; @Name("Is Empty") @Description("Checks whether something is empty.") diff --git a/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprAmount.java b/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprAmount.java index b5222621563..12aee92e4d7 100644 --- a/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprAmount.java +++ b/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprAmount.java @@ -1,6 +1,5 @@ package org.skriptlang.skript.common.properties.expressions; -import ch.njol.skript.SkriptConfig; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.*; import ch.njol.skript.lang.Expression; @@ -14,7 +13,7 @@ import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.properties.Property; import org.skriptlang.skript.lang.properties.PropertyBaseExpression; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; @Name("Amount") @Description(""" diff --git a/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprCustomName.java b/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprCustomName.java index 5fa83b50256..d95c104d04c 100644 --- a/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprCustomName.java +++ b/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprCustomName.java @@ -3,7 +3,7 @@ import ch.njol.skript.doc.*; import org.skriptlang.skript.lang.properties.Property; import org.skriptlang.skript.lang.properties.PropertyBaseExpression; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; @Name("Display Name") @Description({ diff --git a/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprName.java b/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprName.java index f6bf4a89c80..8bdb83c131e 100644 --- a/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprName.java +++ b/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprName.java @@ -3,7 +3,7 @@ import ch.njol.skript.doc.*; import org.skriptlang.skript.lang.properties.Property; import org.skriptlang.skript.lang.properties.PropertyBaseExpression; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; @Name("Name") @Description({ diff --git a/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprNumber.java b/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprNumber.java index efd83a10503..a531d2fcd82 100644 --- a/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprNumber.java +++ b/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprNumber.java @@ -12,7 +12,7 @@ import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.properties.Property; import org.skriptlang.skript.lang.properties.PropertyBaseExpression; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; @Name("Number Of") @Description(""" diff --git a/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprSize.java b/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprSize.java index 8ab90b1f05d..6eeb2c63ce3 100644 --- a/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprSize.java +++ b/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprSize.java @@ -12,7 +12,7 @@ import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.properties.Property; import org.skriptlang.skript.lang.properties.PropertyBaseExpression; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; @Name("Size") @Description(""" diff --git a/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprValueOf.java b/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprValueOf.java index 98845974103..b940e1b54d5 100644 --- a/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprValueOf.java +++ b/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprValueOf.java @@ -15,7 +15,7 @@ import org.skriptlang.skript.lang.properties.Property; import org.skriptlang.skript.lang.properties.PropertyBaseExpression; import org.skriptlang.skript.lang.properties.PropertyBaseSyntax; -import org.skriptlang.skript.lang.properties.PropertyHandler.TypedValuePropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.TypedValueHandler; import java.lang.reflect.Array; import java.util.Arrays; @@ -38,7 +38,7 @@ """) @Since("2.10") @RelatedProperty("typed value") -public class PropExprValueOf extends PropertyBaseExpression> { +public class PropExprValueOf extends PropertyBaseExpression> { static { register(PropExprValueOf.class, "[%-*classinfo%] value", "objects"); @@ -74,7 +74,7 @@ public boolean init(Expression[] expressions, int matchedPattern, Kleenean is // determine possible return types if (type == null) { - returnTypes = getPropertyReturnTypes(properties, TypedValuePropertyHandler::possibleReturnTypes); + returnTypes = getPropertyReturnTypes(properties, TypedValueHandler::possibleReturnTypes); returnType = Utils.getSuperType(returnTypes); } else { returnTypes = new Class[]{ type.getC() }; @@ -92,7 +92,7 @@ public boolean init(Expression[] expressions, int matchedPattern, Kleenean is return expr.stream(event) .flatMap(source -> { //noinspection unchecked - var handler = (TypedValuePropertyHandler) properties.getHandler(source.getClass()); + var handler = (TypedValueHandler) properties.getHandler(source.getClass()); if (handler == null) { return null; // no property info found, skip } @@ -109,7 +109,7 @@ public boolean init(Expression[] expressions, int matchedPattern, Kleenean is } @Override - public @NotNull Property> getProperty() { + public @NotNull Property> getProperty() { return Property.TYPED_VALUE; } diff --git a/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprWXYZ.java b/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprWXYZ.java new file mode 100644 index 00000000000..197f5b27147 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/common/properties/expressions/PropExprWXYZ.java @@ -0,0 +1,70 @@ +package org.skriptlang.skript.common.properties.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.util.Kleenean; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.lang.properties.Property; +import org.skriptlang.skript.lang.properties.PropertyBaseExpression; +import org.skriptlang.skript.lang.properties.handlers.WXYZHandler; +import org.skriptlang.skript.lang.properties.handlers.WXYZHandler.Axis; + +import java.util.ArrayList; +import java.util.Locale; + +public class PropExprWXYZ extends PropertyBaseExpression> { + + static { + register(PropExprWXYZ.class, "(:x|:y|:z|:w)( |-)[component[s]|coord[inate][s]|dep:(pos[ition[s]]|loc[ation][s])]", "objects"); + } + + private Axis axis; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { + axis = Axis.valueOf(parseResult.tags.get(0).toUpperCase(Locale.ENGLISH)); + if (!super.init(expressions, matchedPattern, isDelayed, parseResult)) + return false; + + // filter out unsupported handlers and set axis + var tempProperties = new ArrayList<>(properties.entrySet()); + for (var entry : tempProperties) { + var propertyInfo = entry.getValue(); + Class type = entry.getKey(); + var handler = propertyInfo.handler(); + if (!handler.supportsAxis(axis)) { + properties.remove(type); + continue; + } + propertyInfo.handler().axis(axis); + } + + // ensure we have at least one handler left + if (properties.isEmpty()) { + Skript.error("None of the types returned by " + expr + " have an " + axis.name().toLowerCase(Locale.ENGLISH) + " axis component."); + return false; + } + // warn about deprecated syntax (remove in INSERT VERSION + 2) + if (parseResult.hasTag("dep")) { + Skript.warning("Using 'pos[ition]' or 'loc[ation]' to refer to specific coordinates is deprecated and will be removed. " + + "Please use 'coord[inate]', 'component[s]' or just the axis name 'x of {loc}' instead."); + } + return true; + } + + public Axis axis() { + return axis; + } + + @Override + public @NotNull Property> getProperty() { + return Property.WXYZ; + } + + @Override + public String getPropertyName() { + return axis.name(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/common/types/QuaternionClassInfo.java b/src/main/java/org/skriptlang/skript/common/types/QuaternionClassInfo.java new file mode 100644 index 00000000000..479751905a7 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/common/types/QuaternionClassInfo.java @@ -0,0 +1,137 @@ +package org.skriptlang.skript.common.types; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.ParseContext; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; +import org.skriptlang.skript.lang.properties.Property; +import org.skriptlang.skript.lang.properties.handlers.WXYZHandler; +import org.skriptlang.skript.lang.properties.handlers.base.PropertyHandler; + +@ApiStatus.Internal +public class QuaternionClassInfo extends ClassInfo { + + public QuaternionClassInfo() { + super(Quaternionf.class, "quaternion"); + this.user("quaternionf?s?") + .name("Quaternion") + .description("Quaternions are four dimensional vectors, often used for representing rotations.") + .since("2.10") + .parser(new QuaternionParser()) + .defaultExpression(new EventValueExpression<>(Quaternionf.class)) + .cloner(quaternion -> { + try { + // Implements cloneable, but doesn't return a Quaternionf. + // org.joml improper override. Returns Object. + return (Quaternionf) quaternion.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + }) + .property(Property.WXYZ, + "W, X, Y, or Z component of the quaternion.", + Skript.instance(), + new QuaternionWXYZHandler()); + } + + private static class QuaternionWXYZHandler extends WXYZHandler { + // + @Override + public PropertyHandler newInstance() { + var instance = new QuaternionWXYZHandler(); + instance.axis(this.axis); + return instance; + } + + @Override + public @NotNull Float convert(Quaternionf propertyHolder) { + return switch (axis) { + case W -> propertyHolder.w; + case X -> propertyHolder.x; + case Y -> propertyHolder.y; + case Z -> propertyHolder.z; + }; + } + + @Override + public boolean supportsAxis(Axis axis) { + return true; + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case ADD, SET, REMOVE -> new Class[]{Float.class}; + default -> null; + }; + } + + @Override + public void change(Quaternionf propertyHolder, Object @Nullable [] delta, ChangeMode mode) { + assert delta != null; + float value = ((Float) delta[0]); + float x = propertyHolder.x(); + float y = propertyHolder.y(); + float z = propertyHolder.z(); + float w = propertyHolder.w(); + switch (mode) { + case REMOVE: + value = -value; + //$FALL-THROUGH$ + case ADD: + switch (axis) { + case W -> w += value; + case X -> x += value; + case Y -> y += value; + case Z -> z += value; + } + break; + case SET: + switch (axis) { + case W -> w = value; + case X -> x = value; + case Y -> y = value; + case Z -> z = value; + } + break; + } + propertyHolder.set(x, y, z, w); + } + + @Override + public @NotNull Class returnType() { + return Float.class; + } + + @Override + public boolean requiresSourceExprChange() { + return true; + } + // + } + + private static class QuaternionParser extends Parser { + // + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String toString(Quaternionf quaternion, int flags) { + return "w:" + Skript.toString(quaternion.w()) + ", x:" + Skript.toString(quaternion.x()) + ", y:" + Skript.toString(quaternion.y()) + ", z:" + Skript.toString(quaternion.z()); + } + + @Override + public String toVariableNameString(Quaternionf quaternion) { + return quaternion.w() + "," + quaternion.x() + "," + quaternion.y() + "," + quaternion.z(); + } + // + } + +} diff --git a/src/main/java/org/skriptlang/skript/common/types/QueueClassInfo.java b/src/main/java/org/skriptlang/skript/common/types/QueueClassInfo.java index cd2d2ccf88b..9eaa3317b8d 100644 --- a/src/main/java/org/skriptlang/skript/common/types/QueueClassInfo.java +++ b/src/main/java/org/skriptlang/skript/common/types/QueueClassInfo.java @@ -12,8 +12,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.properties.Property; -import org.skriptlang.skript.lang.properties.PropertyHandler.ConditionPropertyHandler; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ConditionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; import org.skriptlang.skript.lang.util.SkriptQueue; import java.io.StreamCorruptedException; diff --git a/src/main/java/org/skriptlang/skript/common/types/ScriptClassInfo.java b/src/main/java/org/skriptlang/skript/common/types/ScriptClassInfo.java index 8a45d41439c..fc46753c50d 100644 --- a/src/main/java/org/skriptlang/skript/common/types/ScriptClassInfo.java +++ b/src/main/java/org/skriptlang/skript/common/types/ScriptClassInfo.java @@ -12,8 +12,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.properties.Property; -import org.skriptlang.skript.lang.properties.PropertyHandler; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.PropertyHandler; import org.skriptlang.skript.lang.script.Script; import java.io.File; diff --git a/src/main/java/org/skriptlang/skript/lang/properties/Property.java b/src/main/java/org/skriptlang/skript/lang/properties/Property.java index 01171aafbb8..f9afc2253d1 100644 --- a/src/main/java/org/skriptlang/skript/lang/properties/Property.java +++ b/src/main/java/org/skriptlang/skript/lang/properties/Property.java @@ -16,10 +16,12 @@ import org.skriptlang.skript.common.properties.expressions.PropExprName; import org.skriptlang.skript.common.types.QueueClassInfo; import org.skriptlang.skript.common.types.ScriptClassInfo; -import org.skriptlang.skript.lang.properties.PropertyHandler.ConditionPropertyHandler; -import org.skriptlang.skript.lang.properties.PropertyHandler.ContainsHandler; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; -import org.skriptlang.skript.lang.properties.PropertyHandler.TypedValuePropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.ContainsHandler; +import org.skriptlang.skript.lang.properties.handlers.TypedValueHandler; +import org.skriptlang.skript.lang.properties.handlers.WXYZHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ConditionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.PropertyHandler; import java.util.Locale; @@ -237,12 +239,22 @@ public record PropertyInfo>(Property /** * A property for getting a specific value of something. */ - public static final Property> TYPED_VALUE = Property.of( + public static final Property> TYPED_VALUE = Property.of( "typed value", "A value of a specific type, e.g. 'string value of x'.", "2.13", Skript.instance(), - TypedValuePropertyHandler.class); + TypedValueHandler.class); + + /** + * A property for getting the x, y, or z coordinates/components of something. + */ + public static final Property> WXYZ = Property.of( + "wxyz component", + "The W, X, Y, or Z components of something, e.g. the x coordinate of a location or vector.", + "INSERT VERSION", + Skript.instance(), + WXYZHandler.class); /** * Register all Skript's default properties. Should be done prior to loading classinfos. diff --git a/src/main/java/org/skriptlang/skript/lang/properties/PropertyBaseCondition.java b/src/main/java/org/skriptlang/skript/lang/properties/PropertyBaseCondition.java index f4d04f76408..fe4439b53be 100644 --- a/src/main/java/org/skriptlang/skript/lang/properties/PropertyBaseCondition.java +++ b/src/main/java/org/skriptlang/skript/lang/properties/PropertyBaseCondition.java @@ -11,7 +11,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.common.properties.conditions.PropCondIsEmpty; -import org.skriptlang.skript.lang.properties.PropertyHandler.ConditionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ConditionPropertyHandler; /** * A helper class for implementing property-driven conditions. Only {@link #getProperty()} needs to be implemented. diff --git a/src/main/java/org/skriptlang/skript/lang/properties/PropertyBaseExpression.java b/src/main/java/org/skriptlang/skript/lang/properties/PropertyBaseExpression.java index 5fa75b89542..e62e4e99dbd 100644 --- a/src/main/java/org/skriptlang/skript/lang/properties/PropertyBaseExpression.java +++ b/src/main/java/org/skriptlang/skript/lang/properties/PropertyBaseExpression.java @@ -17,7 +17,7 @@ import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.common.properties.expressions.PropExprName; import org.skriptlang.skript.lang.properties.Property.PropertyInfo; -import org.skriptlang.skript.lang.properties.PropertyHandler.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; import java.lang.reflect.Array; import java.util.*; diff --git a/src/main/java/org/skriptlang/skript/lang/properties/PropertyBaseSyntax.java b/src/main/java/org/skriptlang/skript/lang/properties/PropertyBaseSyntax.java index c972dd8b3b6..00be7f23271 100644 --- a/src/main/java/org/skriptlang/skript/lang/properties/PropertyBaseSyntax.java +++ b/src/main/java/org/skriptlang/skript/lang/properties/PropertyBaseSyntax.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.properties.handlers.base.PropertyHandler; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/org/skriptlang/skript/lang/properties/PropertyHandler.java b/src/main/java/org/skriptlang/skript/lang/properties/PropertyHandler.java deleted file mode 100644 index 6343cea0ef7..00000000000 --- a/src/main/java/org/skriptlang/skript/lang/properties/PropertyHandler.java +++ /dev/null @@ -1,282 +0,0 @@ -package org.skriptlang.skript.lang.properties; - -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.expressions.ExprSubnodeValue; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ParseContext; -import ch.njol.skript.lang.parser.ParserInstance; -import ch.njol.skript.registrations.Classes; -import ch.njol.skript.util.StringMode; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.skriptlang.skript.common.properties.conditions.PropCondContains; -import org.skriptlang.skript.common.types.ScriptClassInfo; -import org.skriptlang.skript.lang.converter.Converters; - -import java.util.function.Function; -import java.util.function.Predicate; - -/** - * A handler for a specific property. Any method of resolving or changing the property should be done here. - * A handler can be nearly anything and do nearly anything. Some examples are provided in the sub-interfaces. - *
- * If a handler needs to store state, it should override {@link #newInstance()} to return a new instance of itself. - * Each new instance will be initialized with {@link #init(Expression, ParserInstance)} before use, so state can be - * set up there if it depends on the parser instance or parent expression. - * - * @see ExpressionPropertyHandler - * @param The type of object this property can be applied to. - */ -@ApiStatus.Experimental -public interface PropertyHandler { - - /** - * Creates a new instance of this handler. If a handler does not need to store state, it can simply return {@code this}. - * If a handler needs to store state, it **MUST** return a new instance of itself. See {@link ScriptClassInfo.ScriptNameHandler} - * for an example of a stateful handler. - * - * @return A new instance of this handler, or {@code this} if no state is stored. - */ - default PropertyHandler newInstance() { - return this; - } - - /** - * Initializes this handler with the given parser instance. This method is called once after {@link #newInstance()}. - * If the handler does not need any initialization, it can simply return {@code true}. - *
- * It is safe to print warnings or errors from this method. - * - * @param parentExpression The expression that is using this handler. Can be used to get context about the property usage. - * @param parser The parser instance that will use this handler. - * @return {@code true} if the handler was initialized successfully, {@code false} otherwise. - */ - default boolean init(Expression parentExpression, ParserInstance parser) { - return true; - } - - /** - * A handler that can get and optionally change a property value. This interface is suitable for properties that act - * like expressions, such as "name", "display name", etc. Properties that use this interface should also use - * {@link PropertyBaseExpression} for the parent expression. - * @param The type of object this property can be applied to. - * @param The type of object that is returned by this property. - * - * @see PropertyBaseExpression - */ - @ApiStatus.Experimental - interface ExpressionPropertyHandler extends PropertyHandler { - - /** - * Converts the given object to the property value. This method may return arrays if the property is multi-valued. - * - * @param propertyHolder The object to convert. - * @return The property value. - */ - @Nullable ReturnType convert(Type propertyHolder); - - /** - * Returns the types of changes that this property supports. If the property does not support any changes, - * this method should return {@code null}. If the property supports changes, it should return the classes - * that are accepted for each change mode. {@link ChangeMode#RESET} and {@link ChangeMode#DELETE} do not require - * any specific types, so they can return an empty or non-empty array. - *
- * The default implementation returns {@code null}, indicating that the property is read-only. - * - * @param mode The change mode to check. - * @return The types supported by this property for the given change mode, or {@code null} if the property is read-only. - * @see Expression#acceptChange(ChangeMode) - */ - default Class @Nullable [] acceptChange(ChangeMode mode) { - return null; - } - - /** - * Changes the property value of the given object. This method is only called if {@link #acceptChange(ChangeMode)} - * returns a non-null value for the given change mode. - * - * @param propertyHolder The object to change. - * @param delta The new value(s) to set. This is {@code null} for {@link ChangeMode#RESET} and {@link ChangeMode#DELETE}. - * @param mode The change mode to apply. - * @throws UnsupportedOperationException If the property is read-only and does not support changes. - */ - default void change(Type propertyHolder, Object @Nullable [] delta, ChangeMode mode) { - throw new UnsupportedOperationException("Changing is not supported for this property."); - } - - /** - * Whether changing this property requires the source expression to be re-set. - * For example, `set x of (velocity of player) to 1` requires the velocity to be re-set. - * `set name of tool of player` does not, since the slot property updates the item. - * @return Whether the source expression for this property needs to be changed. - */ - default boolean requiresSourceExprChange() { - return false; - } - - /** - * The return type of this property. This is used for type checking and auto-completion. - * If the property can return multiple types, it should return the most general type that encompasses all - * possible return types. - * - * @return The return type of this property. - */ - @NotNull Class returnType(); - - /** - * The possible return types of this property. This is used for type checking and auto-completion. - * The default implementation returns an array containing the type returned by {@link #returnType()}. - * If the property can return multiple types, it should return all possible return types. - * - * @return The possible return types of this property. - */ - default Class @NotNull [] possibleReturnTypes() { - return new Class[]{ returnType() }; - } - - /** - * Creates a simple property handler from the given converter function and return type. - * This is a convenience method for creating property handlers that only need to convert - * a value and do not support changing the property or hold any state. - * - * @param converter The function to convert the object to the property value. - * @param returnType The return type of the property. - * @param The type of object this property can be applied to. - * @param The type of object that is returned by this property. - * @return A new property handler that uses the given converter and return type. - */ - @Contract(value = "_, _ -> new", pure = true) - static @NotNull ExpressionPropertyHandler of( - Function converter, - @NotNull Class returnType - ) { - return new ExpressionPropertyHandler<>() { - - @Override - public @Nullable ReturnType convert(Type propertyHolder) { - return converter.apply(propertyHolder); - } - - @Override - public @NotNull Class returnType() { - return returnType; - } - }; - } - } - - @ApiStatus.Experimental - interface TypedValuePropertyHandler extends ExpressionPropertyHandler { - - /** - * @return This thing's value - */ - @Override - @Nullable ValueType convert(Type propertyHolder); - - default Converted convert(Type propertyHolder, ClassInfo expected) { - ValueType value = convert(propertyHolder); - if (value == null) - return null; - return ExprSubnodeValue.convertedValue(value, expected); - } - - /** - * This method can be used to convert change values to ValueType (or null) - * - * @param value The (unchecked) new value - */ - default ValueType convertChangeValue(Object value) throws UnsupportedOperationException { - Class typeClass = returnType(); - ClassInfo classInfo = Classes.getSuperClassInfo(typeClass); - if (value == null) { - return null; - } else if (typeClass == String.class) { - return typeClass.cast(Classes.toString(value, StringMode.MESSAGE)); - } else if (value instanceof String string - && classInfo.getParser() != null - && classInfo.getParser().canParse(ParseContext.CONFIG)) { - return (ValueType) classInfo.getParser().parse(string, ParseContext.CONFIG); - } else { - return Converters.convert(value, typeClass); - } - } - } - - /** - * A handler for a simple property condition. This property must be an inherent condition of the thing, - * and not require secondary inputs, like {@link ContainsHandler} does. Properties that use this interface should a - * lso use {@link PropertyBaseCondition} for the parent condition. - * @param The type of object this property can be applied to. - * - * @see PropertyBaseCondition - */ - @ApiStatus.Experimental - interface ConditionPropertyHandler extends PropertyHandler { - boolean check(Type propertyHolder); - - /** - * Creates a simple property handler from the given predicate. - * - * @param predicate The predicate to evaluate the condition with. - * @param The type of object this property can be applied to. - * @return A new property handler that uses the given predicate. - */ - @Contract(value = "_ -> new", pure = true) - static @NotNull ConditionPropertyHandler of( - Predicate predicate - ) { - return predicate::test; - } - } - - /** - * A handler that can check if a container contains a specific element. - * - * @param The type of object that can contain elements. - * @param The type of object that can be contained. - * - * @see PropCondContains - */ - @ApiStatus.Experimental - interface ContainsHandler extends PropertyHandler { - /** - * Checks if the given container contains the given element. - * - * @param container The container to check. - * @param element The element to check for. - * @return {@code true} if the container contains the element, {@code false} otherwise. - */ - boolean contains(Container container, Element element); - - /** - * The types of elements that can be contained. This is used for type checking and auto-completion. - * Implementations that override {@link #canContain(Class)} may not return accurate results for this method. - * Callers should prefer {@link #canContain(Class)} when possible. - * - * @return The types of elements that can be contained. - */ - Class[] elementTypes(); - - /** - * Checks if this handler can contain the given type of element. - * The default implementation checks if the given type is assignable to any of the types returned by - * {@link #elementTypes()}. - * - * @param type The type to check. - * @return {@code true} if this handler can contain the given type, {@code false} otherwise. - */ - default boolean canContain(Class type) { - for (Class elementType : elementTypes()) { - if (elementType.isAssignableFrom(type)) { - return true; - } - } - return false; - } - } - -} diff --git a/src/main/java/org/skriptlang/skript/lang/properties/PropertyMap.java b/src/main/java/org/skriptlang/skript/lang/properties/PropertyMap.java index 682254d0cac..0567e7df616 100644 --- a/src/main/java/org/skriptlang/skript/lang/properties/PropertyMap.java +++ b/src/main/java/org/skriptlang/skript/lang/properties/PropertyMap.java @@ -2,6 +2,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.properties.handlers.base.PropertyHandler; import java.util.HashMap; diff --git a/src/main/java/org/skriptlang/skript/lang/properties/handlers/ContainsHandler.java b/src/main/java/org/skriptlang/skript/lang/properties/handlers/ContainsHandler.java new file mode 100644 index 00000000000..61aace9d7b8 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/properties/handlers/ContainsHandler.java @@ -0,0 +1,54 @@ +package org.skriptlang.skript.lang.properties.handlers; + + +import org.jetbrains.annotations.ApiStatus; +import org.skriptlang.skript.common.properties.conditions.PropCondContains; +import org.skriptlang.skript.lang.properties.handlers.base.PropertyHandler; + +/** + * A handler that can check if a container contains a specific element. + * + * @param The type of object that can contain elements. + * @param The type of object that can be contained. + * + * @see PropCondContains + */ +@ApiStatus.Experimental +public interface ContainsHandler extends PropertyHandler { + + /** + * Checks if the given container contains the given element. + * + * @param container The container to check. + * @param element The element to check for. + * @return {@code true} if the container contains the element, {@code false} otherwise. + */ + boolean contains(Container container, Element element); + + /** + * The types of elements that can be contained. This is used for type checking and auto-completion. + * Implementations that override {@link #canContain(Class)} may not return accurate results for this method. + * Callers should prefer {@link #canContain(Class)} when possible. + * + * @return The types of elements that can be contained. + */ + Class[] elementTypes(); + + /** + * Checks if this handler can contain the given type of element. + * The default implementation checks if the given type is assignable to any of the types returned by + * {@link #elementTypes()}. + * + * @param type The type to check. + * @return {@code true} if this handler can contain the given type, {@code false} otherwise. + */ + default boolean canContain(Class type) { + for (Class elementType : elementTypes()) { + if (elementType.isAssignableFrom(type)) { + return true; + } + } + return false; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/properties/handlers/TypedValueHandler.java b/src/main/java/org/skriptlang/skript/lang/properties/handlers/TypedValueHandler.java new file mode 100644 index 00000000000..6567ee99c5d --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/properties/handlers/TypedValueHandler.java @@ -0,0 +1,50 @@ +package org.skriptlang.skript.lang.properties.handlers; + +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.expressions.ExprSubnodeValue; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.StringMode; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.converter.Converters; +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; + +@ApiStatus.Experimental +public interface TypedValueHandler extends ExpressionPropertyHandler { + + /** + * @return This thing's value + */ + @Override + @Nullable ValueType convert(Type propertyHolder); + + default Converted convert(Type propertyHolder, ClassInfo expected) { + ValueType value = convert(propertyHolder); + if (value == null) + return null; + return ExprSubnodeValue.convertedValue(value, expected); + } + + /** + * This method can be used to convert change values to ValueType (or null) + * + * @param value The (unchecked) new value + */ + default ValueType convertChangeValue(Object value) throws UnsupportedOperationException { + Class typeClass = returnType(); + ClassInfo classInfo = Classes.getSuperClassInfo(typeClass); + if (value == null) { + return null; + } else if (typeClass == String.class) { + return typeClass.cast(Classes.toString(value, StringMode.MESSAGE)); + } else if (value instanceof String string + && classInfo.getParser() != null + && classInfo.getParser().canParse(ParseContext.CONFIG)) { + return (ValueType) classInfo.getParser().parse(string, ParseContext.CONFIG); + } else { + return Converters.convert(value, typeClass); + } + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/properties/handlers/WXYZHandler.java b/src/main/java/org/skriptlang/skript/lang/properties/handlers/WXYZHandler.java new file mode 100644 index 00000000000..133ec8f48aa --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/properties/handlers/WXYZHandler.java @@ -0,0 +1,37 @@ +package org.skriptlang.skript.lang.properties.handlers; + + +import org.skriptlang.skript.lang.properties.handlers.base.ExpressionPropertyHandler; +import org.skriptlang.skript.lang.properties.handlers.base.PropertyHandler; + +public abstract class WXYZHandler implements ExpressionPropertyHandler { + + public enum Axis {W, X, Y, Z} + + protected Axis axis; + + @Override + abstract public PropertyHandler newInstance(); + + /** + * @return Whether this handler supports the given axis + */ + public abstract boolean supportsAxis(Axis axis); + + /** + * Sets the specific axis for this handler to use. + * + * @param axis The axis to set + */ + public void axis(Axis axis) { + this.axis = axis; + } + + /** + * @return The axis this handler is using + */ + public Axis axis() { + return axis; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/properties/handlers/base/ConditionPropertyHandler.java b/src/main/java/org/skriptlang/skript/lang/properties/handlers/base/ConditionPropertyHandler.java new file mode 100644 index 00000000000..aff4316c500 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/properties/handlers/base/ConditionPropertyHandler.java @@ -0,0 +1,38 @@ +package org.skriptlang.skript.lang.properties.handlers.base; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.lang.properties.PropertyBaseCondition; +import org.skriptlang.skript.lang.properties.handlers.ContainsHandler; + +import java.util.function.Predicate; + +/** + * A handler for a simple property condition. This property must be an inherent condition of the thing, + * and not require secondary inputs, like {@link ContainsHandler} does. Properties that use this interface should a + * lso use {@link PropertyBaseCondition} for the parent condition. + * @param The type of object this property can be applied to. + * + * @see PropertyBaseCondition + */ +@ApiStatus.Experimental +public interface ConditionPropertyHandler extends PropertyHandler { + + boolean check(Type propertyHolder); + + /** + * Creates a simple property handler from the given predicate. + * + * @param predicate The predicate to evaluate the condition with. + * @param The type of object this property can be applied to. + * @return A new property handler that uses the given predicate. + */ + @Contract(value = "_ -> new", pure = true) + static @NotNull ConditionPropertyHandler of( + Predicate predicate + ) { + return predicate::test; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/properties/handlers/base/ExpressionPropertyHandler.java b/src/main/java/org/skriptlang/skript/lang/properties/handlers/base/ExpressionPropertyHandler.java new file mode 100644 index 00000000000..3252fb77ddf --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/properties/handlers/base/ExpressionPropertyHandler.java @@ -0,0 +1,123 @@ +package org.skriptlang.skript.lang.properties.handlers.base; + + +import ch.njol.skript.classes.Changer; +import ch.njol.skript.lang.Expression; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.properties.PropertyBaseExpression; + +import java.util.function.Function; + +/** + * A handler that can get and optionally change a property value. This interface is suitable for properties that act + * like expressions, such as "name", "display name", etc. Properties that use this interface should also use + * {@link PropertyBaseExpression} for the parent expression. + * @param The type of object this property can be applied to. + * @param The type of object that is returned by this property. + * + * @see PropertyBaseExpression + */ +@ApiStatus.Experimental +public interface ExpressionPropertyHandler extends PropertyHandler { + + /** + * Converts the given object to the property value. This method may return arrays if the property is multi-valued. + * + * @param propertyHolder The object to convert. + * @return The property value. + */ + @Nullable ReturnType convert(Type propertyHolder); + + /** + * Returns the types of changes that this property supports. If the property does not support any changes, + * this method should return {@code null}. If the property supports changes, it should return the classes + * that are accepted for each change mode. {@link Changer.ChangeMode#RESET} and {@link Changer.ChangeMode#DELETE} do not require + * any specific types, so they can return an empty or non-empty array. + *
+ * The default implementation returns {@code null}, indicating that the property is read-only. + * + * @param mode The change mode to check. + * @return The types supported by this property for the given change mode, or {@code null} if the property is read-only. + * @see Expression#acceptChange(Changer.ChangeMode) + */ + default Class @Nullable [] acceptChange(Changer.ChangeMode mode) { + return null; + } + + /** + * Changes the property value of the given object. This method is only called if {@link #acceptChange(Changer.ChangeMode)} + * returns a non-null value for the given change mode. + * + * @param propertyHolder The object to change. + * @param delta The new value(s) to set. This is {@code null} for {@link Changer.ChangeMode#RESET} and {@link Changer.ChangeMode#DELETE}. + * @param mode The change mode to apply. + * @throws UnsupportedOperationException If the property is read-only and does not support changes. + */ + default void change(Type propertyHolder, Object @Nullable [] delta, Changer.ChangeMode mode) { + throw new UnsupportedOperationException("Changing is not supported for this property."); + } + + /** + * Whether changing this property requires the source expression to be re-set. + * For example, `set x of (velocity of player) to 1` requires the velocity to be re-set. + * `set name of tool of player` does not, since the slot property updates the item. + * @return Whether the source expression for this property needs to be changed. + */ + default boolean requiresSourceExprChange() { + return false; + } + + /** + * The return type of this property. This is used for type checking and auto-completion. + * If the property can return multiple types, it should return the most general type that encompasses all + * possible return types. + * + * @return The return type of this property. + */ + @NotNull Class returnType(); + + /** + * The possible return types of this property. This is used for type checking and auto-completion. + * The default implementation returns an array containing the type returned by {@link #returnType()}. + * If the property can return multiple types, it should return all possible return types. + * + * @return The possible return types of this property. + */ + default Class @NotNull [] possibleReturnTypes() { + return new Class[]{ returnType() }; + } + + /** + * Creates a simple property handler from the given converter function and return type. + * This is a convenience method for creating property handlers that only need to convert + * a value and do not support changing the property or hold any state. + * + * @param converter The function to convert the object to the property value. + * @param returnType The return type of the property. + * @param The type of object this property can be applied to. + * @param The type of object that is returned by this property. + * @return A new property handler that uses the given converter and return type. + */ + @Contract(value = "_, _ -> new", pure = true) + static @NotNull ExpressionPropertyHandler of( + Function converter, + @NotNull Class returnType + ) { + return new ExpressionPropertyHandler<>() { + + @Override + public @Nullable ReturnType convert(Type propertyHolder) { + return converter.apply(propertyHolder); + } + + @Override + public @NotNull Class returnType() { + return returnType; + } + }; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/properties/handlers/base/PropertyHandler.java b/src/main/java/org/skriptlang/skript/lang/properties/handlers/base/PropertyHandler.java new file mode 100644 index 00000000000..063bdc2d862 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/properties/handlers/base/PropertyHandler.java @@ -0,0 +1,47 @@ +package org.skriptlang.skript.lang.properties.handlers.base; + +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.parser.ParserInstance; +import org.jetbrains.annotations.ApiStatus; +import org.skriptlang.skript.common.types.ScriptClassInfo; + +/** + * A handler for a specific property. Any method of resolving or changing the property should be done here. + * A handler can be nearly anything and do nearly anything. Some examples are provided in the sub-interfaces. + *
+ * If a handler needs to store state, it should override {@link #newInstance()} to return a new instance of itself. + * Each new instance will be initialized with {@link #init(Expression, ParserInstance)} before use, so state can be + * set up there if it depends on the parser instance or parent expression. + * + * @see ExpressionPropertyHandler + * @param The type of object this property can be applied to. + */ +@ApiStatus.Experimental +public interface PropertyHandler { + + /** + * Creates a new instance of this handler. If a handler does not need to store state, it can simply return {@code this}. + * If a handler needs to store state, it **MUST** return a new instance of itself. See {@link ScriptClassInfo.ScriptNameHandler} + * for an example of a stateful handler. + * + * @return A new instance of this handler, or {@code this} if no state is stored. + */ + default PropertyHandler newInstance() { + return this; + } + + /** + * Initializes this handler with the given parser instance. This method is called once after {@link #newInstance()}. + * If the handler does not need any initialization, it can simply return {@code true}. + *
+ * It is safe to print warnings or errors from this method. + * + * @param parentExpression The expression that is using this handler. Can be used to get context about the property usage. + * @param parser The parser instance that will use this handler. + * @return {@code true} if the handler was initialized successfully, {@code false} otherwise. + */ + default boolean init(Expression parentExpression, ParserInstance parser) { + return true; + } + +} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprXYZComponent.sk b/src/test/skript/tests/syntaxes/expressions/ExprXYZComponent.sk index 519226814dd..7c91f367229 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprXYZComponent.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprXYZComponent.sk @@ -1,12 +1,10 @@ test "vector xyz": - assert the w component of vector(0, 0, 0) is not set with "w vector component return value for vector unexpectedly" assert the x component of vector(1, 0, 0) is 1 with "x vector component failed" assert the y component of vector(0, 1, 0) is 1 with "y vector component failed" assert the z component of vector(0, 0, 1) is 1 with "z vector component failed" set {_v} to vector(0,0,0) set x of {_v} to infinity value - assert the w component of {_v} is not set with "set x of vector created w component somehow" assert the x component of {_v} is infinity value with "set x of vector failed" assert the y component of {_v} is 0 with "set x of vector modified other components" assert the z component of {_v} is 0 with "set x of vector modified other components"