diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 216bc6687d8..5db18a0f246 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -68,6 +68,9 @@ import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos; import org.skriptlang.skript.bukkit.text.TextComponentParser; import org.skriptlang.skript.common.CommonModule; +import org.skriptlang.skript.common.elements.functions.MathFunctions; +import org.skriptlang.skript.common.elements.functions.StringFunctions; +import org.skriptlang.skript.common.elements.functions.TimeFunctions; import org.skriptlang.skript.docs.Origin; import org.skriptlang.skript.lang.comparator.Comparator; import org.skriptlang.skript.lang.comparator.Comparators; @@ -554,7 +557,6 @@ public void load(org.skriptlang.skript.addon.SkriptAddon addon) { throw new RuntimeException(e); } BukkitEventValues.register(eventValueRegistry); - new DefaultFunctions(); new DefaultOperations(); } diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java deleted file mode 100644 index ff604c3e9b7..00000000000 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ /dev/null @@ -1,1065 +0,0 @@ -package ch.njol.skript.classes.data; - -import ch.njol.skript.Skript; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.KeyedValue; -import ch.njol.skript.lang.function.Functions; -import ch.njol.skript.lang.function.Parameter; -import ch.njol.skript.lang.function.SimpleJavaFunction; -import ch.njol.skript.lang.util.SimpleLiteral; -import ch.njol.skript.registrations.Classes; -import ch.njol.skript.registrations.DefaultClasses; -import ch.njol.skript.util.*; -import ch.njol.skript.util.Date; -import ch.njol.util.Math2; -import ch.njol.util.StringUtils; -import ch.njol.util.coll.CollectionUtils; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.OfflinePlayer; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Nullable; -import org.joml.AxisAngle4f; -import org.joml.Quaternionf; -import org.joml.Vector3f; -import org.skriptlang.skript.addon.SkriptAddon; -import org.skriptlang.skript.common.function.DefaultFunction; -import org.skriptlang.skript.common.function.Parameter.Modifier; - -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.text.DecimalFormat; -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; - -public class DefaultFunctions { - - private static String str(double n) { - return StringUtils.toString(n, 4); - } - - private static final DecimalFormat DEFAULT_INTEGER_FORMAT = new DecimalFormat("###,###"); - private static final DecimalFormat DEFAULT_DECIMAL_FORMAT = new DecimalFormat("###,###.##"); - - static { - SkriptAddon skript = Skript.instance(); - Parameter[] numberParam = new Parameter[] {new Parameter<>("n", DefaultClasses.NUMBER, true, null)}; - Parameter[] numbersParam = new Parameter[] {new Parameter<>("ns", DefaultClasses.NUMBER, false, null)}; - - // basic math functions - - Functions.register(DefaultFunction.builder(skript, "floor", Long.class) - .description("Rounds a number down, i.e. returns the closest integer smaller than or equal to the argument.") - .examples("floor(2.34) = 2", "floor(2) = 2", "floor(2.99) = 2") - .since("2.2") - .parameter("n", Number.class) - .build(args -> { - Number value = args.get("n"); - - if (value instanceof Long l) - return l; - - return Math2.floor(value.doubleValue()); - })); - - Functions.registerFunction(new SimpleJavaFunction("round", new Parameter[] {new Parameter<>("n", DefaultClasses.NUMBER, true, null), new Parameter<>("d", DefaultClasses.NUMBER, true, new SimpleLiteral(0, false))}, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - if (params[0][0] instanceof Long longValue) - return new Long[] {longValue}; - double value = ((Number) params[0][0]).doubleValue(); - if (!Double.isFinite(value)) - return new Double[] {value}; - - double placementDouble = ((Number) params[1][0]).doubleValue(); - if (!Double.isFinite(placementDouble) || placementDouble >= Integer.MAX_VALUE || placementDouble <= Integer.MIN_VALUE) - return new Double[] {Double.NaN}; - int placement = (int) placementDouble; - if (placement == 0) - return new Long[] {Math2.round(value)}; - if (placement >= 0) { - BigDecimal decimal = new BigDecimal(Double.toString(value)); - decimal = decimal.setScale(placement, RoundingMode.HALF_UP); - return new Double[] {decimal.doubleValue()}; - } - long rounded = Math2.round(value); - return new Double[] {(int) Math2.round(rounded * Math.pow(10.0, placement)) / Math.pow(10.0, placement)}; - } - }.description("Rounds a number, i.e. returns the closest integer to the argument. Place a second argument to define the decimal placement.") - .examples("round(2.34) = 2", "round(2) = 2", "round(2.99) = 3", "round(2.5) = 3") - .since("2.2, 2.7 (decimal placement)")); - - Functions.registerFunction(new SimpleJavaFunction("ceil", numberParam, DefaultClasses.LONG, true) { - @Override - public Long[] executeSimple(Object[][] params) { - if (params[0][0] instanceof Long) - return new Long[] {(Long) params[0][0]}; - return new Long[] {Math2.ceil(((Number) params[0][0]).doubleValue())}; - } - }.description("Rounds a number up, i.e. returns the closest integer larger than or equal to the argument.") - .examples("ceil(2.34) = 3", "ceil(2) = 2", "ceil(2.99) = 3") - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("ceiling", numberParam, DefaultClasses.LONG, true) { - @Override - public Long[] executeSimple(Object[][] params) { - if (params[0][0] instanceof Long) - return new Long[] {(Long) params[0][0]}; - return new Long[] {Math2.ceil(((Number) params[0][0]).doubleValue())}; - } - }.description("Alias of ceil.") - .examples("ceiling(2.34) = 3", "ceiling(2) = 2", "ceiling(2.99) = 3") - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("abs", numberParam, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - Number n = (Number) params[0][0]; - if (n instanceof Byte || n instanceof Short || n instanceof Integer || n instanceof Long) - return new Long[] {Math.abs(n.longValue())}; - return new Double[] {Math.abs(n.doubleValue())}; - } - }.description("Returns the absolute value of the argument, i.e. makes the argument positive.") - .examples("abs(3) = 3", "abs(-2) = 2") - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("mod", new Parameter[] {new Parameter<>("d", DefaultClasses.NUMBER, true, null), new Parameter<>("m", DefaultClasses.NUMBER, true, null)}, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - Number d = (Number) params[0][0]; - Number m = (Number) params[1][0]; - double mm = m.doubleValue(); - if (mm == 0) - return new Double[] {Double.NaN}; - return new Double[] {Math2.mod(d.doubleValue(), mm)}; - } - }.description("Returns the modulo of the given arguments, i.e. the remainder of the division d/m, where d and m are the arguments of this function.", - "The returned value is always positive. Returns NaN (not a number) if the second argument is zero.") - .examples("mod(3, 2) = 1", "mod(256436, 100) = 36", "mod(-1, 10) = 9") - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("exp", numberParam, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - return new Double[] {Math.exp(((Number) params[0][0]).doubleValue())}; - } - }.description("The exponential function. You probably don't need this if you don't know what this is.") - .examples("exp(0) = 1", "exp(1) = " + str(Math.exp(1))) - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("ln", numberParam, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - return new Double[] {Math.log(((Number) params[0][0]).doubleValue())}; - } - }.description("The natural logarithm. You probably don't need this if you don't know what this is.", - "Returns NaN (not a number) if the argument is negative.") - .examples("ln(1) = 0", "ln(exp(5)) = 5", "ln(2) = " + StringUtils.toString(Math.log(2), 4)) - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("log", new Parameter[] {new Parameter<>("n", DefaultClasses.NUMBER, true, null), new Parameter<>("base", DefaultClasses.NUMBER, true, new SimpleLiteral(10, false))}, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - return new Double[] {Math.log10(((Number) params[0][0]).doubleValue()) / Math.log10(((Number) params[1][0]).doubleValue())}; - } - }.description("A logarithm, with base 10 if none is specified. This is the inverse operation to exponentiation (for positive bases only), i.e. log(base ^ exponent, base) = exponent for any positive number 'base' and any number 'exponent'.", - "Another useful equation is base ^ log(a, base) = a for any numbers 'base' and 'a'.", - "Please note that due to how numbers are represented in computers, these equations do not hold for all numbers, as the computed values may slightly differ from the correct value.", - "Returns NaN (not a number) if any of the arguments are negative.") - .examples("log(100) = 2 # 10^2 = 100", "log(16, 2) = 4 # 2^4 = 16") - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("sqrt", numberParam, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - return new Double[] {Math.sqrt(((Number) params[0][0]).doubleValue())}; - } - }.description("The square root, which is the inverse operation to squaring a number (for positive numbers only). This is the same as (argument) ^ (1/2) – other roots can be calculated via number ^ (1/root), e.g. set {_l} to {_volume}^(1/3).", - "Returns NaN (not a number) if the argument is negative.") - .examples("sqrt(4) = 2", "sqrt(2) = " + str(Math.sqrt(2)), "sqrt(-1) = " + str(Math.sqrt(-1))) - .since("2.2")); - - // trigonometry - - Functions.registerFunction(new SimpleJavaFunction("sin", numberParam, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - return new Double[] {Math.sin(Math.toRadians(((Number) params[0][0]).doubleValue()))}; - } - }.description("The sine function. It starts at 0° with a value of 0, goes to 1 at 90°, back to 0 at 180°, to -1 at 270° and then repeats every 360°. Uses degrees, not radians.") - .examples("sin(90) = 1", "sin(60) = " + str(Math.sin(Math.toRadians(60)))) - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("cos", numberParam, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - return new Double[] {Math.cos(Math.toRadians(((Number) params[0][0]).doubleValue()))}; - } - }.description("The cosine function. This is basically the sine shifted by 90°, i.e. cos(a) = sin(a + 90°), for any number a. Uses degrees, not radians.") - .examples("cos(0) = 1", "cos(90) = 0") - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("tan", numberParam, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - return new Double[] {Math.tan(Math.toRadians(((Number) params[0][0]).doubleValue()))}; - } - }.description("The tangent function. This is basically sin(arg)/cos(arg). Uses degrees, not radians.") - .examples("tan(0) = 0", "tan(45) = 1", "tan(89.99) = " + str(Math.tan(Math.toRadians(89.99)))) - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("asin", numberParam, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - return new Double[] {Math.toDegrees(Math.asin(((Number) params[0][0]).doubleValue()))}; - } - }.description("The inverse of the sine, also called arcsin. Returns result in degrees, not radians. Only returns values from -90 to 90.") - .examples("asin(0) = 0", "asin(1) = 90", "asin(0.5) = " + str(Math.toDegrees(Math.asin(0.5)))) - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("acos", numberParam, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - return new Double[] {Math.toDegrees(Math.acos(((Number) params[0][0]).doubleValue()))}; - } - }.description("The inverse of the cosine, also called arccos. Returns result in degrees, not radians. Only returns values from 0 to 180.") - .examples("acos(0) = 90", "acos(1) = 0", "acos(0.5) = " + str(Math.toDegrees(Math.asin(0.5)))) - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("atan", numberParam, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - return new Double[] {Math.toDegrees(Math.atan(((Number) params[0][0]).doubleValue()))}; - } - }.description("The inverse of the tangent, also called arctan. Returns result in degrees, not radians. Only returns values from -90 to 90.") - .examples("atan(0) = 0", "atan(1) = 45", "atan(10000) = " + str(Math.toDegrees(Math.atan(10000)))) - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("atan2", new Parameter[] { - new Parameter<>("x", DefaultClasses.NUMBER, true, null), - new Parameter<>("y", DefaultClasses.NUMBER, true, null) - }, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - return new Double[] {Math.toDegrees(Math.atan2(((Number) params[1][0]).doubleValue(), ((Number) params[0][0]).doubleValue()))}; - } - }.description("Similar to atan, but requires two coordinates and returns values from -180 to 180.", - "The returned angle is measured counterclockwise in a standard mathematical coordinate system (x to the right, y to the top).") - .examples("atan2(0, 1) = 0", "atan2(10, 0) = 90", "atan2(-10, 5) = " + str(Math.toDegrees(Math.atan2(-10, 5)))) - .since("2.2")); - - // more stuff - - Functions.registerFunction(new SimpleJavaFunction("sum", numbersParam, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - Object[] ns = params[0]; - double sum = ((Number) ns[0]).doubleValue(); - for (int i = 1; i < ns.length; i++) - sum += ((Number) ns[i]).doubleValue(); - return new Double[] {sum}; - } - }.description("Sums a list of numbers.") - .examples("sum(1) = 1", "sum(2, 3, 4) = 9", "sum({some list variable::*})", "sum(2, {_v::*}, and the player's y-coordinate)") - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("product", numbersParam, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - Object[] ns = params[0]; - double product = ((Number) ns[0]).doubleValue(); - for (int i = 1; i < ns.length; i++) - product *= ((Number) ns[i]).doubleValue(); - return new Double[] {product}; - } - }.description("Calculates the product of a list of numbers.") - .examples("product(1) = 1", "product(2, 3, 4) = 24", "product({some list variable::*})", "product(2, {_v::*}, and the player's y-coordinate)") - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("max", numbersParam, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - Object[] ns = params[0]; - double max = ((Number) ns[0]).doubleValue(); - for (int i = 1; i < ns.length; i++) { - double d = ((Number) ns[i]).doubleValue(); - if (d > max || Double.isNaN(max)) - max = d; - } - return new Double[] {max}; - } - }.description("Returns the maximum number from a list of numbers.") - .examples("max(1) = 1", "max(1, 2, 3, 4) = 4", "max({some list variable::*})") - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("min", numbersParam, DefaultClasses.NUMBER, true) { - @Override - public Number[] executeSimple(Object[][] params) { - Object[] ns = params[0]; - double min = ((Number) ns[0]).doubleValue(); - for (int i = 1; i < ns.length; i++) { - double d = ((Number) ns[i]).doubleValue(); - if (d < min || Double.isNaN(min)) - min = d; - } - return new Double[] {min}; - } - }.description("Returns the minimum number from a list of numbers.") - .examples("min(1) = 1", "min(1, 2, 3, 4) = 1", "min({some list variable::*})") - .since("2.2")); - - Functions.registerFunction(new SimpleJavaFunction("clamp", new Parameter[] { - new Parameter<>("values", DefaultClasses.NUMBER, false, null, true), - new Parameter<>("min", DefaultClasses.NUMBER, true, null), - new Parameter<>("max", DefaultClasses.NUMBER, true, null) - }, DefaultClasses.NUMBER, false, new Contract() { - - @Override - public boolean isSingle(Expression... arguments) { - return arguments[0].isSingle(); - } - - @Override - public Class getReturnType(Expression... arguments) { - return Number.class; - } - }) { - @Override - public @Nullable Number[] executeSimple(Object[][] params) { - //noinspection unchecked - KeyedValue[] values = (KeyedValue[]) params[0]; - Double[] clampedValues = new Double[values.length]; - double min = ((Number) params[1][0]).doubleValue(); - double max = ((Number) params[2][0]).doubleValue(); - // we'll be nice and swap them if they're in the wrong order - double trueMin = Math.min(min, max); - double trueMax = Math.max(min, max); - for (int i = 0; i < values.length; i++) { - double value = values[i].value().doubleValue(); - clampedValues[i] = Math.max(Math.min(value, trueMax), trueMin); - } - setReturnedKeys(KeyedValue.unzip(values).keys().toArray(new String[0])); - return clampedValues; - } - }).description("Clamps one or more values between two numbers.", "This function retains indices") - .examples( - "clamp(5, 0, 10) = 5", - "clamp(5.5, 0, 5) = 5", - "clamp(0.25, 0, 0.5) = 0.25", - "clamp(5, 7, 10) = 7", - "clamp((5, 0, 10, 9, 13), 7, 10) = (7, 7, 10, 9, 10)", - "set {_clamped::*} to clamp({_values::*}, 0, 10)") - .since("2.8.0"); - - Functions.register(DefaultFunction.builder(skript, "toBase", String[].class) - .description(""" - Turns a number in a string using a specific base (decimal, hexadecimal, octal). - For example, converting 32 to hexadecimal (base 16) would be 'toBase(32, 16)', which would return "20". - You can use any base between 2 and 36. - """) - .examples( - "send \"Decode this binary number for a prize! %toBase({_guess}, 2)%\"" - ) - .since("2.14") - .parameter("n", Long[].class) - .parameter("base", Long.class, Modifier.ranged(2, 36)) - .contract(new Contract() { - @Override - public boolean isSingle(Expression... arguments) { - return arguments[0].isSingle(); - } - - @Override - public Class getReturnType(Expression... arguments) { - return String.class; - } - }) - .build(args -> { - Long[] n = args.get("n"); - Long base = args.get("base"); - String[] results = new String[n.length]; - for (int i = 0; i < n.length; i++) { - results[i] = Long.toString(n[i], base.intValue()); - } - return results; - })); - - Functions.register(DefaultFunction.builder(skript, "fromBase", Long[].class) - .description(""" - Turns a text version of a number in a specific base (decimal, hexadecimal, octal) into an actual number. - For example, converting "20" in hexadecimal (base 16) would be 'fromBase("20", 16)', which would return 32. - You can use any base between 2 and 36. - """) - .examples(""" - # /binaryText 01110011 01101011 01110010 01101001 01110000 01110100 00100001 - # sends "skript!" - command binaryText : - trigger: - set {_characters::*} to argument split at " " without trailing empty string - transform {_characters::*} with fromBase(input, 2) # convert to codepoints - transform {_characters::*} with character from codepoint input # convert to characters - send join {_characters::*} - """) - .since("2.14") - .parameter("string value", String[].class) - .parameter("base", Long.class, Modifier.ranged(2, 36)) - .contract(new Contract() { - @Override - public boolean isSingle(Expression... arguments) { - return arguments[0].isSingle(); - } - - @Override - public Class getReturnType(Expression... arguments) { - return Long.class; - } - }) - .build(args -> { - String[] n = args.get("string value"); - Long base = args.get("base"); - Long[] results = new Long[n.length]; - try { - for (int i = 0; i < n.length; i++) { - results[i] = Long.parseLong(n[i], base.intValue()); - } - } catch (NumberFormatException e) { - return null; - } - return results; - })); - - // misc - - Functions.registerFunction(new SimpleJavaFunction("world", new Parameter[] { - new Parameter<>("name", DefaultClasses.STRING, true, null) - }, DefaultClasses.WORLD, true) { - @Override - public World[] executeSimple(Object[][] params) { - World w = Bukkit.getWorld((String) params[0][0]); - return w == null ? new World[0] : new World[] {w}; - } - }).description("Gets a world from its name.") - .examples("set {_nether} to world(\"%{_world}%_nether\")") - .since("2.2"); - - Functions.register(DefaultFunction.builder(skript, "location", Location.class) - .description( - "Creates a location from a world and 3 coordinates, with an optional yaw and pitch.", - "If for whatever reason the world is not found, it will fallback to the server's main world." - ) - .examples(""" - # TELEPORTING - teleport player to location(1,1,1, world "world") - teleport player to location(1,1,1, world "world", 100, 0) - teleport player to location(1,1,1, world "world", yaw of player, pitch of player) - teleport player to location(1,1,1, world of player) - teleport player to location(1,1,1, world("world")) - teleport player to location({_x}, {_y}, {_z}, {_w}, {_yaw}, {_pitch}) - - # SETTING BLOCKS - set block at location(1,1,1, world "world") to stone - set block at location(1,1,1, world "world", 100, 0) to stone - set block at location(1,1,1, world of player) to stone - set block at location(1,1,1, world("world")) to stone - set block at location({_x}, {_y}, {_z}, {_w}) to stone - - # USING VARIABLES - set {_l1} to location(1,1,1) - set {_l2} to location(10,10,10) - set blocks within {_l1} and {_l2} to stone - if player is within {_l1} and {_l2}: - - # OTHER - kill all entities in radius 50 around location(1,65,1, world "world") - delete all entities in radius 25 around location(50,50,50, world "world_nether") - ignite all entities in radius 25 around location(1,1,1, world of player) - """ - ) - .since("2.2") - .parameter("x", Number.class) - .parameter("y", Number.class) - .parameter("z", Number.class) - .parameter("world", World.class, Modifier.OPTIONAL) - .parameter("yaw", Float.class, Modifier.OPTIONAL) - .parameter("pitch", Float.class, Modifier.OPTIONAL) - .build(args -> { - World world = args.getOrDefault("world", Bukkit.getWorlds().get(0)); - - return new Location(world, - args.get("x").doubleValue(), args.get("y").doubleValue(), args.get("z").doubleValue(), - args.getOrDefault("yaw", 0f), args.getOrDefault("pitch", 0f)); - })); - - Functions.registerFunction(new SimpleJavaFunction("date", new Parameter[] { - new Parameter<>("year", DefaultClasses.NUMBER, true, null), - new Parameter<>("month", DefaultClasses.NUMBER, true, null), - new Parameter<>("day", DefaultClasses.NUMBER, true, null), - new Parameter<>("hour", DefaultClasses.NUMBER, true, new SimpleLiteral(0, true)), - new Parameter<>("minute", DefaultClasses.NUMBER, true, new SimpleLiteral(0, true)), - new Parameter<>("second", DefaultClasses.NUMBER, true, new SimpleLiteral(0, true)), - new Parameter<>("millisecond", DefaultClasses.NUMBER, true, new SimpleLiteral(0, true)), - new Parameter<>("zone_offset", DefaultClasses.NUMBER, true, new SimpleLiteral(Double.NaN, true)), - new Parameter<>("dst_offset", DefaultClasses.NUMBER, true, new SimpleLiteral(Double.NaN, true)) - }, DefaultClasses.DATE, true) { - private final int[] fields = { - Calendar.YEAR, Calendar.MONTH, Calendar.DAY_OF_MONTH, - Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND, - Calendar.ZONE_OFFSET, Calendar.DST_OFFSET - }; - private final int[] offsets = { - 0, -1, 0, - 0, 0, 0, 0, - 0, 0 - }; - private final double[] scale = { - 1, 1, 1, - 1, 1, 1, 1, - 1000 * 60, 1000 * 60 - }; - private final double[] relations = { - 1. / 12, 1. / 30, - 1. / 24, 1. / 60, 1. / 60, 1. / 1000, - 0, 0, - 0 - }; - - { - int length = getSignature().getMaxParameters(); - assert fields.length == length - && offsets.length == length - && scale.length == length - && relations.length == length; - } - - @Override - public Date[] executeSimple(Object[][] params) { - Calendar c = Calendar.getInstance(); - c.setLenient(true); - double carry = 0; - for (int i = 0; i < fields.length; i++) { - int field = fields[i]; - Number n = (Number) params[i][0]; - - double value = n.doubleValue() * scale[i] + offsets[i] + carry; - int v = (int) Math2.floor(value); - carry = (value - v) * relations[i]; - //noinspection MagicConstant - c.set(field, v); - } - - return new Date[] {new Date(c.getTimeInMillis(), c.getTimeZone())}; - } - }.description("Creates a date from a year, month, and day, and optionally also from hour, minute, second and millisecond.", - "A time zone and DST offset can be specified as well (in minutes), if they are left out the server's time zone and DST offset are used (the created date will not retain this information).") - .examples("date(2014, 10, 1) # 0:00, 1st October 2014", "date(1990, 3, 5, 14, 30) # 14:30, 5th May 1990", "date(1999, 12, 31, 23, 59, 59, 999, -3*60, 0) # almost year 2000 in parts of Brazil (-3 hours offset, no DST)") - .since("2.2")); - - Functions.register(DefaultFunction.builder(skript, "vector", Vector.class) - .description("Creates a vector from a single argument. Equivalent to vector(n, n, n).") - .examples("vector(1) # = vector(1, 1, 1)") - .since("2.15") - .parameter("n", Number.class) - .build(args -> { - double value = args.get("n").doubleValue(); - return new Vector(value, value, value); - })); - - Functions.register(DefaultFunction.builder(skript, "vector", Vector.class) - .description("Creates a new vector, which can be used with various expressions, effects and functions.") - .examples("vector(0, 0, 0)") - .since("2.2-dev23") - .parameter("x", Number.class) - .parameter("y", Number.class) - .parameter("z", Number.class) - .build(args -> new Vector( - args.get("x").doubleValue(), - args.get("y").doubleValue(), - args.get("z").doubleValue() - ))); - - Functions.registerFunction(new SimpleJavaFunction("calcExperience", new Parameter[] { - new Parameter<>("level", DefaultClasses.LONG, true, null) - }, DefaultClasses.LONG, true) { - @Override - public Long[] executeSimple(Object[][] params) { - long level = (long) params[0][0]; - long exp; - if (level <= 0) { - exp = 0; - } else if (level >= 1 && level <= 15) { - exp = level * level + 6 * level; - } else if (level >= 16 && level <= 30) { // Truncating decimal parts probably works - exp = (int) (2.5 * level * level - 40.5 * level + 360); - } else { // Half experience points do not exist, anyway - exp = (int) (4.5 * level * level - 162.5 * level + 2220); - } - - return new Long[] {exp}; - } - - }.description("Calculates the total amount of experience needed to achieve given level from scratch in Minecraft.") - .since("2.2-dev32")); - - Functions.register(DefaultFunction.builder(skript, "rgb", Color.class) - .description(""" - Returns a RGB color from the given red, green and blue parameters. - Alpha values can be added optionally but these only take affect in certain situations, like text display backgrounds.""") - .examples( - "dye player's leggings rgb(120, 30, 45)", - "set the colour of a text display to rgb(10, 50, 100, 50)" - ) - .since("2.5, 2.10 (alpha)") - .parameter("red", Long.class, Modifier.ranged(0, 255)) - .parameter("green", Long.class, Modifier.ranged(0, 255)) - .parameter("blue", Long.class, Modifier.ranged(0, 255)) - .parameter("alpha", Long.class, Modifier.ranged(0, 255), Modifier.OPTIONAL) - .build(args -> ColorRGB.fromRGBA( - args.get("red").intValue(), - args.get("green").intValue(), - args.get("blue").intValue(), - args.getOrDefault("alpha", 255L).intValue() - ))); - - Functions.register(DefaultFunction.builder(skript, "player", Player.class) - .description( - "Returns an online player from their name or UUID, if player is offline function will return nothing.", - "Setting 'getExactPlayer' parameter to true will return the player whose name is exactly equal to the provided name instead of returning a player that their name starts with the provided name." - ) - .examples( - "set {_p} to player(\"Notch\") # will return an online player whose name is or starts with 'Notch'", - "set {_p} to player(\"Notch\", true) # will return the only online player whose name is 'Notch'", - "set {_p} to player(\"069a79f4-44e9-4726-a5be-fca90e38aaf5\") # if player is offline" - ) - .since("2.8.0") - .parameter("nameOrUUID", String.class) - .parameter("getExactPlayer", Boolean.class, Modifier.OPTIONAL) - .build(args -> { - String name = args.get("nameOrUUID"); - boolean isExact = args.getOrDefault("getExactPlayer", false); - - UUID uuid = null; - if (name.length() > 16 || name.contains("-")) { - if (Utils.isValidUUID(name)) - uuid = UUID.fromString(name); - } - - return uuid != null ? Bukkit.getPlayer(uuid) : (isExact ? Bukkit.getPlayerExact(name) : Bukkit.getPlayer(name)); - })); - - { // offline player function - // TODO - remove this when Spigot support is dropped - boolean hasIfCached = Skript.methodExists(Bukkit.class, "getOfflinePlayerIfCached", String.class); - - List> params = new ArrayList<>(); - params.add(new Parameter<>("nameOrUUID", DefaultClasses.STRING, true, null)); - if (hasIfCached) - params.add(new Parameter<>("allowLookups", DefaultClasses.BOOLEAN, true, new SimpleLiteral<>(true, true))); - - Functions.registerFunction(new SimpleJavaFunction("offlineplayer", params.toArray(new Parameter[0]), - DefaultClasses.OFFLINE_PLAYER, true) { - @Override - public OfflinePlayer[] executeSimple(Object[][] params) { - String name = (String) params[0][0]; - UUID uuid = null; - if (name.length() > 16 || name.contains("-")) { // shortcut - if (Utils.isValidUUID(name)) - uuid = UUID.fromString(name); - } - OfflinePlayer result; - - if (uuid != null) { - result = Bukkit.getOfflinePlayer(uuid); // doesn't do lookups - } else if (hasIfCached && !((Boolean) params[1][0])) { - result = Bukkit.getOfflinePlayerIfCached(name); - if (result == null) - return new OfflinePlayer[0]; - } else { - result = Bukkit.getOfflinePlayer(name); - } - - return CollectionUtils.array(result); - } - - }).description( - "Returns a offline player from their name or UUID. This function will still return the player if they're online. " + - "If Paper 1.16.5+ is used, the 'allowLookup' parameter can be set to false to prevent this function from doing a " + - "web lookup for players who have not joined before. Lookups can cause lag spikes of up to multiple seconds, so " + - "use offline players with caution." - ) - .examples( - "set {_p} to offlineplayer(\"Notch\")", - "set {_p} to offlineplayer(\"069a79f4-44e9-4726-a5be-fca90e38aaf5\")", - "set {_p} to offlineplayer(\"Notch\", false)" - ) - .since("2.8.0, 2.9.0 (prevent lookups)"); - } // end offline player function - - Functions.registerFunction(new SimpleJavaFunction("isNaN", numberParam, DefaultClasses.BOOLEAN, true) { - @Override - public Boolean[] executeSimple(Object[][] params) { - return new Boolean[] {Double.isNaN(((Number) params[0][0]).doubleValue())}; - } - }).description("Returns true if the input is NaN (not a number).") - .examples("isNaN(0) # false", "isNaN(0/0) # true", "isNaN(sqrt(-1)) # true") - .since("2.8.0"); - - Functions.register(DefaultFunction.builder(skript, "concat", String.class) - .description("Joins the provided texts (and other things) into a single text.") - .examples( - "concat(\"hello \", \"there\") # hello there", - "concat(\"foo \", 100, \" bar\") # foo 100 bar" - ) - .since("2.9.0") - .parameter("texts", Object[].class) - .build(args -> { - StringBuilder builder = new StringBuilder(); - Object[] objects = args.get("texts"); - for (Object object : objects) { - builder.append(Classes.toString(object)); - } - return builder.toString(); - })); - - // joml functions - for display entities - { - if (Skript.classExists("org.joml.Quaternionf")) { - Functions.registerFunction(new SimpleJavaFunction<>("quaternion", new Parameter[]{ - new Parameter<>("w", DefaultClasses.NUMBER, true, null), - new Parameter<>("x", DefaultClasses.NUMBER, true, null), - new Parameter<>("y", DefaultClasses.NUMBER, true, null), - new Parameter<>("z", DefaultClasses.NUMBER, true, null) - }, Classes.getExactClassInfo(Quaternionf.class), true) { - @Override - public Quaternionf[] executeSimple(Object[][] params) { - double w = ((Number) params[0][0]).doubleValue(); - double x = ((Number) params[1][0]).doubleValue(); - double y = ((Number) params[2][0]).doubleValue(); - double z = ((Number) params[3][0]).doubleValue(); - return CollectionUtils.array(new Quaternionf(x, y, z, w)); - } - }) - .description("Returns a quaternion from the given W, X, Y and Z parameters. ") - .examples("quaternion(1, 5.6, 45.21, 10)") - .since("2.10"); - } - - if (Skript.classExists("org.joml.AxisAngle4f")) { - Functions.registerFunction(new SimpleJavaFunction<>("axisAngle", new Parameter[]{ - new Parameter<>("angle", DefaultClasses.NUMBER, true, null), - new Parameter<>("axis", DefaultClasses.VECTOR, true, null) - }, Classes.getExactClassInfo(Quaternionf.class), true) { - @Override - public Quaternionf[] executeSimple(Object[][] params) { - float angle = (float) (((Number) params[0][0]).floatValue() / 180 * Math.PI); - Vector v = ((Vector) params[1][0]); - if (v.isZero() || !Double.isFinite(v.getX()) || !Double.isFinite(v.getY()) || !Double.isFinite(v.getZ())) - return new Quaternionf[0]; - Vector3f axis = ((Vector) params[1][0]).toVector3f(); - return CollectionUtils.array(new Quaternionf(new AxisAngle4f(angle, axis))); - } - }) - .description("Returns a quaternion from the given angle (in degrees) and axis (as a vector). This represents a rotation around the given axis by the given angle.") - .examples("axisangle(90, (vector from player's facing))") - .since("2.10"); - } - } // end joml functions - - Functions.registerFunction(new SimpleJavaFunction<>("formatNumber", new Parameter[]{ - new Parameter<>("number", DefaultClasses.NUMBER, true, null), - new Parameter<>("format", DefaultClasses.STRING, true, new SimpleLiteral<>("", true)) - }, DefaultClasses.STRING, true) { - @Override - public String[] executeSimple(Object[][] params) { - Number number = (Number) params[0][0]; - String format = (String) params[1][0]; - - if (format.isEmpty()) { - if (number instanceof Double || number instanceof Float) { - return new String[]{DEFAULT_DECIMAL_FORMAT.format(number)}; - } else { - return new String[]{DEFAULT_INTEGER_FORMAT.format(number)}; - } - } - - try { - return new String[]{new DecimalFormat(format).format(number)}; - } catch (IllegalArgumentException e) { - return null; // invalid format - } - } - }) - .description( - "Converts numbers to human-readable format. By default, '###,###' (e.g. '123,456,789') " + - "will be used for whole numbers and '###,###.##' (e.g. '123,456,789.00) will be used for decimal numbers. " + - "A hashtag '#' represents a digit, a comma ',' is used to separate numbers, and a period '.' is used for decimals. ", - "Will return none if the format is invalid.", - "For further reference, see this article.") - .examples( - "command /balance:", - "\taliases: bal", - "\texecutable by: players", - "\ttrigger:", - "\t\tset {_money} to formatNumber({money::%sender's uuid%})", - "\t\tsend \"Your balance: %{_money}%\" to sender") - .since("2.10"); - - Functions.registerFunction(new SimpleJavaFunction<>("uuid", new Parameter[]{ - new Parameter<>("uuid", DefaultClasses.STRING, true, null) - }, Classes.getExactClassInfo(UUID.class), true) { - @Override - public UUID[] executeSimple(Object[][] params) { - String uuid = (String) params[0][0]; - if (Utils.isValidUUID(uuid)) - return CollectionUtils.array(UUID.fromString(uuid)); - return new UUID[0]; - } - } - .description("Returns a UUID from the given string. The string must be in the format of a UUID.") - .examples("uuid(\"069a79f4-44e9-4726-a5be-fca90e38aaf5\")") - .since("2.11") - ); - - Functions.registerFunction(new SimpleJavaFunction("mean", new Parameter[]{ - new Parameter<>("numbers", DefaultClasses.NUMBER, false, null) - }, DefaultClasses.NUMBER, true) { - @Override - public Number @Nullable [] executeSimple(Object[][] params) { - Double total = 0d; - int length = params[0].length; - for (int i = 0; i < length; i++) { - Number number = (Number) params[0][i]; - if (Double.isInfinite(number.doubleValue()) || Double.isNaN(number.doubleValue())) - return null; - if (total.isInfinite() || total.isNaN()) - return null; - total += number.doubleValue() / length; - } - return new Number[]{total}; - } - }) - .description( - "Get the mean (average) of a list of numbers.", - "You cannot get the mean of a set of numbers that includes infinity or NaN." - ) - .examples( - "mean(1, 2, 3) = 2", - "mean(0, 5, 10) = 5", - "mean(13, 97, 376, 709) = 298.75" - ) - .since("2.11"); - - Functions.registerFunction(new SimpleJavaFunction("median", new Parameter[]{ - new Parameter<>("numbers", DefaultClasses.NUMBER, false, null) - }, DefaultClasses.NUMBER, true) { - @Override - public Number @Nullable [] executeSimple(Object[][] params) { - AtomicBoolean invalid = new AtomicBoolean(false); - // median requires the numbers to be sorted from lowest to biggest - Number[] sorted = Arrays.stream(params[0]) - .filter(object -> { - if (!(object instanceof Number number)) - return false; - if (Double.isNaN(number.doubleValue())) { - invalid.set(true); - return false; - } - return true; - }) - .map(object -> (Number) object) - .sorted(((o1, o2) -> { - Double n1 = o1.doubleValue(); - Double n2 = o2.doubleValue(); - return n1.compareTo(n2); - })) - .toArray(Number[]::new); - if (invalid.get()) - return null; - int size = sorted.length; - // If the size of numbers provided is odd, we can just grab the middle number - if (size % 2 == 1) - return new Number[]{sorted[Math2.ceil(size /2)]}; - // If not, we grab the rounded up and rounded down numbers, then get the average of those - int half = size / 2; - double first = (sorted[half - 1]).doubleValue(); - double second = (sorted[half]).doubleValue(); - double median = (first+second)/2; - return new Number[]{median}; - } - }) - .description( - "Get the middle value of a sorted list of numbers. " - + "If the list has an even number of values, the median is the average of the two middle numbers.", - "You cannot get the median of a set of numbers that includes NaN." - ) - .examples( - "median(1, 2, 3, 4, 5) = 3", - "median(1, 2, 3, 4, 5, 6) = 3.5", - "median(0, 123, 456, 789) = 289.5" - ) - .since("2.11"); - - Functions.registerFunction(new SimpleJavaFunction<>("factorial", new Parameter[]{ - new Parameter<>("number", DefaultClasses.NUMBER, true, null) - }, DefaultClasses.NUMBER, true) { - @Override - public Number @Nullable [] executeSimple(Object[][] params) { - Double number = ((Number) params[0][0]).doubleValue(); - if (number < 0) { - return null; - } else if (number <= 1) { // 0 and 1 - return new Number[]{1}; - } else if (number > 170) { - return new Number[]{Double.POSITIVE_INFINITY}; - } - Double result = 1d; - for (double i = number; i > 1; i--) { - if (result.isInfinite() || result.isNaN()) - break; - result *= i; - } - return new Number[]{result}; - } - }) - .description( - "Get the factorial of a number.", - "Getting the factorial of any number above 21 will return an approximation, not an exact value.", - "Any number after 170 will always return Infinity.", - "Should not be used to calculate permutations or combinations manually." - ) - .examples( - "factorial(0) = 1", - "factorial(3) = 3*2*1 = 6", - "factorial(5) = 5*4*3*2*1 = 120", - "factorial(171) = Infinity" - ) - .since("2.11"); - - Functions.registerFunction(new SimpleJavaFunction("root", new Parameter[]{ - new Parameter<>("n", DefaultClasses.NUMBER, true, null), - new Parameter<>("number", DefaultClasses.NUMBER, true, null) - }, DefaultClasses.NUMBER, true) { - @Override - public Number @Nullable [] executeSimple(Object[][] params) { - Double n = ((Number) params[0][0]).doubleValue(); - Double number = ((Number) params[1][0]).doubleValue(); - if (n == 0) { - return null; - } else if (n == 1) { - return new Number[]{number}; - } else if (n == 2) { - return new Number[]{Math.sqrt(number)}; - } - return new Number[]{Math.pow(number, (1 / n))}; - } - }) - .description("Calculates the nth root of a number.") - .examples( - "root(2, 4) = 2 # same as sqrt(4)", - "root(4, 16) = 2", - "root(-4, 16) = 0.5 # same as 16^(-1/4)" - ) - .since("2.11"); - - Functions.registerFunction(new SimpleJavaFunction("permutations", new Parameter[]{ - new Parameter<>("options", DefaultClasses.NUMBER, true, null), - new Parameter<>("selected", DefaultClasses.NUMBER, true, null) - }, DefaultClasses.NUMBER, true) { - @Override - public Number @Nullable [] executeSimple(Object[][] params) { - Double options = ((Number) params[0][0]).doubleValue(); - Double selected = ((Number) params[1][0]).doubleValue(); - if (selected > options || selected < 0) { // Illegal argument - return null; - } else if (selected.equals(0d)) { // Will always be 1 - return new Number[]{1}; - } else if (selected.equals(1d)) { // Will always be the number from 'options' - return new Number[]{options}; - } - // We can simplify this as there will always be a factorial that can cancel out - // Example: options = 10, selected = 2; 10!/(10-2)! = 10!/8! - // We can deduce that 10! = (10)(9)(8!) ; allowing the '8!' factorial to cancel out, leaving us with: (10)(9) - // Which allows us to start from 10 and go down to 10-2, but will never reach 8 as 'i' needs to be higher - Double result = 1d; - for (double i = options; i > options - selected; i--) { - if (result.isInfinite() || result.isNaN()) - break; - result *= i; - } - return new Number[]{result}; - } - }) - .description( - "Get the number of possible ordered arrangements from 1 to 'options' with each arrangement having a size equal to 'selected'", - "For example, permutations with 3 options and an arrangement size of 1, returns 3: (1), (2), (3)", - "Permutations with 3 options and an arrangement size of 2 returns 6: (1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)", - "Note that the bigger the 'options' and lower the 'selected' may result in approximations or even infinity values.", - "Permutations differ from combinations in that permutations account for the arrangement of elements within the set, " - + "whereas combinations focus on unique sets and ignore the order of elements.", - "Example: (1, 2) and (2, 1) are two distinct permutations because the positions of '1' and '2' are different, " - + "but they represent a single combination since order doesn't matter in combinations." - ) - .examples( - "permutations(10, 2) = 90", - "permutations(10, 4) = 5040", - "permutations(size of {some list::*}, 2)" - ) - .since("2.11"); - - Functions.registerFunction(new SimpleJavaFunction("combinations", new Parameter[]{ - new Parameter<>("options", DefaultClasses.NUMBER, true, null), - new Parameter<>("selected", DefaultClasses.NUMBER, true, null) - }, DefaultClasses.NUMBER, true) { - @Override - public Number @Nullable [] executeSimple(Object[][] params) { - Double options = ((Number) params[0][0]).doubleValue(); - Double selected = ((Number) params[1][0]).doubleValue(); - if (selected > options || selected < 0) { // Illegal arguments - return null; - } else if (selected.equals(0d)) { // Will always return 1 - return new Number[]{1}; - } else if (selected.equals(1d)) { // Will always be the number from 'options' - return new Number[]{options}; - } - // By the same reasoning from 'permutations' there will always be a factorial that can cancel out - // Example: options = 10, selected = 2 ; 10!/(10-2)!(2!) = 10!/(8!)(2!) - // 10! = (10)(9)(8!) ; the 8! cancel out, leaving us with: (10)(9)/2! - // 'top' will calculate the leftovers in the numerator: (10)(9) - Double top = 1d; - for (double i = options; i > options - selected; i--) { - if (top.isInfinite() || top.isNaN()) - return new Number[]{top}; - top *= i; - } - // 'bottom' will calculate the leftovers in the denominator: 2! - Double bottom = selected; - for (double i = selected - 1; i > 1; i--) { - if (bottom.isInfinite() || bottom.isNaN()) - break; - bottom *= i; - } - // Then we divide - return new Number[]{top/bottom}; - } - }) - .description( - "Get the number of possible sets from 1 to 'options' with each set having a size equal to 'selected'", - "For example, a combination with 3 options and a set size of 1, returns 3: (1), (2), (3)", - "A combination of 3 options with a set size of 2 returns 3: (1, 2), (1, 3), (2, 3)", - "Note that the bigger the 'options' and lower the 'selected' may result in approximations or even infinity values.", - "Combinations differ from permutations in that combinations focus on unique sets, ignoring the order of elements, " - + "whereas permutations account for the arrangement of elements within the set.", - "Example: (1, 2) and (2, 1) represent a single combination since order doesn't matter in combinations, " - + "but they are two distinct permutations because permutations consider the order." - ) - .examples( - "combinations(10, 8) = 45", - "combinations(5, 3) = 10", - "combinations(size of {some list::*}, 2)" - ) - .since("2.11"); - - } - -} diff --git a/src/main/java/ch/njol/skript/conditions/CondElytraBoostConsume.java b/src/main/java/ch/njol/skript/conditions/CondElytraBoostConsume.java index e5ecfa662c4..fb3d079d0ac 100644 --- a/src/main/java/ch/njol/skript/conditions/CondElytraBoostConsume.java +++ b/src/main/java/ch/njol/skript/conditions/CondElytraBoostConsume.java @@ -3,11 +3,9 @@ import ch.njol.skript.Skript; import ch.njol.skript.doc.*; import ch.njol.skript.lang.Condition; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; import com.destroystokyo.paper.event.player.PlayerElytraBoostEvent; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -20,7 +18,7 @@ prevent the used firework from being consumed """) @Since("2.10") -public class CondElytraBoostConsume extends Condition implements EventRestrictedSyntax { +public class CondElytraBoostConsume extends Condition { static { if (Skript.classExists("com.destroystokyo.paper.event.player.PlayerElytraBoostEvent")) { @@ -34,15 +32,14 @@ public class CondElytraBoostConsume extends Condition implements EventRestricted @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerElytraBoostEvent.class)) { + Skript.error("This condition can only be used in an 'elytra boost' event."); + return false; + } checkConsume = matchedPattern == 0; return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PlayerElytraBoostEvent.class); - } - @Override public boolean check(Event event) { if (!(event instanceof PlayerElytraBoostEvent boostEvent)) diff --git a/src/main/java/ch/njol/skript/conditions/CondLeashWillDrop.java b/src/main/java/ch/njol/skript/conditions/CondLeashWillDrop.java index d9bf5a2c336..05d7f640eb7 100644 --- a/src/main/java/ch/njol/skript/conditions/CondLeashWillDrop.java +++ b/src/main/java/ch/njol/skript/conditions/CondLeashWillDrop.java @@ -1,7 +1,5 @@ package ch.njol.skript.conditions; -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.bukkit.event.entity.EntityUnleashEvent; import org.jetbrains.annotations.Nullable; @@ -25,7 +23,7 @@ @Keywords("lead") @Events("Leash / Unleash") @Since("2.10") -public class CondLeashWillDrop extends Condition implements EventRestrictedSyntax { +public class CondLeashWillDrop extends Condition { static { // TODO - remove this when Spigot support is dropped @@ -35,15 +33,14 @@ public class CondLeashWillDrop extends Condition implements EventRestrictedSynta @Override public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(EntityUnleashEvent.class)) { + Skript.error("The 'leash will drop' condition can only be used in an 'unleash' event"); + return false; + } setNegated(parseResult.hasTag("not")); return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(EntityUnleashEvent.class); - } - @Override public boolean check(Event event) { if (!(event instanceof EntityUnleashEvent unleashEvent)) diff --git a/src/main/java/ch/njol/skript/conditions/CondResourcePack.java b/src/main/java/ch/njol/skript/conditions/CondResourcePack.java index 0e7893b0bd1..f0ee8e6c404 100644 --- a/src/main/java/ch/njol/skript/conditions/CondResourcePack.java +++ b/src/main/java/ch/njol/skript/conditions/CondResourcePack.java @@ -1,7 +1,5 @@ package ch.njol.skript.conditions; -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.bukkit.event.player.PlayerResourcePackStatusEvent; import org.bukkit.event.player.PlayerResourcePackStatusEvent.Status; @@ -27,7 +25,7 @@ """) @Since("2.4") @Events("resource pack request response") -public class CondResourcePack extends Condition implements EventRestrictedSyntax { +public class CondResourcePack extends Condition { static { Skript.registerCondition(CondResourcePack.class, @@ -41,15 +39,14 @@ public class CondResourcePack extends Condition implements EventRestrictedSyntax @SuppressWarnings({"unchecked", "null"}) @Override public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerResourcePackStatusEvent.class)) { + Skript.error("The resource pack condition can't be used outside of a resource pack response event"); + return false; + } states = (Expression) exprs[0]; setNegated(matchedPattern == 1); return true; } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PlayerResourcePackStatusEvent.class); - } @Override public boolean check(Event e) { diff --git a/src/main/java/ch/njol/skript/conditions/CondRespawnLocation.java b/src/main/java/ch/njol/skript/conditions/CondRespawnLocation.java index 210428ef1d7..055b678cd81 100644 --- a/src/main/java/ch/njol/skript/conditions/CondRespawnLocation.java +++ b/src/main/java/ch/njol/skript/conditions/CondRespawnLocation.java @@ -8,11 +8,9 @@ import ch.njol.skript.doc.RequiredPlugins; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Condition; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.bukkit.event.player.PlayerRespawnEvent; import org.jetbrains.annotations.Nullable; @@ -27,7 +25,7 @@ @RequiredPlugins("Minecraft 1.16+") @Since("2.7") @Events("respawn") -public class CondRespawnLocation extends Condition implements EventRestrictedSyntax { +public class CondRespawnLocation extends Condition { static { Skript.registerCondition(CondRespawnLocation.class, "[the] respawn location (was|is)[1:(n'| no)t] [a] (:bed|respawn anchor)"); @@ -37,16 +35,15 @@ public class CondRespawnLocation extends Condition implements EventRestrictedSyn @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerRespawnEvent.class)) { + Skript.error("The 'respawn location' condition may only be used in a respawn event"); + return false; + } setNegated(parseResult.mark == 1); bedSpawn = parseResult.hasTag("bed"); return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PlayerRespawnEvent.class); - } - @Override public boolean check(Event event) { if (event instanceof PlayerRespawnEvent) { diff --git a/src/main/java/ch/njol/skript/conditions/CondWillHatch.java b/src/main/java/ch/njol/skript/conditions/CondWillHatch.java index 8349215bc01..f7370647ed6 100644 --- a/src/main/java/ch/njol/skript/conditions/CondWillHatch.java +++ b/src/main/java/ch/njol/skript/conditions/CondWillHatch.java @@ -7,11 +7,9 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Condition; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.bukkit.event.player.PlayerEggThrowEvent; import org.jetbrains.annotations.Nullable; @@ -25,7 +23,7 @@ """) @Events("Egg Throw") @Since("2.7") -public class CondWillHatch extends Condition implements EventRestrictedSyntax { +public class CondWillHatch extends Condition { static { Skript.registerCondition(CondWillHatch.class, @@ -35,15 +33,14 @@ public class CondWillHatch extends Condition implements EventRestrictedSyntax { @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerEggThrowEvent.class)) { + Skript.error("You can't use the 'egg will hatch' condition outside of a Player Egg Throw event."); + return false; + } setNegated(!parseResult.hasTag("will")); return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PlayerEggThrowEvent.class); - } - @Override public boolean check(Event event) { if (!(event instanceof PlayerEggThrowEvent)) diff --git a/src/main/java/ch/njol/skript/effects/EffCancelCooldown.java b/src/main/java/ch/njol/skript/effects/EffCancelCooldown.java index eb84feae65a..076b9867fd9 100644 --- a/src/main/java/ch/njol/skript/effects/EffCancelCooldown.java +++ b/src/main/java/ch/njol/skript/effects/EffCancelCooldown.java @@ -1,7 +1,5 @@ package ch.njol.skript.effects; -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -32,7 +30,7 @@ set the player's display name to arg-1 """) @Since("2.2-dev34") -public class EffCancelCooldown extends Effect implements EventRestrictedSyntax { +public class EffCancelCooldown extends Effect { static { Skript.registerEffect(EffCancelCooldown.class, @@ -44,15 +42,14 @@ public class EffCancelCooldown extends Effect implements EventRestrictedSyntax { @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { + if (!getParser().isCurrentEvent(ScriptCommandEvent.class)) { + Skript.error("The cancel cooldown effect may only be used in a command", ErrorQuality.SEMANTIC_ERROR); + return false; + } cancel = matchedPattern == 0; return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(ScriptCommandEvent.class); - } - @Override protected void execute(Event e) { if (!(e instanceof ScriptCommandEvent)) diff --git a/src/main/java/ch/njol/skript/effects/EffDropLeash.java b/src/main/java/ch/njol/skript/effects/EffDropLeash.java index 17d9b5447ac..7ebe6294f6a 100644 --- a/src/main/java/ch/njol/skript/effects/EffDropLeash.java +++ b/src/main/java/ch/njol/skript/effects/EffDropLeash.java @@ -1,7 +1,5 @@ package ch.njol.skript.effects; -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.bukkit.event.entity.EntityUnleashEvent; import org.jetbrains.annotations.Nullable; @@ -25,7 +23,7 @@ @Keywords("lead") @Events("Leash / Unleash") @Since("2.10") -public class EffDropLeash extends Effect implements EventRestrictedSyntax { +public class EffDropLeash extends Effect { static { Skript.registerEffect(EffDropLeash.class, @@ -38,15 +36,14 @@ public class EffDropLeash extends Effect implements EventRestrictedSyntax { @Override public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { + if (!getParser().isCurrentEvent(EntityUnleashEvent.class)) { + Skript.error("The 'drop leash' effect can only be used in an 'unleash' event"); + return false; + } allowLeashDrop = matchedPattern == 0; return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(EntityUnleashEvent.class); - } - @Override protected void execute(Event event) { if (!(event instanceof EntityUnleashEvent unleashEvent)) diff --git a/src/main/java/ch/njol/skript/effects/EffElytraBoostConsume.java b/src/main/java/ch/njol/skript/effects/EffElytraBoostConsume.java index 1df5046bebe..2199efee982 100644 --- a/src/main/java/ch/njol/skript/effects/EffElytraBoostConsume.java +++ b/src/main/java/ch/njol/skript/effects/EffElytraBoostConsume.java @@ -3,11 +3,9 @@ import ch.njol.skript.Skript; import ch.njol.skript.doc.*; import ch.njol.skript.lang.Effect; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; import com.destroystokyo.paper.event.player.PlayerElytraBoostEvent; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -20,7 +18,7 @@ prevent the used firework from being consume """) @Since("2.10") -public class EffElytraBoostConsume extends Effect implements EventRestrictedSyntax { +public class EffElytraBoostConsume extends Effect { static { if (Skript.classExists("com.destroystokyo.paper.event.player.PlayerElytraBoostEvent")) { @@ -34,15 +32,14 @@ public class EffElytraBoostConsume extends Effect implements EventRestrictedSynt @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerElytraBoostEvent.class)) { + Skript.error("This effect can only be used in an 'elytra boost' event."); + return false; + } consume = matchedPattern == 1; return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PlayerElytraBoostEvent.class); - } - @Override protected void execute(Event event) { if (!(event instanceof PlayerElytraBoostEvent boostEvent)) diff --git a/src/main/java/ch/njol/skript/effects/EffHidePlayerFromServerList.java b/src/main/java/ch/njol/skript/effects/EffHidePlayerFromServerList.java index 0daa0b285ef..2c267191685 100644 --- a/src/main/java/ch/njol/skript/effects/EffHidePlayerFromServerList.java +++ b/src/main/java/ch/njol/skript/effects/EffHidePlayerFromServerList.java @@ -3,8 +3,6 @@ import java.util.Arrays; import java.util.Iterator; -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.event.server.ServerListPingEvent; @@ -30,7 +28,7 @@ hide {vanished::*} from the server list """) @Since("2.3") -public class EffHidePlayerFromServerList extends Effect implements EventRestrictedSyntax { +public class EffHidePlayerFromServerList extends Effect { static { Skript.registerEffect(EffHidePlayerFromServerList.class, @@ -38,13 +36,20 @@ public class EffHidePlayerFromServerList extends Effect implements EventRestrict "hide %players%'[s] info[rmation] (in|on|from) [the] server list"); } + private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); + @SuppressWarnings("null") private Expression players; @SuppressWarnings({"unchecked", "null"}) @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - if (isDelayed == Kleenean.TRUE) { + boolean isServerPingEvent = getParser().isCurrentEvent(ServerListPingEvent.class) || + (PAPER_EVENT_EXISTS && getParser().isCurrentEvent(PaperServerListPingEvent.class)); + if (!isServerPingEvent) { + Skript.error("The hide player from server list effect can't be used outside of a server list ping event"); + return false; + } else if (isDelayed == Kleenean.TRUE) { Skript.error("Can't hide players from the server list anymore after the server list ping event has already passed"); return false; } @@ -52,11 +57,6 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(ServerListPingEvent.class, PaperServerListPingEvent.class); - } - @Override @SuppressWarnings("removal") protected void execute(Event e) { diff --git a/src/main/java/ch/njol/skript/effects/EffKeepInventory.java b/src/main/java/ch/njol/skript/effects/EffKeepInventory.java index 9f05beb95bc..1a9c43add6e 100644 --- a/src/main/java/ch/njol/skript/effects/EffKeepInventory.java +++ b/src/main/java/ch/njol/skript/effects/EffKeepInventory.java @@ -1,7 +1,5 @@ package ch.njol.skript.effects; -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.PlayerDeathEvent; @@ -27,7 +25,7 @@ """) @Since("2.4") @Events("death") -public class EffKeepInventory extends Effect implements EventRestrictedSyntax { +public class EffKeepInventory extends Effect { static { Skript.registerEffect(EffKeepInventory.class, @@ -41,6 +39,10 @@ public class EffKeepInventory extends Effect implements EventRestrictedSyntax { public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { keepItems = matchedPattern == 0 || parseResult.mark == 1; keepExp = matchedPattern == 1 || parseResult.mark == 1; + if (!getParser().isCurrentEvent(EntityDeathEvent.class)) { + Skript.error("The keep inventory/experience effect can't be used outside of a death event"); + return false; + } if (isDelayed.isTrue()) { Skript.error("Can't keep the inventory/experience anymore after the event has already passed"); return false; @@ -48,11 +50,6 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(EntityDeathEvent.class); - } - @Override protected void execute(Event event) { if (event instanceof PlayerDeathEvent) { diff --git a/src/main/java/ch/njol/skript/effects/EffLoadServerIcon.java b/src/main/java/ch/njol/skript/effects/EffLoadServerIcon.java index 7f568089ec1..ff87e2b0698 100644 --- a/src/main/java/ch/njol/skript/effects/EffLoadServerIcon.java +++ b/src/main/java/ch/njol/skript/effects/EffLoadServerIcon.java @@ -39,6 +39,8 @@ public class EffLoadServerIcon extends AsyncEffect { Skript.registerEffect(EffLoadServerIcon.class, "load [the] server icon (from|of) [the] [image] [file] %string%"); } + private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); + @SuppressWarnings("null") private Expression path; @@ -49,6 +51,10 @@ public class EffLoadServerIcon extends AsyncEffect { @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { getParser().setHasDelayBefore(Kleenean.TRUE); + if (!PAPER_EVENT_EXISTS) { + Skript.error("The load server icon effect requires Paper 1.12.2 or newer"); + return false; + } path = (Expression) exprs[0]; return true; } diff --git a/src/main/java/ch/njol/skript/effects/EffMakeEggHatch.java b/src/main/java/ch/njol/skript/effects/EffMakeEggHatch.java index 38303592c82..83c62f64420 100644 --- a/src/main/java/ch/njol/skript/effects/EffMakeEggHatch.java +++ b/src/main/java/ch/njol/skript/effects/EffMakeEggHatch.java @@ -7,11 +7,9 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Effect; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.bukkit.event.player.PlayerEggThrowEvent; import org.jetbrains.annotations.Nullable; @@ -25,7 +23,7 @@ """) @Events("Egg Throw") @Since("2.7") -public class EffMakeEggHatch extends Effect implements EventRestrictedSyntax { +public class EffMakeEggHatch extends Effect { static { Skript.registerEffect(EffMakeEggHatch.class, @@ -37,15 +35,14 @@ public class EffMakeEggHatch extends Effect implements EventRestrictedSyntax { @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerEggThrowEvent.class)) { + Skript.error("You can't use the 'make the egg hatch' effect outside of a Player Egg Throw event."); + return false; + } not = parseResult.hasTag("not"); return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PlayerEggThrowEvent.class); - } - @Override protected void execute(Event e) { if (e instanceof PlayerEggThrowEvent) { diff --git a/src/main/java/ch/njol/skript/effects/EffPlayerInfoVisibility.java b/src/main/java/ch/njol/skript/effects/EffPlayerInfoVisibility.java index 50489423ead..ced4b44dc6f 100644 --- a/src/main/java/ch/njol/skript/effects/EffPlayerInfoVisibility.java +++ b/src/main/java/ch/njol/skript/effects/EffPlayerInfoVisibility.java @@ -3,11 +3,9 @@ import ch.njol.skript.Skript; import ch.njol.skript.doc.*; import ch.njol.skript.lang.Effect; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; import com.destroystokyo.paper.event.server.PaperServerListPingEvent; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -24,7 +22,7 @@ @Example("reveal all player related info") @Since("2.3") @Events("server list ping") -public class EffPlayerInfoVisibility extends Effect implements EventRestrictedSyntax { +public class EffPlayerInfoVisibility extends Effect { static { Skript.registerEffect(EffPlayerInfoVisibility.class, @@ -32,11 +30,19 @@ public class EffPlayerInfoVisibility extends Effect implements EventRestrictedSy "(show|reveal) [all] player [related] info[rmation] [(in|to|on|from) [the] server list]"); } + private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); + private boolean shouldHide; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - if (isDelayed == Kleenean.TRUE) { + if (!PAPER_EVENT_EXISTS) { + Skript.error("The player info visibility effect requires Paper 1.12.2 or newer"); + return false; + } else if (!getParser().isCurrentEvent(PaperServerListPingEvent.class)) { + Skript.error("The player info visibility effect can't be used outside of a server list ping event"); + return false; + } else if (isDelayed == Kleenean.TRUE) { Skript.error("Can't change the player info visibility anymore after the server list ping event has already passed"); return false; } @@ -44,11 +50,6 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PaperServerListPingEvent.class); - } - @Override protected void execute(Event e) { if (!(e instanceof PaperServerListPingEvent)) diff --git a/src/main/java/ch/njol/skript/expressions/ExprAbsorbedBlocks.java b/src/main/java/ch/njol/skript/expressions/ExprAbsorbedBlocks.java index 153a4048426..d41a808dfcb 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAbsorbedBlocks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAbsorbedBlocks.java @@ -3,8 +3,6 @@ import java.util.Iterator; import java.util.List; -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.block.BlockState; import org.bukkit.event.Event; import org.bukkit.event.block.SpongeAbsorbEvent; @@ -29,7 +27,7 @@ @Events("sponge absorb") @Example("the absorbed blocks") @Since("2.5") -public class ExprAbsorbedBlocks extends SimpleExpression implements EventRestrictedSyntax { +public class ExprAbsorbedBlocks extends SimpleExpression { static { Skript.registerExpression(ExprAbsorbedBlocks.class, BlockStateBlock.class, ExpressionType.SIMPLE, "[the] absorbed blocks"); @@ -37,13 +35,12 @@ public class ExprAbsorbedBlocks extends SimpleExpression implem @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(SpongeAbsorbEvent.class)) { + Skript.error("The 'absorbed blocks' are only usable in sponge absorb events", ErrorQuality.SEMANTIC_ERROR); + return false; + } return true; } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(SpongeAbsorbEvent.class); - } @Override @Nullable diff --git a/src/main/java/ch/njol/skript/expressions/ExprAffectedEntities.java b/src/main/java/ch/njol/skript/expressions/ExprAffectedEntities.java index ee463e3ea8d..56aaa587a4c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAffectedEntities.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAffectedEntities.java @@ -4,7 +4,6 @@ import java.util.Objects; import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.util.coll.CollectionUtils; import org.bukkit.entity.LivingEntity; import org.bukkit.event.Event; @@ -31,7 +30,7 @@ send "WARNING: you've step on an area effect cloud!" to loop-value """) @Since("2.4") -public class ExprAffectedEntities extends SimpleExpression implements EventRestrictedSyntax { +public class ExprAffectedEntities extends SimpleExpression { static { Skript.registerExpression(ExprAffectedEntities.class, LivingEntity.class, ExpressionType.SIMPLE, "[the] affected entities"); @@ -39,14 +38,13 @@ public class ExprAffectedEntities extends SimpleExpression impleme @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { + if (!getParser().isCurrentEvent(AreaEffectCloudApplyEvent.class)) { + Skript.error("The 'affected entities' expression may only be used in an area cloud effect event."); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(AreaEffectCloudApplyEvent.class); - } - @Override protected LivingEntity @Nullable [] get(Event event) { if (event instanceof AreaEffectCloudApplyEvent areaEvent) diff --git a/src/main/java/ch/njol/skript/expressions/ExprAppliedEffect.java b/src/main/java/ch/njol/skript/expressions/ExprAppliedEffect.java index 3dee2c5b0d3..f02a191d845 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAppliedEffect.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAppliedEffect.java @@ -2,13 +2,11 @@ import ch.njol.skript.Skript; import ch.njol.skript.doc.*; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; import com.destroystokyo.paper.event.block.BeaconEffectEvent; import org.bukkit.event.Event; import org.bukkit.potion.PotionEffectType; @@ -25,7 +23,7 @@ """) @Events("Beacon Effect") @Since("2.10") -public class ExprAppliedEffect extends SimpleExpression implements EventRestrictedSyntax { +public class ExprAppliedEffect extends SimpleExpression { static { if (Skript.classExists("com.destroystokyo.paper.event.block.BeaconEffectEvent")) { @@ -36,14 +34,13 @@ public class ExprAppliedEffect extends SimpleExpression implem @Override public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(BeaconEffectEvent.class)) { + Skript.error("You can only use 'applied effect' in a beacon effect event."); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(BeaconEffectEvent.class); - } - @Override protected PotionEffectType @Nullable [] get(Event event) { if (!(event instanceof BeaconEffectEvent effectEvent)) diff --git a/src/main/java/ch/njol/skript/expressions/ExprAppliedEnchantments.java b/src/main/java/ch/njol/skript/expressions/ExprAppliedEnchantments.java index 85885ddcc9f..4faa757cb48 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAppliedEnchantments.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAppliedEnchantments.java @@ -1,6 +1,5 @@ package ch.njol.skript.expressions; -import ch.njol.skript.lang.EventRestrictedSyntax; import org.bukkit.enchantments.Enchantment; import org.bukkit.event.Event; import org.bukkit.event.enchantment.EnchantItemEvent; @@ -31,7 +30,7 @@ """) @Events("enchant") @Since("2.5") -public class ExprAppliedEnchantments extends SimpleExpression implements EventRestrictedSyntax { +public class ExprAppliedEnchantments extends SimpleExpression { static { Skript.registerExpression(ExprAppliedEnchantments.class, EnchantmentType.class, ExpressionType.SIMPLE, "[the] applied enchant[ment]s"); @@ -39,14 +38,13 @@ public class ExprAppliedEnchantments extends SimpleExpression i @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(EnchantItemEvent.class)) { + Skript.error("The applied enchantments are only usable in an enchant event.", ErrorQuality.SEMANTIC_ERROR); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(EnchantItemEvent.class); - } - @SuppressWarnings("null") @Override @Nullable diff --git a/src/main/java/ch/njol/skript/expressions/ExprBarterDrops.java b/src/main/java/ch/njol/skript/expressions/ExprBarterDrops.java index 1486dc89f84..385daca4422 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBarterDrops.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBarterDrops.java @@ -7,7 +7,6 @@ import ch.njol.skript.doc.Example; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; @@ -30,7 +29,7 @@ broadcast "it's not halloween yet!" """) @Since("2.10") -public class ExprBarterDrops extends SimpleExpression implements EventRestrictedSyntax { +public class ExprBarterDrops extends SimpleExpression { static { if (Skript.classExists("org.bukkit.event.entity.PiglinBarterEvent")) { @@ -43,15 +42,15 @@ public class ExprBarterDrops extends SimpleExpression implements Event @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult result) { + if (!getParser().isCurrentEvent(PiglinBarterEvent.class)) { + Skript.error("The expression 'barter drops' can only be used in the piglin bartering event"); + return false; + } + delay = isDelayed; return true; } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PiglinBarterEvent.class); - } @Override @Nullable diff --git a/src/main/java/ch/njol/skript/expressions/ExprBarterInput.java b/src/main/java/ch/njol/skript/expressions/ExprBarterInput.java index bd0f5adc648..f677e08143c 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBarterInput.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBarterInput.java @@ -6,13 +6,11 @@ import ch.njol.skript.doc.Example; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.bukkit.event.entity.PiglinBarterEvent; import org.jetbrains.annotations.Nullable; @@ -25,7 +23,7 @@ broadcast "my precious..." """) @Since("2.10") -public class ExprBarterInput extends SimpleExpression implements EventRestrictedSyntax { +public class ExprBarterInput extends SimpleExpression { static { if (Skript.classExists("org.bukkit.event.entity.PiglinBarterEvent")) { @@ -36,14 +34,13 @@ public class ExprBarterInput extends SimpleExpression implements Event @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult result) { + if (!getParser().isCurrentEvent(PiglinBarterEvent.class)) { + Skript.error("The expression 'barter input' can only be used in the piglin bartering event"); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PiglinBarterEvent.class); - } - @Override @Nullable protected ItemType[] get(Event event) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprCmdCooldownInfo.java b/src/main/java/ch/njol/skript/expressions/ExprCmdCooldownInfo.java index 9cb0eaf7882..1afd010d11e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprCmdCooldownInfo.java +++ b/src/main/java/ch/njol/skript/expressions/ExprCmdCooldownInfo.java @@ -2,8 +2,6 @@ import java.util.UUID; -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.event.Event; @@ -37,7 +35,7 @@ teleport player to {home::%player%} """) @Since("2.2-dev33") -public class ExprCmdCooldownInfo extends SimpleExpression implements EventRestrictedSyntax { +public class ExprCmdCooldownInfo extends SimpleExpression { static { Skript.registerExpression(ExprCmdCooldownInfo.class, Object.class, ExpressionType.SIMPLE, @@ -53,14 +51,13 @@ public class ExprCmdCooldownInfo extends SimpleExpression implements Eve @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { pattern = matchedPattern; + if (!getParser().isCurrentEvent(ScriptCommandEvent.class)) { + Skript.error("The " + getExpressionName() + " expression can only be used within a command", ErrorQuality.SEMANTIC_ERROR); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(ScriptCommandEvent.class); - } - @Override @Nullable protected Object[] get(Event e) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprDamage.java b/src/main/java/ch/njol/skript/expressions/ExprDamage.java index de0c2f4a36b..77af9b89b35 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprDamage.java +++ b/src/main/java/ch/njol/skript/expressions/ExprDamage.java @@ -1,6 +1,5 @@ package ch.njol.skript.expressions; -import ch.njol.skript.lang.EventRestrictedSyntax; import org.bukkit.event.Event; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.player.PlayerItemDamageEvent; @@ -40,7 +39,7 @@ """) @Since("1.3.5, 2.8.0 (item damage event)") @Events({"Damage", "Vehicle Damage", "Item Damage"}) -public class ExprDamage extends SimpleExpression implements EventRestrictedSyntax { +public class ExprDamage extends SimpleExpression { static { Skript.registerExpression(ExprDamage.class, Number.class, ExpressionType.SIMPLE, "[the] damage"); @@ -51,14 +50,13 @@ public class ExprDamage extends SimpleExpression implements EventRestric @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(EntityDamageEvent.class, VehicleDamageEvent.class, PlayerItemDamageEvent.class)) { + Skript.error("The 'damage' expression may only be used in damage events"); + return false; + } delay = isDelayed; return true; } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(EntityDamageEvent.class, VehicleDamageEvent.class, PlayerItemDamageEvent.class); - } @Override @Nullable diff --git a/src/main/java/ch/njol/skript/expressions/ExprEnchantItem.java b/src/main/java/ch/njol/skript/expressions/ExprEnchantItem.java index b76aeccad44..59c31f6b364 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEnchantItem.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEnchantItem.java @@ -1,6 +1,5 @@ package ch.njol.skript.expressions; -import ch.njol.skript.lang.EventRestrictedSyntax; import org.bukkit.event.Event; import org.bukkit.event.enchantment.EnchantItemEvent; import org.bukkit.event.enchantment.PrepareItemEnchantEvent; @@ -35,7 +34,7 @@ """) @Events({"enchant prepare", "enchant"}) @Since("2.5") -public class ExprEnchantItem extends SimpleExpression implements EventRestrictedSyntax { +public class ExprEnchantItem extends SimpleExpression { static { Skript.registerExpression(ExprEnchantItem.class, ItemType.class, ExpressionType.SIMPLE, "[the] enchant[ed] item"); @@ -43,14 +42,13 @@ public class ExprEnchantItem extends SimpleExpression implements Event @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(EnchantItemEvent.class) && !getParser().isCurrentEvent(PrepareItemEnchantEvent.class)) { + Skript.error("The enchant item is only usable in an enchant prepare event or enchant event.", ErrorQuality.SEMANTIC_ERROR); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(EnchantItemEvent.class, PrepareItemEnchantEvent.class); - } - @Override @Nullable protected ItemType[] get(Event e) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprEnchantingExpCost.java b/src/main/java/ch/njol/skript/expressions/ExprEnchantingExpCost.java index 97d0d4f2f0e..e22595e946b 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEnchantingExpCost.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEnchantingExpCost.java @@ -1,6 +1,5 @@ package ch.njol.skript.expressions; -import ch.njol.skript.lang.EventRestrictedSyntax; import org.bukkit.event.Event; import org.bukkit.event.enchantment.EnchantItemEvent; import org.jetbrains.annotations.Nullable; @@ -30,7 +29,7 @@ """) @Events("enchant") @Since("2.5") -public class ExprEnchantingExpCost extends SimpleExpression implements EventRestrictedSyntax { +public class ExprEnchantingExpCost extends SimpleExpression { static { Skript.registerExpression(ExprEnchantingExpCost.class, Long.class, ExpressionType.SIMPLE, @@ -39,14 +38,13 @@ public class ExprEnchantingExpCost extends SimpleExpression implements Eve @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(EnchantItemEvent.class)) { + Skript.error("The experience cost of enchanting is only usable in an enchant event.", ErrorQuality.SEMANTIC_ERROR); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(EnchantItemEvent.class); - } - @Override @Nullable protected Long[] get(Event e) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentBonus.java b/src/main/java/ch/njol/skript/expressions/ExprEnchantmentBonus.java index 42b53e16623..fce4094a136 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentBonus.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEnchantmentBonus.java @@ -1,7 +1,5 @@ package ch.njol.skript.expressions; -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.bukkit.event.enchantment.PrepareItemEnchantEvent; import org.jetbrains.annotations.Nullable; @@ -27,7 +25,7 @@ """) @Events("enchant prepare") @Since("2.5") -public class ExprEnchantmentBonus extends SimpleExpression implements EventRestrictedSyntax { +public class ExprEnchantmentBonus extends SimpleExpression { static { Skript.registerExpression(ExprEnchantmentBonus.class, Long.class, ExpressionType.SIMPLE, "[the] enchantment bonus"); @@ -35,14 +33,13 @@ public class ExprEnchantmentBonus extends SimpleExpression implements Even @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PrepareItemEnchantEvent.class)) { + Skript.error("The enchantment bonus is only usable in an enchant prepare event.", ErrorQuality.SEMANTIC_ERROR); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PrepareItemEnchantEvent.class); - } - @Override @Nullable protected Long[] get(Event e) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOffer.java b/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOffer.java index d574d0c8e31..ffac6e92301 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOffer.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEnchantmentOffer.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.Random; -import ch.njol.skript.lang.EventRestrictedSyntax; import org.bukkit.enchantments.EnchantmentOffer; import org.bukkit.event.Event; import org.bukkit.event.enchantment.PrepareItemEnchantEvent; @@ -37,7 +36,7 @@ @Since("2.5") @Events("enchant prepare") @RequiredPlugins("1.11 or newer") -public class ExprEnchantmentOffer extends SimpleExpression implements EventRestrictedSyntax { +public class ExprEnchantmentOffer extends SimpleExpression { static { if (Skript.classExists("org.bukkit.enchantments.EnchantmentOffer")) { @@ -59,6 +58,10 @@ public class ExprEnchantmentOffer extends SimpleExpression imp @SuppressWarnings({"null", "unchecked"}) @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PrepareItemEnchantEvent.class)) { + Skript.error("Enchantment offers are only usable in enchant prepare events", ErrorQuality.SEMANTIC_ERROR); + return false; + } if (matchedPattern == 0) { all = true; } else { @@ -68,11 +71,6 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PrepareItemEnchantEvent.class); - } - @SuppressWarnings({"null", "unused"}) @Override @Nullable diff --git a/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java b/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java index d25242e7f5c..6ec2f503161 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEvtInitiator.java @@ -6,7 +6,6 @@ import ch.njol.skript.doc.Example; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; @@ -27,7 +26,7 @@ """) @Events("Inventory Item Move") @Since("2.8.0") -public class ExprEvtInitiator extends SimpleExpression implements EventRestrictedSyntax { +public class ExprEvtInitiator extends SimpleExpression { static { Skript.registerExpression(ExprEvtInitiator.class, Inventory.class, ExpressionType.SIMPLE, "[the] [event-]initiator[( |-)inventory]"); @@ -35,14 +34,13 @@ public class ExprEvtInitiator extends SimpleExpression implements Eve @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(InventoryMoveItemEvent.class)) { + Skript.error("'event-initiator' can only be used in an 'inventory item move' event."); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(InventoryMoveItemEvent.class); - } - @Override protected Inventory[] get(Event event) { if (!(event instanceof InventoryMoveItemEvent)) diff --git a/src/main/java/ch/njol/skript/expressions/ExprExperience.java b/src/main/java/ch/njol/skript/expressions/ExprExperience.java index 97a51e71a62..fd3785da2be 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprExperience.java +++ b/src/main/java/ch/njol/skript/expressions/ExprExperience.java @@ -4,7 +4,6 @@ import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.*; import ch.njol.skript.events.bukkit.ExperienceSpawnEvent; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; @@ -16,7 +15,10 @@ import org.bukkit.event.player.PlayerExpChangeEvent; import org.bukkit.event.player.PlayerFishEvent; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.entity.EntityBreedEvent; +import org.bukkit.event.player.PlayerExpChangeEvent; import org.jetbrains.annotations.Nullable; @Name("Experience") @@ -45,7 +47,7 @@ """) @Since("2.1, 2.5.3 (block break event), 2.7 (experience change event), 2.10 (breeding, fishing)") @Events({"experience spawn", "break / mine", "experience change", "entity breed"}) -public class ExprExperience extends SimpleExpression implements EventRestrictedSyntax { +public class ExprExperience extends SimpleExpression { static { Skript.registerExpression(ExprExperience.class, Experience.class, ExpressionType.SIMPLE, @@ -54,14 +56,15 @@ public class ExprExperience extends SimpleExpression implements Even @Override public boolean init(Expression[] expressions, int matchedPattern, - Kleenean isDelayed, ParseResult parseResult) { - return true; - } + Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(ExperienceSpawnEvent.class, BlockBreakEvent.class, + PlayerExpChangeEvent.class, EntityBreedEvent.class, PlayerFishEvent.class)) { + Skript.error("The 'experience' expression can only be used in experience spawn, " + + "block break, player experience change, entity breed or fishing events"); + return false; + } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(ExperienceSpawnEvent.class, BlockBreakEvent.class, - PlayerExpChangeEvent.class, EntityBreedEvent.class, PlayerFishEvent.class); + return true; } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprExplosionBlockYield.java b/src/main/java/ch/njol/skript/expressions/ExprExplosionBlockYield.java index 38b7b2040cf..b24b984bbc4 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprExplosionBlockYield.java +++ b/src/main/java/ch/njol/skript/expressions/ExprExplosionBlockYield.java @@ -1,6 +1,5 @@ package ch.njol.skript.expressions; -import ch.njol.skript.lang.EventRestrictedSyntax; import org.bukkit.event.Event; import org.bukkit.event.entity.EntityExplodeEvent; import org.jetbrains.annotations.Nullable; @@ -30,7 +29,7 @@ """) @Events("explode") @Since("2.5") -public class ExprExplosionBlockYield extends SimpleExpression implements EventRestrictedSyntax { +public class ExprExplosionBlockYield extends SimpleExpression { static { Skript.registerExpression(ExprExplosionBlockYield.class, Number.class, ExpressionType.PROPERTY, @@ -41,14 +40,13 @@ public class ExprExplosionBlockYield extends SimpleExpression implements @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(EntityExplodeEvent.class)) { + Skript.error("The 'explosion block yield' is only usable in an explosion event", ErrorQuality.SEMANTIC_ERROR); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(EntityExplodeEvent.class); - } - @Override @Nullable protected Number[] get(Event e) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprExplosionYield.java b/src/main/java/ch/njol/skript/expressions/ExprExplosionYield.java index f982cbc10b9..a1012d7f921 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprExplosionYield.java +++ b/src/main/java/ch/njol/skript/expressions/ExprExplosionYield.java @@ -1,6 +1,5 @@ package ch.njol.skript.expressions; -import ch.njol.skript.lang.EventRestrictedSyntax; import org.bukkit.event.Event; import org.bukkit.event.entity.ExplosionPrimeEvent; import org.jetbrains.annotations.Nullable; @@ -30,7 +29,7 @@ """) @Events("explosion prime") @Since("2.5") -public class ExprExplosionYield extends SimpleExpression implements EventRestrictedSyntax { +public class ExprExplosionYield extends SimpleExpression { static { Skript.registerExpression(ExprExplosionYield.class, Number.class, ExpressionType.SIMPLE, @@ -41,14 +40,13 @@ public class ExprExplosionYield extends SimpleExpression implements Even @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(ExplosionPrimeEvent.class)) { + Skript.error("The explosion radius is only usable in explosion prime events", ErrorQuality.SEMANTIC_ERROR); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(ExplosionPrimeEvent.class); - } - @Override @Nullable protected Number[] get(Event e) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprFertilizedBlocks.java b/src/main/java/ch/njol/skript/expressions/ExprFertilizedBlocks.java index 3050ca08db4..750967d0bc0 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFertilizedBlocks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFertilizedBlocks.java @@ -3,8 +3,6 @@ import java.util.Iterator; -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.bukkit.event.block.BlockFertilizeEvent; import org.jetbrains.annotations.Nullable; @@ -29,7 +27,7 @@ @Events("block fertilize") @Example("the fertilized blocks") @Since("2.5") -public class ExprFertilizedBlocks extends SimpleExpression implements EventRestrictedSyntax { +public class ExprFertilizedBlocks extends SimpleExpression { static { if (Skript.classExists("org.bukkit.event.block.BlockFertilizeEvent")) @@ -38,13 +36,12 @@ public class ExprFertilizedBlocks extends SimpleExpression impl @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(BlockFertilizeEvent.class)) { + Skript.error("The 'fertilized blocks' are only usable in block fertilize events"); + return false; + } return true; } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(BlockFertilizeEvent.class); - } @Nullable @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprFinalDamage.java b/src/main/java/ch/njol/skript/expressions/ExprFinalDamage.java index ab2f526114b..d9c80c51825 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFinalDamage.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFinalDamage.java @@ -1,7 +1,5 @@ package ch.njol.skript.expressions; -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.bukkit.event.entity.EntityDamageEvent; import org.jetbrains.annotations.Nullable; @@ -26,7 +24,7 @@ @Example("send \"%final damage%\" to victim") @Since("2.2-dev19") @Events("damage") -public class ExprFinalDamage extends SimpleExpression implements EventRestrictedSyntax { +public class ExprFinalDamage extends SimpleExpression { static { Skript.registerExpression(ExprFinalDamage.class, Number.class, ExpressionType.SIMPLE, "[the] final damage"); @@ -34,13 +32,12 @@ public class ExprFinalDamage extends SimpleExpression implements EventRe @Override public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { + if (!getParser().isCurrentEvent(EntityDamageEvent.class)) { + Skript.error("The expression 'final damage' can only be used in damage events", ErrorQuality.SEMANTIC_ERROR); + return false; + } return true; } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(EntityDamageEvent.class); - } @Override @Nullable diff --git a/src/main/java/ch/njol/skript/expressions/ExprHanging.java b/src/main/java/ch/njol/skript/expressions/ExprHanging.java index fec9a5ac729..01395ec06c3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHanging.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHanging.java @@ -5,13 +5,11 @@ import ch.njol.skript.doc.Example; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.entity.Entity; import org.bukkit.event.Event; import org.bukkit.event.hanging.HangingBreakByEntityEvent; @@ -30,7 +28,7 @@ send "You can't break that item frame!" to hanging remover """) @Since("2.6.2") -public class ExprHanging extends SimpleExpression implements EventRestrictedSyntax { +public class ExprHanging extends SimpleExpression { static { Skript.registerExpression(ExprHanging.class, Entity.class, ExpressionType.SIMPLE, "[the] hanging (entity|:remover)"); @@ -45,15 +43,13 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye if (isRemover && !getParser().isCurrentEvent(HangingBreakEvent.class)) { Skript.error("The expression 'hanging remover' can only be used in break event"); return false; + } else if (!getParser().isCurrentEvent(HangingBreakEvent.class, HangingPlaceEvent.class)) { + Skript.error("The expression 'hanging entity' can only be used in break and place events"); + return false; } return true; } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(HangingBreakEvent.class, HangingPlaceEvent.class); - } - + @Override @Nullable public Entity[] get(Event e) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprHatchingNumber.java b/src/main/java/ch/njol/skript/expressions/ExprHatchingNumber.java index aeb93039e39..6004abae9e1 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHatchingNumber.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHatchingNumber.java @@ -7,7 +7,6 @@ import ch.njol.skript.doc.Example; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; @@ -29,7 +28,7 @@ """) @Events("Egg Throw") @Since("2.7") -public class ExprHatchingNumber extends SimpleExpression implements EventRestrictedSyntax { +public class ExprHatchingNumber extends SimpleExpression { static { Skript.registerExpression(ExprHatchingNumber.class, Byte.class, ExpressionType.SIMPLE, @@ -39,14 +38,13 @@ public class ExprHatchingNumber extends SimpleExpression implements EventR @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerEggThrowEvent.class)) { + Skript.error("You can't use 'the hatching number' outside of a Player Egg Throw event."); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PlayerEggThrowEvent.class); - } - @Override @Nullable protected Byte[] get(Event event) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprHatchingType.java b/src/main/java/ch/njol/skript/expressions/ExprHatchingType.java index 2ff1c118e69..46dcec5479d 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHatchingType.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHatchingType.java @@ -9,7 +9,6 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.entity.EntityData; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; @@ -29,7 +28,7 @@ """) @Events("Egg Throw") @Since("2.7") -public class ExprHatchingType extends SimpleExpression> implements EventRestrictedSyntax { +public class ExprHatchingType extends SimpleExpression> { static { //noinspection unchecked @@ -40,14 +39,13 @@ public class ExprHatchingType extends SimpleExpression> implements @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerEggThrowEvent.class)) { + Skript.error("You can't use 'the hatching entity type' outside of a Player Egg Throw event."); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PlayerEggThrowEvent.class); - } - @Override @Nullable protected EntityData[] get(Event event) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprHealAmount.java b/src/main/java/ch/njol/skript/expressions/ExprHealAmount.java index ecc962a7841..433c514e326 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHealAmount.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHealAmount.java @@ -1,7 +1,6 @@ package ch.njol.skript.expressions; -import ch.njol.skript.lang.EventRestrictedSyntax; import org.bukkit.event.Event; import org.bukkit.event.entity.EntityRegainHealthEvent; import org.jetbrains.annotations.Nullable; @@ -30,7 +29,7 @@ """) @Events("heal") @Since("2.5.1") -public class ExprHealAmount extends SimpleExpression implements EventRestrictedSyntax { +public class ExprHealAmount extends SimpleExpression { static { Skript.registerExpression(ExprHealAmount.class, Double.class, ExpressionType.SIMPLE, "[the] heal[ing] amount"); @@ -40,15 +39,14 @@ public class ExprHealAmount extends SimpleExpression implements EventRes @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(EntityRegainHealthEvent.class)) { + Skript.error("The expression 'heal amount' may only be used in a healing event"); + return false; + } delay = isDelayed; return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(EntityRegainHealthEvent.class); - } - @Nullable @Override protected Double[] get(Event event) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprHostname.java b/src/main/java/ch/njol/skript/expressions/ExprHostname.java index bc892d173ca..2ce724c0c6e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHostname.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHostname.java @@ -1,7 +1,5 @@ package ch.njol.skript.expressions; -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.bukkit.event.player.PlayerLoginEvent; import org.jetbrains.annotations.Nullable; @@ -25,7 +23,7 @@ send "Welcome back tester!" """) @Since("2.6.1") -public class ExprHostname extends SimpleExpression implements EventRestrictedSyntax { +public class ExprHostname extends SimpleExpression { static { Skript.registerExpression(ExprHostname.class, String.class, ExpressionType.SIMPLE, "[the] (host|domain)[ ][name]"); @@ -34,13 +32,12 @@ public class ExprHostname extends SimpleExpression implements EventRestr @Override @SuppressWarnings({"null"}) public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerLoginEvent.class)) { + Skript.error("The hostname expression must be used in a player connect event"); + return false; + } return true; } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PlayerLoginEvent.class); - } @Override @Nullable diff --git a/src/main/java/ch/njol/skript/expressions/ExprHotbarButton.java b/src/main/java/ch/njol/skript/expressions/ExprHotbarButton.java index e82f5067538..cdad734b128 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHotbarButton.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHotbarButton.java @@ -1,7 +1,5 @@ package ch.njol.skript.expressions; -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.bukkit.event.inventory.InventoryClickEvent; import org.jetbrains.annotations.Nullable; @@ -24,7 +22,7 @@ send "You clicked the hotbar button %hotbar button%!" """) @Since("2.5") -public class ExprHotbarButton extends SimpleExpression implements EventRestrictedSyntax { +public class ExprHotbarButton extends SimpleExpression { static { Skript.registerExpression(ExprHotbarButton.class, Long.class, ExpressionType.SIMPLE, "[the] hotbar button"); @@ -32,13 +30,12 @@ public class ExprHotbarButton extends SimpleExpression implements EventRes @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { + if (!getParser().isCurrentEvent(InventoryClickEvent.class)) { + Skript.error("The 'hotbar button' expression may only be used in an inventory click event."); + return false; + } return true; } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(InventoryClickEvent.class); - } @Nullable @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprHoverList.java b/src/main/java/ch/njol/skript/expressions/ExprHoverList.java index 787c08db3db..11d31f9d8e3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprHoverList.java +++ b/src/main/java/ch/njol/skript/expressions/ExprHoverList.java @@ -3,7 +3,6 @@ import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.*; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; @@ -11,7 +10,9 @@ import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; import com.destroystokyo.paper.event.server.PaperServerListPingEvent; +import com.destroystokyo.paper.profile.PlayerProfile; import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -26,9 +27,9 @@ @Description({ "The list when you hover on the player counts of the server in the server list.", "This can be changed using texts or players in a server list ping event only. " + - "Adding players to the list means adding the name of the players.", + "Adding players to the list means adding the name of the players.", "And note that, for example if there are 5 online players (includes fake online count) " + - "in the server and the hover list is set to 3 values, Minecraft will show \"... and 2 more ...\" at end of the list." + "in the server and the hover list is set to 3 values, Minecraft will show \"... and 2 more ...\" at end of the list." }) @Example(""" on server list ping: @@ -39,52 +40,54 @@ """) @Since("2.3") @Events("server list ping") -public class ExprHoverList extends SimpleExpression implements EventRestrictedSyntax -{ +public class ExprHoverList extends SimpleExpression { - static - { + static { Skript.registerExpression(ExprHoverList.class, String.class, ExpressionType.SIMPLE, "[the] [custom] [player|server] (hover|sample) ([message] list|message)", "[the] [custom] player [hover|sample] list"); } - @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) - { - return true; - } + private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); + private static final boolean HAS_NEW_LISTED_PLAYER_INFO = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent$ListedPlayerInfo"); @Override - public Class[] supportedEvents() - { - return CollectionUtils.array(PaperServerListPingEvent.class); + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!PAPER_EVENT_EXISTS) { + Skript.error("The hover list expression requires Paper 1.12.2 or newer"); + return false; + } else if (!getParser().isCurrentEvent(PaperServerListPingEvent.class)) { + Skript.error("The hover list expression can't be used outside of a server list ping event"); + return false; + } + return true; } @Override - public String @Nullable [] get(Event event) - { - if (!(event instanceof PaperServerListPingEvent pingEvent)) - { + @SuppressWarnings({"removal"}) + public String @Nullable [] get(Event event) { + if (!(event instanceof PaperServerListPingEvent)) return null; - } - return pingEvent.getListedPlayers().stream() - .map(PaperServerListPingEvent.ListedPlayerInfo::name) - .toArray(String[]::new); + if (HAS_NEW_LISTED_PLAYER_INFO) { + return ((PaperServerListPingEvent) event).getListedPlayers().stream() + .map(PaperServerListPingEvent.ListedPlayerInfo::name) + .toArray(String[]::new); + } else { + return ((PaperServerListPingEvent) event).getPlayerSample().stream() + .map(PlayerProfile::getName) + .toArray(String[]::new); + } } @Override @Nullable - public Class[] acceptChange(ChangeMode mode) - { - if (getParser().getHasDelayBefore().isTrue()) - { + public Class[] acceptChange(ChangeMode mode) { + if (getParser().getHasDelayBefore().isTrue()) { Skript.error("Can't change the hover list anymore after the server list ping event has already passed"); return null; } - switch (mode) - { + switch (mode) { case SET: case ADD: case REMOVE: @@ -97,41 +100,65 @@ public Class[] acceptChange(ChangeMode mode) @Override @SuppressWarnings({"null", "removal"}) - public void change(Event event, @Nullable Object[] delta, ChangeMode mode) - { - if (!(event instanceof PaperServerListPingEvent pingEvent)) - { + public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + if (!(event instanceof PaperServerListPingEvent)) return; - } // convert components to legacy strings - if (delta != null) - { + if (delta != null) { delta = Arrays.stream(delta) .map(obj -> obj instanceof Component component ? TextComponentParser.instance().toLegacyString(component) : obj) .toArray(); } - List values = new ArrayList<>(); - if (mode != ChangeMode.DELETE && mode != ChangeMode.RESET && mode != ChangeMode.REMOVE) - { - for (Object object : delta) - { - if (object instanceof Player) - { - Player player = (Player) object; - values.add(new PaperServerListPingEvent.ListedPlayerInfo(player.getName(), player.getUniqueId())); + if (HAS_NEW_LISTED_PLAYER_INFO) { + List values = new ArrayList<>(); + if (mode != ChangeMode.DELETE && mode != ChangeMode.RESET && mode != ChangeMode.REMOVE) { + for (Object object : delta) { + if (object instanceof Player) { + Player player = (Player) object; + values.add(new PaperServerListPingEvent.ListedPlayerInfo(player.getName(), player.getUniqueId())); + } else { + values.add(new PaperServerListPingEvent.ListedPlayerInfo((String) object, UUID.randomUUID())); + } } - else - { - values.add(new PaperServerListPingEvent.ListedPlayerInfo((String) object, UUID.randomUUID())); + } + + List sample = ((PaperServerListPingEvent) event).getListedPlayers(); + switch (mode) { + case SET: + sample.clear(); + // $FALL-THROUGH$ + case ADD: + sample.addAll(values); + break; + case REMOVE: + for (Object value : delta) { + sample.removeIf(profile -> profile.name().equals(value)); + } + break; + case DELETE: + case RESET: + sample.clear(); + break; + } + return; + } + + List values = new ArrayList<>(); + if (mode != ChangeMode.DELETE && mode != ChangeMode.RESET && mode != ChangeMode.REMOVE) { + for (Object object : delta) { + if (object instanceof Player) { + Player player = (Player) object; + values.add(Bukkit.createProfile(player.getUniqueId(), player.getName())); + } else { + values.add(Bukkit.createProfile(UUID.randomUUID(), (String) object)); } } } - List sample = pingEvent.getListedPlayers(); - switch (mode) - { + List sample = ((PaperServerListPingEvent) event).getPlayerSample(); + switch (mode) { case SET: sample.clear(); // $FALL-THROUGH$ @@ -139,9 +166,8 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) sample.addAll(values); break; case REMOVE: - for (Object value : delta) - { - sample.removeIf(profile -> profile.name().equals(value)); + for (Object value : delta) { + sample.removeIf(profile -> profile.getName() != null && profile.getName().equals(value)); } break; case DELETE: @@ -152,20 +178,17 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) } @Override - public boolean isSingle() - { + public boolean isSingle() { return false; } @Override - public Class getReturnType() - { + public Class getReturnType() { return String.class; } @Override - public String toString(@Nullable Event e, boolean debug) - { + public String toString(@Nullable Event e, boolean debug) { return "the hover list"; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprIP.java b/src/main/java/ch/njol/skript/expressions/ExprIP.java index c473b535909..c6da46554ea 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprIP.java +++ b/src/main/java/ch/njol/skript/expressions/ExprIP.java @@ -48,6 +48,8 @@ public class ExprIP extends SimpleExpression { "IP[( |-)address]"); } + private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); + @SuppressWarnings("null") private Expression players; @@ -59,7 +61,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye isProperty = matchedPattern < 2; boolean isConnectEvent = getParser().isCurrentEvent(PlayerLoginEvent.class); boolean isServerPingEvent = getParser().isCurrentEvent(ServerListPingEvent.class) || - getParser().isCurrentEvent(PaperServerListPingEvent.class); + (PAPER_EVENT_EXISTS && getParser().isCurrentEvent(PaperServerListPingEvent.class)); if (isProperty) { players = (Expression) exprs[0]; } else if (!isConnectEvent && !isServerPingEvent) { @@ -74,15 +76,14 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye protected String[] get(Event e) { if (!isProperty) { InetAddress address; - if (e instanceof PlayerLoginEvent loginEvent) { + if (e instanceof PlayerLoginEvent) // Return IP address of the connected player in connect event - address = loginEvent.getAddress(); - } else if (e instanceof ServerListPingEvent pingEvent) + address = ((PlayerLoginEvent) e).getAddress(); + else if (e instanceof ServerListPingEvent) // Return IP address of the pinger in server list ping event - address = pingEvent.getAddress(); - else { + address = ((ServerListPingEvent) e).getAddress(); + else return null; - } return CollectionUtils.array(address.getHostAddress()); } @@ -105,7 +106,7 @@ private String getIP(Player player, Event e) { assert sockAddr != null; // Not in connect event address = sockAddr.getAddress(); } - + String hostAddress = address == null ? "unknown" : address.getHostAddress(); assert hostAddress != null; return hostAddress; diff --git a/src/main/java/ch/njol/skript/expressions/ExprLastLoadedServerIcon.java b/src/main/java/ch/njol/skript/expressions/ExprLastLoadedServerIcon.java index 6fa2aeafacc..c91ecb47ee3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLastLoadedServerIcon.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLastLoadedServerIcon.java @@ -26,8 +26,14 @@ public class ExprLastLoadedServerIcon extends SimpleExpression Skript.registerExpression(ExprLastLoadedServerIcon.class, CachedServerIcon.class, ExpressionType.SIMPLE, "[the] [last[ly]] loaded server icon"); } + private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); + @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!PAPER_EVENT_EXISTS) { + Skript.error("The last loaded server icon expression requires Paper 1.12.2+"); + return false; + } return true; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprMaxPlayers.java b/src/main/java/ch/njol/skript/expressions/ExprMaxPlayers.java index 340e248fc6e..95a4e6cb500 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMaxPlayers.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMaxPlayers.java @@ -36,12 +36,16 @@ public class ExprMaxPlayers extends SimpleExpression { ); } + // TODO - remove these fields when Spigot support is dropped + private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); + private static final boolean SET_MAX_PLAYERS_EXISTS = Skript.methodExists(Server.class, "setMaxPlayers", int.class); + private boolean isReal; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { boolean isServerPingEvent = getParser().isCurrentEvent(ServerListPingEvent.class) || - getParser().isCurrentEvent(PaperServerListPingEvent.class); + (PAPER_EVENT_EXISTS && getParser().isCurrentEvent(PaperServerListPingEvent.class)); if (parseResult.mark == 2 && !isServerPingEvent) { Skript.error("The 'shown' max players count expression can't be used outside of a server list ping event"); @@ -73,6 +77,11 @@ public Class[] acceptChange(ChangeMode mode) { return null; } + if (isReal && !SET_MAX_PLAYERS_EXISTS) { + Skript.error("Modifying the 'real max player count' is only supported on Paper 1.16 and newer"); + return null; + } + switch (mode) { case SET: case ADD: diff --git a/src/main/java/ch/njol/skript/expressions/ExprMendingRepairAmount.java b/src/main/java/ch/njol/skript/expressions/ExprMendingRepairAmount.java index 3892f3bc39c..e562b75e35d 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMendingRepairAmount.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMendingRepairAmount.java @@ -1,6 +1,5 @@ package ch.njol.skript.expressions; -import ch.njol.skript.lang.EventRestrictedSyntax; import org.bukkit.event.Event; import org.bukkit.event.player.PlayerItemMendEvent; import org.jetbrains.annotations.Nullable; @@ -28,7 +27,7 @@ set the mending repair amount to 100 """) @Since("2.5.1") -public class ExprMendingRepairAmount extends SimpleExpression implements EventRestrictedSyntax { +public class ExprMendingRepairAmount extends SimpleExpression { static { Skript.registerExpression(ExprMendingRepairAmount.class, Long.class, ExpressionType.SIMPLE, "[the] [mending] repair amount"); @@ -36,14 +35,13 @@ public class ExprMendingRepairAmount extends SimpleExpression implements E @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerItemMendEvent.class)) { + Skript.error("The 'mending repair amount' is only usable in item mend events", ErrorQuality.SEMANTIC_ERROR); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PlayerItemMendEvent.class); - } - @Override protected Long[] get(final Event e) { if (!(e instanceof PlayerItemMendEvent)) diff --git a/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java b/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java index bc52420da22..e0a8af841ff 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java +++ b/src/main/java/ch/njol/skript/expressions/ExprOnlinePlayersCount.java @@ -38,18 +38,23 @@ public class ExprOnlinePlayersCount extends SimpleExpression { "[the] [(1:(real|default)|2:(fake|shown|displayed))] (count|amount|number|size) of online players"); } + private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); + private boolean isReal; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - boolean isListPingEvent = getParser().isCurrentEvent(PaperServerListPingEvent.class); + boolean isPaperEvent = PAPER_EVENT_EXISTS && getParser().isCurrentEvent(PaperServerListPingEvent.class); if (parseResult.mark == 2) { - if (!isListPingEvent) { + if (!PAPER_EVENT_EXISTS && getParser().isCurrentEvent(ServerListPingEvent.class)) { + Skript.error("The 'fake' online players count expression requires Paper 1.12.2 or newer"); + return false; + } else if (!isPaperEvent) { Skript.error("The 'fake' online players count expression can't be used outside of a server list ping event"); return false; } } - isReal = (parseResult.mark == 0 && !isListPingEvent) || parseResult.mark == 1; + isReal = (parseResult.mark == 0 && !isPaperEvent) || parseResult.mark == 1; return true; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprPortal.java b/src/main/java/ch/njol/skript/expressions/ExprPortal.java index b8c911329fc..5bde4f7ce26 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPortal.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPortal.java @@ -3,8 +3,6 @@ import java.util.Iterator; import java.util.List; -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.event.Event; @@ -32,7 +30,7 @@ """) @Since("2.4") @Events("portal_create") -public class ExprPortal extends SimpleExpression implements EventRestrictedSyntax { +public class ExprPortal extends SimpleExpression { // 1.14+ returns List, 1.13.2 and below returns ArrayList private static final boolean USING_BLOCKSTATE = Skript.isRunningMinecraft(1, 14); @@ -45,12 +43,10 @@ public class ExprPortal extends SimpleExpression implements EventRestrict @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { - return true; - } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PortalCreateEvent.class); + if (getParser().isCurrentEvent(PortalCreateEvent.class)) + return true; + Skript.error("The 'portal' expression may only be used in a portal creation event."); + return false; } @Nullable diff --git a/src/main/java/ch/njol/skript/expressions/ExprProtocolVersion.java b/src/main/java/ch/njol/skript/expressions/ExprProtocolVersion.java index 24e89444de5..26505c24467 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprProtocolVersion.java +++ b/src/main/java/ch/njol/skript/expressions/ExprProtocolVersion.java @@ -3,7 +3,6 @@ import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.*; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; @@ -34,22 +33,26 @@ """) @Since("2.3") @Events("server list ping") -public class ExprProtocolVersion extends SimpleExpression implements EventRestrictedSyntax { +public class ExprProtocolVersion extends SimpleExpression { static { Skript.registerExpression(ExprProtocolVersion.class, Long.class, ExpressionType.SIMPLE, "[the] [server] [(sent|required|fake)] protocol version [number]"); } + private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); + @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!PAPER_EVENT_EXISTS) { + Skript.error("The protocol version expression requires Paper 1.12.2 or newer"); + return false; + } else if (!getParser().isCurrentEvent(PaperServerListPingEvent.class)) { + Skript.error("The protocol version expression can't be used outside of a server list ping event"); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PaperServerListPingEvent.class); - } - @Override @Nullable public Long[] get(Event e) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprRespawnLocation.java b/src/main/java/ch/njol/skript/expressions/ExprRespawnLocation.java index 9aede4a2bcd..ba480d51225 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRespawnLocation.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRespawnLocation.java @@ -1,6 +1,5 @@ package ch.njol.skript.expressions; -import ch.njol.skript.lang.EventRestrictedSyntax; import org.bukkit.Location; import org.bukkit.event.Event; import org.bukkit.event.player.PlayerRespawnEvent; @@ -28,7 +27,7 @@ set respawn location to {example::spawn} """) @Since("2.2-dev35") -public class ExprRespawnLocation extends SimpleExpression implements EventRestrictedSyntax { +public class ExprRespawnLocation extends SimpleExpression { static { Skript.registerExpression(ExprRespawnLocation.class, Location.class, ExpressionType.SIMPLE, "[the] respawn location"); @@ -36,13 +35,12 @@ public class ExprRespawnLocation extends SimpleExpression implements E @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(PlayerRespawnEvent.class)) { + Skript.error("The expression 'respawn location' may only be used in the respawn event", ErrorQuality.SEMANTIC_ERROR); + return false; + } return true; } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PlayerRespawnEvent.class); - } @Override @Nullable diff --git a/src/main/java/ch/njol/skript/expressions/ExprServerIcon.java b/src/main/java/ch/njol/skript/expressions/ExprServerIcon.java index e90046abb6f..722008dc7a9 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprServerIcon.java +++ b/src/main/java/ch/njol/skript/expressions/ExprServerIcon.java @@ -35,10 +35,16 @@ public class ExprServerIcon extends SimpleExpression { "[the] [(1¦(default)|2¦(shown|sent))] [server] icon"); } + private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); + private boolean isServerPingEvent, isDefault; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!PAPER_EVENT_EXISTS) { + Skript.error("The server icon expression requires Paper 1.12.2 or newer"); + return false; + } isServerPingEvent = getParser().isCurrentEvent(PaperServerListPingEvent.class); isDefault = (parseResult.mark == 0 && !isServerPingEvent) || parseResult.mark == 1; if (!isServerPingEvent && !isDefault) { @@ -51,8 +57,8 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable public CachedServerIcon[] get(Event e) { - CachedServerIcon icon; - if ((isServerPingEvent && !isDefault)) { + CachedServerIcon icon = null; + if ((isServerPingEvent && !isDefault) && PAPER_EVENT_EXISTS) { if (!(e instanceof PaperServerListPingEvent)) return null; icon = ((PaperServerListPingEvent) e).getServerIcon(); diff --git a/src/main/java/ch/njol/skript/expressions/ExprSourceBlock.java b/src/main/java/ch/njol/skript/expressions/ExprSourceBlock.java index 0274e0c109c..ef2bc7aa5f7 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSourceBlock.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSourceBlock.java @@ -7,13 +7,11 @@ import ch.njol.skript.doc.Events; import ch.njol.skript.doc.Example; import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.block.Block; import org.bukkit.event.Event; import org.bukkit.event.block.BlockSpreadEvent; @@ -28,7 +26,7 @@ set the source block to dirt """) @Since("2.7") -public class ExprSourceBlock extends SimpleExpression implements EventRestrictedSyntax { +public class ExprSourceBlock extends SimpleExpression { static { Skript.registerExpression(ExprSourceBlock.class, Block.class, ExpressionType.SIMPLE, "[the] source block"); @@ -36,14 +34,13 @@ public class ExprSourceBlock extends SimpleExpression implements EventRes @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(BlockSpreadEvent.class)) { + Skript.error("The 'source block' is only usable in a spread event"); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(BlockSpreadEvent.class); - } - @Override protected Block[] get(Event event) { if (!(event instanceof BlockSpreadEvent)) diff --git a/src/main/java/ch/njol/skript/expressions/ExprTamer.java b/src/main/java/ch/njol/skript/expressions/ExprTamer.java index 4cb9419d85e..1f8d9a04242 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTamer.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTamer.java @@ -1,7 +1,5 @@ package ch.njol.skript.expressions; -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.event.entity.EntityTameEvent; @@ -26,7 +24,7 @@ send "someone tamed something!" to console """) @Since("2.2-dev25") -public class ExprTamer extends SimpleExpression implements EventRestrictedSyntax { +public class ExprTamer extends SimpleExpression { static { Skript.registerExpression(ExprTamer.class, Player.class, ExpressionType.SIMPLE, "[the] tamer"); @@ -34,13 +32,12 @@ public class ExprTamer extends SimpleExpression implements EventRestrict @Override public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { + if (!getParser().isCurrentEvent(EntityTameEvent.class)) { + Skript.error("the expression 'tamer' may only be used in the entity tame event."); + return false; + } return true; } - - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(EntityTameEvent.class); - } @Override protected Player[] get(final Event e) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprUnleashReason.java b/src/main/java/ch/njol/skript/expressions/ExprUnleashReason.java index aea0e3fa901..3242d51c154 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprUnleashReason.java +++ b/src/main/java/ch/njol/skript/expressions/ExprUnleashReason.java @@ -1,7 +1,5 @@ package ch.njol.skript.expressions; -import ch.njol.skript.lang.EventRestrictedSyntax; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.bukkit.event.entity.EntityUnleashEvent; import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason; @@ -23,7 +21,7 @@ """) @Events("Leash / Unleash") @Since("2.10") -public class ExprUnleashReason extends EventValueExpression implements EventRestrictedSyntax { +public class ExprUnleashReason extends EventValueExpression { public ExprUnleashReason() { super(UnleashReason.class); @@ -35,14 +33,13 @@ public ExprUnleashReason() { @Override public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!getParser().isCurrentEvent(EntityUnleashEvent.class)) { + Skript.error("The 'unleash reason' expression can only be used in an 'unleash' event"); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(EntityUnleashEvent.class); - } - @Override protected UnleashReason[] get(Event event) { if (!(event instanceof EntityUnleashEvent unleashEvent)) diff --git a/src/main/java/ch/njol/skript/expressions/ExprVersionString.java b/src/main/java/ch/njol/skript/expressions/ExprVersionString.java index 105cf51790b..05b74bb0502 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVersionString.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVersionString.java @@ -3,7 +3,6 @@ import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.*; -import ch.njol.skript.lang.EventRestrictedSyntax; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.SkriptParser.ParseResult; @@ -27,7 +26,9 @@ """) @Since("2.3") @Events("Server List Ping") -public class ExprVersionString extends SimpleExpression implements EventRestrictedSyntax { +public class ExprVersionString extends SimpleExpression { + + private static final boolean PAPER_EVENT_EXISTS = Skript.classExists("com.destroystokyo.paper.event.server.PaperServerListPingEvent"); static { Skript.registerExpression(ExprVersionString.class, String.class, ExpressionType.SIMPLE, "[the] [shown|custom] version [string|text]"); @@ -35,14 +36,16 @@ public class ExprVersionString extends SimpleExpression implements Event @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (!PAPER_EVENT_EXISTS) { + Skript.error("The 'version string' expression requires Paper 1.12.2+"); + return false; + } else if (!getParser().isCurrentEvent(PaperServerListPingEvent.class)) { + Skript.error("The 'version string' expression can't be used outside of a 'server list ping' event"); + return false; + } return true; } - @Override - public Class[] supportedEvents() { - return CollectionUtils.array(PaperServerListPingEvent.class); - } - @Override @Nullable public String[] get(Event event) { diff --git a/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java b/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java index 3aa1baca66f..7e943162e1b 100644 --- a/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java +++ b/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java @@ -39,7 +39,7 @@ public ExprFunctionCall(FunctionReference reference, Class[] exp Class returnType = Utils.getComponentType(functionReturnType); if (CollectionUtils.containsSuperclass(expectedReturnTypes, returnType)) { // Function returns expected type already - this.returnTypes = new Class[] {returnType}; + this.returnTypes = new Class[]{returnType}; this.returnType = (Class) returnType; } else { // Return value needs to be converted @@ -55,12 +55,12 @@ public ExprFunctionCall(FunctionReference reference, Class[] exp if (execute == null) { values = null; } else if (!execute.getClass().isArray()) { - values = new Object[] {execute}; + values = new Object[]{execute}; } else { values = (Object[]) execute; } - String[] keys = reference.function().returnedKeys(); + String[] keys = reference.function().getReturnedKeys().toArray(new String[0]); reference.function().resetReturnValue(); //noinspection unchecked @@ -71,7 +71,7 @@ public ExprFunctionCall(FunctionReference reference, Class[] exp } Converters.convert(values, convertedValues, returnTypes); - if (keys != null) { + if (keys.length > 0) { for (int i = 0; i < convertedValues.length; i++) { if (convertedValues[i] == null) keys[i] = null; diff --git a/src/main/java/ch/njol/skript/lang/function/Function.java b/src/main/java/ch/njol/skript/lang/function/Function.java index ca9ccca28b1..b3eeb2e33d1 100644 --- a/src/main/java/ch/njol/skript/lang/function/Function.java +++ b/src/main/java/ch/njol/skript/lang/function/Function.java @@ -3,7 +3,6 @@ import ch.njol.skript.SkriptConfig; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.KeyProviderExpression; import ch.njol.skript.lang.KeyedValue; import ch.njol.util.coll.CollectionUtils; import org.bukkit.Bukkit; @@ -16,6 +15,8 @@ import org.skriptlang.skript.common.function.ScriptParameter; import java.util.Arrays; +import java.util.Collections; +import java.util.SequencedCollection; /** * Functions can be called using arguments. @@ -188,6 +189,15 @@ public Class type() { return null; } + @Override + public final @NotNull SequencedCollection getReturnedKeys() { + String[] returnedKeys = returnedKeys(); + if (returnedKeys == null || returnedKeys.length == 0) { + return Collections.emptyList(); + } + return Arrays.asList(returnedKeys); + } + /** * Resets the return value of the {@code Function}. * Should be called right after execution. diff --git a/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java b/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java index 9bbba5cc8bb..1af513622bb 100644 --- a/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java +++ b/src/main/java/ch/njol/skript/lang/function/ScriptFunction.java @@ -15,6 +15,7 @@ import org.skriptlang.skript.common.function.Parameters; import java.util.Arrays; +import java.util.SequencedCollection; public class ScriptFunction extends Function implements ReturnHandler { diff --git a/src/main/java/org/skriptlang/skript/bukkit/BukkitModule.java b/src/main/java/org/skriptlang/skript/bukkit/BukkitModule.java index b1e42eadc10..5320eadf503 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/BukkitModule.java +++ b/src/main/java/org/skriptlang/skript/bukkit/BukkitModule.java @@ -11,6 +11,8 @@ import org.skriptlang.skript.bukkit.damagesource.DamageSourceModule; import org.skriptlang.skript.bukkit.entity.EntityModule; import org.skriptlang.skript.bukkit.fishing.FishingModule; +import org.skriptlang.skript.bukkit.functions.BukkitFunctions; +import org.skriptlang.skript.bukkit.functions.VectorFunctions; import org.skriptlang.skript.bukkit.input.InputModule; import org.skriptlang.skript.bukkit.item.ItemModule; import org.skriptlang.skript.bukkit.itemcomponents.ItemComponentModule; @@ -67,6 +69,9 @@ protected void initSelf(SkriptAddon addon) { Classes.registerClass(new PlayerClassInfo()); Classes.registerClass(new SlotClassInfo()); Classes.registerClass(new VectorClassInfo()); + + new BukkitFunctions(this, addon); + new VectorFunctions(this, addon); } @Override diff --git a/src/main/java/org/skriptlang/skript/bukkit/functions/BukkitFunctions.java b/src/main/java/org/skriptlang/skript/bukkit/functions/BukkitFunctions.java new file mode 100644 index 00000000000..4b396f454e8 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/functions/BukkitFunctions.java @@ -0,0 +1,187 @@ +package org.skriptlang.skript.bukkit.functions; + +import ch.njol.skript.lang.function.Functions; +import ch.njol.skript.util.Color; +import ch.njol.skript.util.ColorRGB; +import ch.njol.skript.util.Utils; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.skriptlang.skript.addon.SkriptAddon; +import org.skriptlang.skript.bukkit.BukkitModule; +import org.skriptlang.skript.common.function.DefaultFunction; +import org.skriptlang.skript.common.function.Parameter.Modifier; + +import java.util.UUID; + +/** + * Contains all functions using Bukkit classes which cannot easily be grouped. + */ +public class BukkitFunctions { + + public BukkitFunctions(BukkitModule module, SkriptAddon addon) { + SkriptAddon skript = module.origin(addon).addon(); + + Functions.register(DefaultFunction.builder(skript, "world", World.class) + .description("Gets a world from its name.") + .examples("set {_nether} to world(\"%{_world}%_nether\")") + .since("2.2") + .parameter("name", String.class) + .build(args -> Bukkit.getWorld(args.get("name")))); + + Functions.register(DefaultFunction.builder(skript, "location", Location.class) + .description( + "Creates a location from a world and 3 coordinates, with an optional yaw and pitch.", + "If for whatever reason the world is not found, it will fallback to the server's main world." + ) + .examples(""" + # TELEPORTING + teleport player to location(1,1,1, world "world") + teleport player to location(1,1,1, world "world", 100, 0) + teleport player to location(1,1,1, world "world", yaw of player, pitch of player) + teleport player to location(1,1,1, world of player) + teleport player to location(1,1,1, world("world")) + teleport player to location({_x}, {_y}, {_z}, {_w}, {_yaw}, {_pitch}) + + # SETTING BLOCKS + set block at location(1,1,1, world "world") to stone + set block at location(1,1,1, world "world", 100, 0) to stone + set block at location(1,1,1, world of player) to stone + set block at location(1,1,1, world("world")) to stone + set block at location({_x}, {_y}, {_z}, {_w}) to stone + + # USING VARIABLES + set {_l1} to location(1,1,1) + set {_l2} to location(10,10,10) + set blocks within {_l1} and {_l2} to stone + if player is within {_l1} and {_l2}: + + # OTHER + kill all entities in radius 50 around location(1,65,1, world "world") + delete all entities in radius 25 around location(50,50,50, world "world_nether") + ignite all entities in radius 25 around location(1,1,1, world of player) + """ + ) + .since("2.2") + .parameter("x", Number.class) + .parameter("y", Number.class) + .parameter("z", Number.class) + .parameter("world", World.class, Modifier.OPTIONAL) + .parameter("yaw", Float.class, Modifier.OPTIONAL) + .parameter("pitch", Float.class, Modifier.OPTIONAL) + .build(args -> { + World world = args.getOrDefault("world", Bukkit.getWorlds().getFirst()); + + return new Location(world, + args.get("x").doubleValue(), args.get("y").doubleValue(), args.get("z").doubleValue(), + args.getOrDefault("yaw", 0f), args.getOrDefault("pitch", 0f)); + })); + + Functions.register(DefaultFunction.builder(skript, "calcExperience", Long.class) + .description("Calculates the total amount of experience needed to achieve given level from scratch in Minecraft.") + .since("2.2-dev32") + .parameter("level", Long.class, Modifier.ranged(0L, Long.MAX_VALUE)) + .build(args -> { + long level = args.get("level"); + long exp; + if (level <= 0) { + exp = 0; + } else if (level <= 15) { + exp = level * level + 6 * level; + } else if (level <= 30) { // Truncating decimal parts probably works + exp = (int) (2.5 * level * level - 40.5 * level + 360); + } else { // Half experience points do not exist, anyway + exp = (int) (4.5 * level * level - 162.5 * level + 2220); + } + + return exp; + })); + + Functions.register(DefaultFunction.builder(skript, "rgb", Color.class) + .description(""" + Returns a RGB color from the given red, green and blue parameters. + Alpha values can be added optionally but these only take affect in certain situations, like text display backgrounds.""") + .examples( + "dye player's leggings rgb(120, 30, 45)", + "set the colour of a text display to rgb(10, 50, 100, 50)" + ) + .since("2.5, 2.10 (alpha)") + .parameter("red", Long.class, Modifier.ranged(0, 255)) + .parameter("green", Long.class, Modifier.ranged(0, 255)) + .parameter("blue", Long.class, Modifier.ranged(0, 255)) + .parameter("alpha", Long.class, Modifier.ranged(0, 255), Modifier.OPTIONAL) + .build(args -> ColorRGB.fromRGBA( + args.get("red").intValue(), + args.get("green").intValue(), + args.get("blue").intValue(), + args.getOrDefault("alpha", 255L).intValue() + ))); + + Functions.register(DefaultFunction.builder(skript, "player", Player.class) + .description( + "Returns an online player from their name or UUID, if player is offline function will return nothing.", + "Setting 'getExactPlayer' parameter to true will return the player whose name is exactly equal to the provided name instead of returning a player that their name starts with the provided name." + ) + .examples( + "set {_p} to player(\"Notch\") # will return an online player whose name is or starts with 'Notch'", + "set {_p} to player(\"Notch\", true) # will return the only online player whose name is 'Notch'", + "set {_p} to player(\"069a79f4-44e9-4726-a5be-fca90e38aaf5\") # if player is offline" + ) + .since("2.8.0") + .parameter("nameOrUUID", String.class) + .parameter("getExactPlayer", Boolean.class, Modifier.OPTIONAL) + .build(args -> { + String name = args.get("nameOrUUID"); + boolean isExact = args.getOrDefault("getExactPlayer", false); + + UUID uuid = null; + if (name.length() > 16 || name.contains("-")) { + if (Utils.isValidUUID(name)) + uuid = UUID.fromString(name); + } + + if (uuid != null) + return Bukkit.getPlayer(uuid); + if (isExact) + return Bukkit.getPlayerExact(name); + return Bukkit.getPlayer(name); + })); + + Functions.register(DefaultFunction.builder(skript, "offlineplayer", OfflinePlayer.class) + .description( + "Returns a offline player from their name or UUID. This function will still return the player if they're online. " + + "If Paper 1.16.5+ is used, the 'allowLookup' parameter can be set to false to prevent this function from doing a " + + "web lookup for players who have not joined before. Lookups can cause lag spikes of up to multiple seconds, so " + + "use offline players with caution." + ) + .examples( + "set {_p} to offlineplayer(\"Notch\")", + "set {_p} to offlineplayer(\"069a79f4-44e9-4726-a5be-fca90e38aaf5\")", + "set {_p} to offlineplayer(\"Notch\", false)" + ) + .since("2.8.0, 2.9.0 (prevent lookups)") + .parameter("nameOrUUID", String.class) + .parameter("allowLookups", Boolean.class, Modifier.OPTIONAL) + .build(args -> { + String name = args.get("nameOrUUID"); + UUID uuid = null; + if (name.length() > 16 || name.contains("-")) { // shortcut + if (Utils.isValidUUID(name)) + uuid = UUID.fromString(name); + } + OfflinePlayer result; + + if (uuid != null) { + result = Bukkit.getOfflinePlayer(uuid); // doesn't do lookups + } else if (!args.getOrDefault("allowLookups", true)) { + result = Bukkit.getOfflinePlayerIfCached(name); + } else { + result = Bukkit.getOfflinePlayer(name); + } + + return result; + })); + } +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/functions/VectorFunctions.java b/src/main/java/org/skriptlang/skript/bukkit/functions/VectorFunctions.java new file mode 100644 index 00000000000..aa2a615cf64 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/functions/VectorFunctions.java @@ -0,0 +1,79 @@ +package org.skriptlang.skript.bukkit.functions; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.function.Functions; +import org.bukkit.util.Vector; +import org.joml.AxisAngle4f; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.skriptlang.skript.addon.SkriptAddon; +import org.skriptlang.skript.bukkit.BukkitModule; +import org.skriptlang.skript.common.function.DefaultFunction; + +/** + * Contains all functions using Bukkit vectors. + */ +public class VectorFunctions { + + public VectorFunctions(BukkitModule module, SkriptAddon addon) { + SkriptAddon skript = module.origin(addon).addon(); + + Functions.register(DefaultFunction.builder(skript, "vector", Vector.class) + .description("Creates a vector from a single argument. Equivalent to vector(n, n, n).") + .examples("vector(1) # = vector(1, 1, 1)") + .since("2.15") + .parameter("n", Number.class) + .build(args -> { + double value = args.get("n").doubleValue(); + return new Vector(value, value, value); + })); + + Functions.register(DefaultFunction.builder(skript, "vector", Vector.class) + .description("Creates a new vector, which can be used with various expressions, effects and functions.") + .examples("vector(0, 0, 0)") + .since("2.2-dev23") + .parameter("x", Number.class) + .parameter("y", Number.class) + .parameter("z", Number.class) + .build(args -> new Vector( + args.get("x").doubleValue(), + args.get("y").doubleValue(), + args.get("z").doubleValue() + ))); + + if (Skript.classExists("org.joml.Quaternionf")) { + Functions.register(DefaultFunction.builder(skript, "quaternion", Quaternionf.class) + .description("Returns a quaternion from the given W, X, Y and Z parameters. ") + .examples("quaternion(1, 5.6, 45.21, 10)") + .since("2.10") + .parameter("w", Number.class) + .parameter("x", Number.class) + .parameter("y", Number.class) + .parameter("z", Number.class) + .build(args -> { + double w = args.get("w").doubleValue(); + double x = args.get("x").doubleValue(); + double y = args.get("y").doubleValue(); + double z = args.get("z").doubleValue(); + return new Quaternionf(x, y, z, w); + })); + } + + if (Skript.classExists("org.joml.AxisAngle4f")) { + Functions.register(DefaultFunction.builder(skript, "axisAngle", Quaternionf.class) + .description("Returns a quaternion from the given angle (in degrees) and axis (as a vector). This represents a rotation around the given axis by the given angle.") + .examples("axisAngle(90, (vector from player's facing))") + .since("2.10") + .parameter("angle", Number.class) + .parameter("axis", Vector.class) + .build(args -> { + float angle = (float) (args.get("angle").floatValue() / 180 * Math.PI); + Vector v = args.get("axis"); + if (v.isZero() || !Double.isFinite(v.getX()) || !Double.isFinite(v.getY()) || !Double.isFinite(v.getZ())) + return null; + Vector3f axis = v.toVector3f(); + return new Quaternionf(new AxisAngle4f(angle, axis)); + })); + } + } +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/text/elements/expressions/ExprStringColor.java b/src/main/java/org/skriptlang/skript/bukkit/text/elements/expressions/ExprStringColor.java index b8ec9c58ed1..2d1b3c96710 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/text/elements/expressions/ExprStringColor.java +++ b/src/main/java/org/skriptlang/skript/bukkit/text/elements/expressions/ExprStringColor.java @@ -136,9 +136,6 @@ private List getColors(String string) { continue; } String tag = string.substring(index + 1, end); - if (tag.isEmpty()) { // malformed (empty tag) - continue; - } if (tag.charAt(0) == '#') { // should be a hex tag = tag.substring(1); int tagLength = tag.length(); diff --git a/src/main/java/org/skriptlang/skript/common/CommonModule.java b/src/main/java/org/skriptlang/skript/common/CommonModule.java index c3569dc0bc1..fd5675d8bc0 100644 --- a/src/main/java/org/skriptlang/skript/common/CommonModule.java +++ b/src/main/java/org/skriptlang/skript/common/CommonModule.java @@ -5,6 +5,9 @@ import org.skriptlang.skript.addon.HierarchicalAddonModule; import org.skriptlang.skript.addon.SkriptAddon; import org.skriptlang.skript.common.elements.expressions.*; +import org.skriptlang.skript.common.elements.functions.MathFunctions; +import org.skriptlang.skript.common.elements.functions.StringFunctions; +import org.skriptlang.skript.common.elements.functions.TimeFunctions; import org.skriptlang.skript.common.properties.PropertiesModule; import org.skriptlang.skript.common.types.*; @@ -34,6 +37,10 @@ protected void loadSelf(SkriptAddon addon) { ExprHexCode::register, ExprRecursiveSize::register ); + + new MathFunctions(); + new StringFunctions(); + new TimeFunctions(); } @Override diff --git a/src/main/java/org/skriptlang/skript/common/elements/functions/MathFunctions.java b/src/main/java/org/skriptlang/skript/common/elements/functions/MathFunctions.java new file mode 100644 index 00000000000..0418dfa3bc5 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/common/elements/functions/MathFunctions.java @@ -0,0 +1,568 @@ +package org.skriptlang.skript.common.elements.functions; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.KeyedValue; +import ch.njol.skript.lang.function.Functions; +import ch.njol.skript.util.Contract; +import ch.njol.util.Math2; +import ch.njol.util.StringUtils; +import org.skriptlang.skript.addon.SkriptAddon; +import org.skriptlang.skript.common.function.DefaultFunction; +import org.skriptlang.skript.common.function.Parameter.Modifier; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Contains all generic math functions. + */ +public class MathFunctions { + + /** + * @param n The number. + * @return a number in a string representation with at most 4 digits after the decimal point. + */ + private static String str(double n) { + return StringUtils.toString(n, 4); + } + + static { + SkriptAddon skript = Skript.instance(); + + Functions.register(DefaultFunction.builder(skript, "isNaN", Boolean.class) + .description("Returns true if the input is NaN (not a number).") + .examples("isNaN(0) # false", "isNaN(0/0) # true", "isNaN(sqrt(-1)) # true") + .since("2.8.0") + .parameter("n", Number.class) + .build(args -> Double.isNaN(args.get("n").doubleValue()))); + + Functions.register(DefaultFunction.builder(skript, "floor", Long.class) + .description("Rounds a number down, i.e. returns the closest integer smaller than or equal to the argument.") + .examples("floor(2.34) = 2", "floor(2) = 2", "floor(2.99) = 2") + .since("2.2") + .parameter("n", Number.class) + .build(args -> { + Number value = args.get("n"); + + if (value instanceof Long l) + return l; + + return Math2.floor(value.doubleValue()); + })); + + Functions.register(DefaultFunction.builder(skript, "round", Number.class) + .description("Rounds a number, i.e. returns the closest integer to the argument. Place a second argument to define the decimal placement.") + .examples("round(2.34) = 2", "round(2) = 2", "round(2.99) = 3", "round(2.5) = 3") + .since("2.2, 2.7 (decimal placement)") + .parameter("n", Number.class) + .parameter("decimals", Number.class, Modifier.OPTIONAL) + .build(args -> { + if (args.get("n") instanceof Long longValue) + return longValue; + double value = args.get("n").doubleValue(); + if (!Double.isFinite(value)) + return value; + + double placementDouble = args.getOrDefault("decimals", 0).doubleValue(); + if (!Double.isFinite(placementDouble) || placementDouble >= Integer.MAX_VALUE || placementDouble <= Integer.MIN_VALUE) + return Double.NaN; + int placement = (int) placementDouble; + if (placement == 0) + return Math2.round(value); + if (placement >= 0) { + BigDecimal decimal = new BigDecimal(Double.toString(value)); + decimal = decimal.setScale(placement, RoundingMode.HALF_UP); + return decimal.doubleValue(); + } + long rounded = Math2.round(value); + return (int) Math2.round(rounded * Math.pow(10.0, placement)) / Math.pow(10.0, placement); + })); + + Functions.register(DefaultFunction.builder(skript, "ceil", Long.class) + .description("Rounds a number up, i.e. returns the closest integer larger than or equal to the argument.") + .examples("ceil(2.34) = 3", "ceil(2) = 2", "ceil(2.99) = 3") + .since("2.2") + .parameter("n", Number.class) + .build(args -> { + Number num = args.get("n"); + if (num instanceof Long lng) + return lng; + return Math2.ceil(num.doubleValue()); + })); + + Functions.register(DefaultFunction.builder(skript, "ceiling", Long.class) + .description("Alias of ceil.") + .examples("ceiling(2.34) = 3", "ceiling(2) = 2", "ceiling(2.99) = 3") + .since("2.2") + .parameter("n", Number.class) + .build(args -> { + Number num = args.get("n"); + if (num instanceof Long lng) + return lng; + return Math2.ceil(num.doubleValue()); + })); + + Functions.register(DefaultFunction.builder(skript, "abs", Number.class) + .description("Returns the absolute value of the argument, i.e. makes the argument positive.") + .examples("abs(3) = 3", "abs(-2) = 2") + .since("2.2") + .parameter("n", Number.class) + .build(args -> { + Number n = args.get("n"); + if (n instanceof Byte || n instanceof Short || n instanceof Integer || n instanceof Long) + return Math.abs(n.longValue()); + return Math.abs(n.doubleValue()); + })); + + Functions.register(DefaultFunction.builder(skript, "mod", Number.class) + .description("Returns the modulo of the given arguments, i.e. the remainder of the division d/m, where d and m are the arguments of this function.", + "The returned value is always positive. Returns NaN (not a number) if the second argument is zero.") + .examples("mod(3, 2) = 1", "mod(256436, 100) = 36", "mod(-1, 10) = 9") + .since("2.2") + .parameter("d", Number.class) + .parameter("m", Number.class) + .build(args -> { + Number d = args.get("d"); + Number m = args.get("m"); + double mm = m.doubleValue(); + if (mm == 0) + return Double.NaN; + return Math2.mod(d.doubleValue(), mm); + })); + + Functions.register(DefaultFunction.builder(skript, "exp", Number.class) + .description("The exponential function.") + .examples("exp(0) = 1", "exp(1) = " + str(Math.exp(1))) + .since("2.2") + .parameter("n", Number.class) + .build(args -> Math.exp(args.get("n").doubleValue()))); + + Functions.register(DefaultFunction.builder(skript, "ln", Number.class) + .description("The natural logarithm.", + "Returns NaN (not a number) if the argument is negative.") + .examples("ln(1) = 0", "ln(exp(5)) = 5", "ln(2) = " + StringUtils.toString(Math.log(2), 4)) + .since("2.2") + .parameter("n", Number.class) + .build(args -> Math.log(args.get("n").doubleValue()))); + + Functions.register(DefaultFunction.builder(skript, "log", Number.class) + .description("A logarithm, with base 10 if none is specified. This is the inverse operation to exponentiation (for positive bases only), i.e. log(base ^ exponent, base) = exponent for any positive number 'base' and any number 'exponent'.", + "Another useful equation is base ^ log(a, base) = a for any numbers 'base' and 'a'.", + "Please note that due to how numbers are represented in computers, these equations do not hold for all numbers, as the computed values may slightly differ from the correct value.", + "Returns NaN (not a number) if any of the arguments are negative.") + .examples("log(100) = 2 # 10^2 = 100", "log(16, 2) = 4 # 2^4 = 16") + .since("2.2") + .parameter("n", Number.class) + .parameter("base", Number.class, Modifier.OPTIONAL) + .build(args -> { + double base = args.getOrDefault("base", 10).doubleValue(); + double n = args.get("n").doubleValue(); + return Math.log10(n) / Math.log10(base); + })); + + Functions.register(DefaultFunction.builder(skript, "sqrt", Number.class) + .description("The square root, which is the inverse operation to squaring a number (for positive numbers only). This is the same as (argument) ^ (1/2) – other roots can be calculated via number ^ (1/root), e.g. set {_l} to {_volume}^(1/3).", + "Returns NaN (not a number) if the argument is negative.") + .examples("sqrt(4) = 2", "sqrt(2) = " + str(Math.sqrt(2)), "sqrt(-1) = " + str(Math.sqrt(-1))) + .since("2.2") + .parameter("n", Number.class) + .build(args -> Math.sqrt(args.get("n").doubleValue()))); + + // trigonometry + + Functions.register(DefaultFunction.builder(skript, "sin", Number.class) + .description("The sine function. It starts at 0° with a value of 0, goes to 1 at 90°, back to 0 at 180°, to -1 at 270° and then repeats every 360°. Uses degrees, not radians.") + .examples("sin(90) = 1", "sin(60) = " + str(Math.sin(Math.toRadians(60)))) + .since("2.2") + .parameter("n", Number.class) + .build(args -> Math.sin(Math.toRadians(args.get("n").doubleValue())))); + + Functions.register(DefaultFunction.builder(skript, "cos", Number.class) + .description("The cosine function. This is basically the sine shifted by 90°, i.e. cos(a) = sin(a + 90°), for any number a. Uses degrees, not radians.") + .examples("cos(0) = 1", "cos(90) = 0") + .since("2.2") + .parameter("n", Number.class) + .build(args -> Math.cos(Math.toRadians(args.get("n").doubleValue())))); + + Functions.register(DefaultFunction.builder(skript, "tan", Number.class) + .description("The tangent function. This is basically sin(arg)/cos(arg). Uses degrees, not radians.") + .examples("tan(0) = 0", "tan(45) = 1", "tan(89.99) = " + str(Math.tan(Math.toRadians(89.99)))) + .since("2.2") + .parameter("n", Number.class) + .build(args -> Math.tan(Math.toRadians(args.get("n").doubleValue())))); + + Functions.register(DefaultFunction.builder(skript, "asin", Number.class) + .description("The inverse of the sine, also called arcsin. Returns result in degrees, not radians. Only returns values from -90 to 90.") + .examples("asin(0) = 0", "asin(1) = 90", "asin(0.5) = " + str(Math.toDegrees(Math.asin(0.5)))) + .since("2.2") + .parameter("n", Number.class) + .build(args -> Math.toDegrees(Math.asin(args.get("n").doubleValue())))); + + Functions.register(DefaultFunction.builder(skript, "acos", Number.class) + .description("The inverse of the cosine, also called arccos. Returns result in degrees, not radians. Only returns values from 0 to 180.") + .examples("acos(0) = 90", "acos(1) = 0", "acos(0.5) = " + str(Math.toDegrees(Math.asin(0.5)))) + .since("2.2") + .parameter("n", Number.class) + .build(args -> Math.toDegrees(Math.acos(args.get("n").doubleValue())))); + + Functions.register(DefaultFunction.builder(skript, "atan", Number.class) + .description("The inverse of the tangent, also called arctan. Returns result in degrees, not radians. Only returns values from -90 to 90.") + .examples("atan(0) = 0", "atan(1) = 45", "atan(10000) = " + str(Math.toDegrees(Math.atan(10000)))) + .since("2.2") + .parameter("n", Number.class) + .build(args -> Math.toDegrees(Math.atan(args.get("n").doubleValue())))); + + Functions.register(DefaultFunction.builder(skript, "atan2", Number.class) + .description("Similar to atan, but requires two coordinates and returns values from -180 to 180.", + "The returned angle is measured counterclockwise in a standard mathematical coordinate system (x to the right, y to the top).") + .examples("atan2(0, 1) = 0", "atan2(10, 0) = 90", "atan2(-10, 5) = " + str(Math.toDegrees(Math.atan2(-10, 5)))) + .since("2.2") + .parameter("x", Number.class) + .parameter("y", Number.class) + .build(args -> Math.toDegrees(Math.atan2(args.get("y").doubleValue(), args.get("x").doubleValue())))); + + // more stuff + + Functions.register(DefaultFunction.builder(skript, "sum", Number.class) + .description("Sums a list of numbers.") + .examples("sum(1) = 1", "sum(2, 3, 4) = 9", "sum({some list variable::*})", "sum(2, {_v::*}, and the player's y-coordinate)") + .since("2.2") + .parameter("ns", Number[].class) + .build(args -> { + Number[] ns = args.get("ns"); + if (ns.length == 0) + return null; + + double sum = ns[0].doubleValue(); + for (int i = 1; i < ns.length; i++) + sum += ns[i].doubleValue(); + return sum; + })); + + Functions.register(DefaultFunction.builder(skript, "product", Number.class) + .description("Calculates the product of a list of numbers.") + .examples("product(1) = 1", "product(2, 3, 4) = 24", "product({some list variable::*})", "product(2, {_v::*}, and the player's y-coordinate)") + .since("2.2") + .parameter("ns", Number[].class) + .build(args -> { + Number[] ns = args.get("ns"); + if (ns.length == 0) + return null; + + double product = ns[0].doubleValue(); + for (int i = 1; i < ns.length; i++) + product *= ns[i].doubleValue(); + return product; + })); + + Functions.register(DefaultFunction.builder(skript, "max", Number.class) + .description("Returns the maximum number from a list of numbers.") + .examples("max(1) = 1", "max(1, 2, 3, 4) = 4", "max({some list variable::*})") + .since("2.2") + .parameter("ns", Number[].class) + .build(args -> { + Number[] ns = args.get("ns"); + if (ns.length == 0) + return null; + + double max = ns[0].doubleValue(); + for (int i = 1; i < ns.length; i++) { + double d = ns[i].doubleValue(); + if (d > max || Double.isNaN(max)) + max = d; + } + return max; + })); + + Functions.register(DefaultFunction.builder(skript, "min", Number.class) + .description("Returns the minimum number from a list of numbers.") + .examples("min(1) = 1", "min(1, 2, 3, 4) = 1", "min({some list variable::*})") + .since("2.2") + .parameter("ns", Number[].class) + .build(args -> { + Number[] ns = args.get("ns"); + if (ns.length == 0) + return null; + + double min = ns[0].doubleValue(); + for (int i = 1; i < ns.length; i++) { + double d = ns[i].doubleValue(); + if (d < min || Double.isNaN(min)) + min = d; + } + return min; + })); + + Functions.register(DefaultFunction.keyedBuilder(skript, "clamp", Number[].class) + .description("Clamps one or more values between two numbers.", "This function retains indices") + .examples( + "clamp(5, 0, 10) = 5", + "clamp(5.5, 0, 5) = 5", + "clamp(0.25, 0, 0.5) = 0.25", + "clamp(5, 7, 10) = 7", + "clamp((5, 0, 10, 9, 13), 7, 10) = (7, 7, 10, 9, 10)", + "set {_clamped::*} to clamp({_values::*}, 0, 10)") + .since("2.8.0") + .parameter("values", Number[].class, Modifier.KEYED) + .parameter("min", Number.class) + .parameter("max", Number.class) + .contract(new Contract() { + @Override + public boolean isSingle(Expression... arguments) { + return arguments[0].isSingle(); + } + + @Override + public Class getReturnType(Expression... arguments) { + return Number.class; + } + }) + .build(args -> { + KeyedValue[] values = args.get("values"); + return KeyedValue.unzip(values).keys(); + }, args -> { + KeyedValue[] values = args.get("values"); + Double[] clampedValues = new Double[values.length]; + double min = args.get("min").doubleValue(); + double max = args.get("max").doubleValue(); + // we'll be nice and swap them if they're in the wrong order + double trueMin = Math.min(min, max); + double trueMax = Math.max(min, max); + for (int i = 0; i < values.length; i++) { + double value = values[i].value().doubleValue(); + if (!Double.isNaN(value) && !Double.isNaN(trueMin) && !Double.isNaN(trueMax)) { + clampedValues[i] = Math.clamp(value, trueMin, trueMax); + } else { + clampedValues[i] = Double.NaN; + } + } + return clampedValues; + })); + + // statistics + + Functions.register(DefaultFunction.builder(skript, "mean", Number.class) + .description( + "Get the mean (average) of a list of numbers.", + "You cannot get the mean of a set of numbers that includes infinity or NaN." + ) + .examples( + "mean(1, 2, 3) = 2", + "mean(0, 5, 10) = 5", + "mean(13, 97, 376, 709) = 298.75" + ) + .since("2.11") + .parameter("numbers", Number[].class) + .build(args -> { + Number[] numbers = args.get("numbers"); + Double total = 0d; + int length = numbers.length; + for (Number number : numbers) { + if (Double.isInfinite(number.doubleValue()) || Double.isNaN(number.doubleValue())) + return null; + if (total.isInfinite() || total.isNaN()) + return null; + total += number.doubleValue() / length; + } + return total; + })); + + Functions.register(DefaultFunction.builder(skript, "median", Number.class) + .description( + "Get the middle value of a sorted list of numbers. " + + "If the list has an even number of values, the median is the average of the two middle numbers.", + "You cannot get the median of a set of numbers that includes NaN." + ) + .examples( + "median(1, 2, 3, 4, 5) = 3", + "median(1, 2, 3, 4, 5, 6) = 3.5", + "median(0, 123, 456, 789) = 289.5" + ) + .since("2.11") + .parameter("numbers", Number[].class) + .build(args -> { + Number[] params = args.get("numbers"); + AtomicBoolean invalid = new AtomicBoolean(false); + // median requires the numbers to be sorted from lowest to biggest + Number[] sorted = Arrays.stream(params) + .filter(object -> { + if (!(object instanceof Number number)) + return false; + if (Double.isNaN(number.doubleValue())) { + invalid.set(true); + return false; + } + return true; + }) + .sorted(((o1, o2) -> { + Double n1 = o1.doubleValue(); + Double n2 = o2.doubleValue(); + return n1.compareTo(n2); + })) + .toArray(Number[]::new); + if (invalid.get()) + return null; + int size = sorted.length; + // If the size of numbers provided is odd, we can just grab the middle number + if (size % 2 == 1) + return sorted[Math2.ceil(size / 2)]; + // If not, we grab the rounded up and rounded down numbers, then get the average of those + int half = size / 2; + double first = (sorted[half - 1]).doubleValue(); + double second = (sorted[half]).doubleValue(); + return (first + second) / 2; + })); + + Functions.register(DefaultFunction.builder(skript, "factorial", Number.class) + .description( + "Get the factorial of a number.", + "Getting the factorial of any number above 21 will return an approximation, not an exact value.", + "Any number after 170 will always return Infinity.", + "Should not be used to calculate permutations or combinations manually." + ) + .examples( + "factorial(0) = 1", + "factorial(3) = 3*2*1 = 6", + "factorial(5) = 5*4*3*2*1 = 120", + "factorial(171) = Infinity" + ) + .since("2.11") + .parameter("number", Number.class, Modifier.ranged(0L, Long.MAX_VALUE)) + .build(args -> { + Double number = args.get("number").doubleValue(); + if (number < 0) { + return null; + } else if (number <= 1) { // 0 and 1 + return 1; + } else if (number > 170) { + return Double.POSITIVE_INFINITY; + } + Double result = 1d; + for (double i = number; i > 1; i--) { + if (result.isInfinite() || result.isNaN()) + break; + result *= i; + } + return result; + })); + + Functions.register(DefaultFunction.builder(skript, "root", Number.class) + .description("Calculates the nth root of a number.") + .examples( + "root(2, 4) = 2 # same as sqrt(4)", + "root(4, 16) = 2", + "root(-4, 16) = 0.5 # same as 16^(-1/4)" + ) + .since("2.11") + .parameter("n", Number.class) + .parameter("number", Number.class, Modifier.ranged(0L, Long.MAX_VALUE)) + .build(args -> { + Double n = args.get("n").doubleValue(); + Double number = args.get("number").doubleValue(); + if (n == 0) { + return null; + } else if (n == 1) { + return number; + } else if (n == 2) { + return Math.sqrt(number); + } + return Math.pow(number, (1 / n)); + })); + + Functions.register(DefaultFunction.builder(skript, "permutations", Number.class) + .description( + "Get the number of possible ordered arrangements from 1 to 'options' with each arrangement having a size equal to 'selected'", + "For example, permutations with 3 options and an arrangement size of 1, returns 3: (1), (2), (3)", + "Permutations with 3 options and an arrangement size of 2 returns 6: (1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)", + "Note that the bigger the 'options' and lower the 'selected' may result in approximations or even infinity values.", + "Permutations differ from combinations in that permutations account for the arrangement of elements within the set, " + + "whereas combinations focus on unique sets and ignore the order of elements.", + "Example: (1, 2) and (2, 1) are two distinct permutations because the positions of '1' and '2' are different, " + + "but they represent a single combination since order doesn't matter in combinations." + ) + .examples( + "permutations(10, 2) = 90", + "permutations(10, 4) = 5040", + "permutations(size of {some list::*}, 2)" + ) + .since("2.11") + .parameter("options", Number.class, Modifier.ranged(0L, Long.MAX_VALUE)) + .parameter("selected", Number.class, Modifier.ranged(0L, Long.MAX_VALUE)) + .build(args -> { + Double options = args.get("options").doubleValue(); + Double selected = args.get("selected").doubleValue(); + if (selected > options || selected < 0) { // Illegal argument + return null; + } else if (selected.equals(0d)) { // Will always be 1 + return 1; + } else if (selected.equals(1d)) { // Will always be the number from 'options' + return options; + } + // We can simplify this as there will always be a factorial that can cancel out + // Example: options = 10, selected = 2; 10!/(10-2)! = 10!/8! + // We can deduce that 10! = (10)(9)(8!) ; allowing the '8!' factorial to cancel out, leaving us with: (10)(9) + // Which allows us to start from 10 and go down to 10-2, but will never reach 8 as 'i' needs to be higher + Double result = 1d; + for (double i = options; i > options - selected; i--) { + if (result.isInfinite() || result.isNaN()) + break; + result *= i; + } + return result; + })); + + Functions.register(DefaultFunction.builder(skript, "combinations", Number.class) + .description( + "Get the number of possible sets from 1 to 'options' with each set having a size equal to 'selected'", + "For example, a combination with 3 options and a set size of 1, returns 3: (1), (2), (3)", + "A combination of 3 options with a set size of 2 returns 3: (1, 2), (1, 3), (2, 3)", + "Note that the bigger the 'options' and lower the 'selected' may result in approximations or even infinity values.", + "Combinations differ from permutations in that combinations focus on unique sets, ignoring the order of elements, " + + "whereas permutations account for the arrangement of elements within the set.", + "Example: (1, 2) and (2, 1) represent a single combination since order doesn't matter in combinations, " + + "but they are two distinct permutations because permutations consider the order." + ) + .examples( + "combinations(10, 8) = 45", + "combinations(5, 3) = 10", + "combinations(size of {some list::*}, 2)" + ) + .since("2.11") + .parameter("options", Number.class, Modifier.ranged(0L, Long.MAX_VALUE)) + .parameter("selected", Number.class, Modifier.ranged(0L, Long.MAX_VALUE)) + .build(args -> { + Double options = args.get("options").doubleValue(); + Double selected = args.get("selected").doubleValue(); + if (selected > options || selected < 0) { // Illegal arguments + return null; + } else if (selected.equals(0d)) { // Will always return 1 + return 1; + } else if (selected.equals(1d)) { // Will always be the number from 'options' + return options; + } + // By the same reasoning from 'permutations' there will always be a factorial that can cancel out + // Example: options = 10, selected = 2 ; 10!/(10-2)!(2!) = 10!/(8!)(2!) + // 10! = (10)(9)(8!) ; the 8! cancel out, leaving us with: (10)(9)/2! + // 'top' will calculate the leftovers in the numerator: (10)(9) + Double top = 1d; + for (double i = options; i > options - selected; i--) { + if (top.isInfinite() || top.isNaN()) + return top; + top *= i; + } + // 'bottom' will calculate the leftovers in the denominator: 2! + Double bottom = selected; + for (double i = selected - 1; i > 1; i--) { + if (bottom.isInfinite() || bottom.isNaN()) + break; + bottom *= i; + } + // Then we divide + return top / bottom; + })); + } + +} diff --git a/src/main/java/org/skriptlang/skript/common/elements/functions/StringFunctions.java b/src/main/java/org/skriptlang/skript/common/elements/functions/StringFunctions.java new file mode 100644 index 00000000000..2b0932a4749 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/common/elements/functions/StringFunctions.java @@ -0,0 +1,169 @@ +package org.skriptlang.skript.common.elements.functions; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.function.Functions; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.Contract; +import ch.njol.skript.util.Utils; +import org.skriptlang.skript.addon.SkriptAddon; +import org.skriptlang.skript.common.function.DefaultFunction; +import org.skriptlang.skript.common.function.Parameter.Modifier; + +import java.text.DecimalFormat; +import java.util.UUID; + +/** + * Contains all generic string functions. + */ +public class StringFunctions { + + private static final DecimalFormat DEFAULT_INTEGER_FORMAT = new DecimalFormat("###,###"); + private static final DecimalFormat DEFAULT_DECIMAL_FORMAT = new DecimalFormat("###,###.##"); + + static { + SkriptAddon skript = Skript.instance(); + + Functions.register(DefaultFunction.builder(skript, "formatNumber", String.class) + .description( + "Converts numbers to human-readable format. By default, '###,###' (e.g. '123,456,789') " + + "will be used for whole numbers and '###,###.##' (e.g. '123,456,789.00) will be used for decimal numbers. " + + "A hashtag '#' represents a digit, a comma ',' is used to separate numbers, and a period '.' is used for decimals. ", + "Will return none if the format is invalid.", + "For further reference, see this article.") + .examples(""" + command /balance: + aliases: bal + executable by: players + trigger: + set {_money} to formatNumber({money::%sender's uuid%}) + send "Your balance: %{_money}%" to sender + """) + .since("2.10") + .parameter("number", Number.class) + .parameter("format", String.class, Modifier.OPTIONAL) + .build(args -> { + Number number = args.get("number"); + String format = args.getOrDefault("format", ""); + + if (format.isEmpty()) { + if (number instanceof Double || number instanceof Float) { + return DEFAULT_DECIMAL_FORMAT.format(number); + } else { + return DEFAULT_INTEGER_FORMAT.format(number); + } + } + + try { + return new DecimalFormat(format).format(number); + } catch (IllegalArgumentException e) { + return null; // invalid format + } + })); + + Functions.register(DefaultFunction.builder(skript, "uuid", UUID.class) + .description("Returns a UUID from the given string. The string must be in the format of a UUID.") + .examples("uuid(\"069a79f4-44e9-4726-a5be-fca90e38aaf5\")") + .since("2.11") + .parameter("uuid", String.class) + .build(args -> { + String uuid = args.get("uuid"); + if (Utils.isValidUUID(uuid)) + return UUID.fromString(uuid); + return null; + })); + + Functions.register(DefaultFunction.builder(skript, "concat", String.class) + .description("Joins the provided texts (and other things) into a single text.") + .examples("concat(\"hello \", \"there\") # hello there", + "concat(\"foo \", 100, \" bar\") # foo 100 bar") + .since("2.9.0") + .parameter("texts", Object[].class) + .build(args -> { + StringBuilder builder = new StringBuilder(); + Object[] objects = args.get("texts"); + for (Object object : objects) { + builder.append(Classes.toString(object)); + } + return builder.toString(); + })); + + Functions.register(DefaultFunction.builder(skript, "toBase", String[].class) + .description(""" + Turns a number in a string using a specific base (decimal, hexadecimal, octal). + For example, converting 32 to hexadecimal (base 16) would be 'toBase(32, 16)', which would return "20". + You can use any base between 2 and 36. + """) + .examples( + "send \"Decode this binary number for a prize! %toBase({_guess}, 2)%\"" + ) + .since("2.14") + .parameter("n", Long[].class) + .parameter("base", Long.class, Modifier.ranged(2, 36)) + .contract(new Contract() { + @Override + public boolean isSingle(Expression... arguments) { + return arguments[0].isSingle(); + } + + @Override + public Class getReturnType(Expression... arguments) { + return String.class; + } + }) + .build(args -> { + Long[] n = args.get("n"); + Long base = args.get("base"); + String[] results = new String[n.length]; + for (int i = 0; i < n.length; i++) { + results[i] = Long.toString(n[i], base.intValue()); + } + return results; + })); + + Functions.register(DefaultFunction.builder(skript, "fromBase", Long[].class) + .description(""" + Turns a text version of a number in a specific base (decimal, hexadecimal, octal) into an actual number. + For example, converting "20" in hexadecimal (base 16) would be 'fromBase("20", 16)', which would return 32. + You can use any base between 2 and 36. + """) + .examples(""" + # /binaryText 01110011 01101011 01110010 01101001 01110000 01110100 00100001 + # sends "skript!" + command binaryText : + trigger: + set {_characters::*} to argument split at " " without trailing empty string + transform {_characters::*} with fromBase(input, 2) # convert to codepoints + transform {_characters::*} with character from codepoint input # convert to characters + send join {_characters::*} + """) + .since("2.14") + .parameter("string value", String[].class) + .parameter("base", Long.class, Modifier.ranged(2, 36)) + .contract(new Contract() { + @Override + public boolean isSingle(Expression... arguments) { + return arguments[0].isSingle(); + } + + @Override + public Class getReturnType(Expression... arguments) { + return Long.class; + } + }) + .build(args -> { + String[] n = args.get("string value"); + Long base = args.get("base"); + Long[] results = new Long[n.length]; + try { + for (int i = 0; i < n.length; i++) { + results[i] = Long.parseLong(n[i], base.intValue()); + } + } catch (NumberFormatException e) { + return null; + } + return results; + })); + } + +} diff --git a/src/main/java/org/skriptlang/skript/common/elements/functions/TimeFunctions.java b/src/main/java/org/skriptlang/skript/common/elements/functions/TimeFunctions.java new file mode 100644 index 00000000000..31dd843e308 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/common/elements/functions/TimeFunctions.java @@ -0,0 +1,77 @@ +package org.skriptlang.skript.common.elements.functions; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.function.Functions; +import ch.njol.skript.util.Date; +import ch.njol.util.Math2; +import org.skriptlang.skript.addon.SkriptAddon; +import org.skriptlang.skript.common.function.DefaultFunction; +import org.skriptlang.skript.common.function.Parameter.Modifier; + +import java.util.Calendar; + +/** + * Contains all generic time functions. + */ +public class TimeFunctions { + + static { + SkriptAddon skript = Skript.instance(); + + int[] fields = { + Calendar.YEAR, Calendar.MONTH, Calendar.DAY_OF_MONTH, + Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND, + Calendar.ZONE_OFFSET, Calendar.DST_OFFSET + }; + int[] offsets = { + 0, -1, 0, + 0, 0, 0, 0, + 0, 0 + }; + double[] scale = { + 1, 1, 1, + 1, 1, 1, 1, + 1000 * 60, 1000 * 60 + }; + double[] relations = { + 1. / 12, 1. / 30, + 1. / 24, 1. / 60, 1. / 60, 1. / 1000, + 0, 0, + 0 + }; + String[] dateParamNames = {"year", "month", "day", "hour", "minute", "second", "millisecond", "zone_offset", "dst_offset"}; + + Functions.register(DefaultFunction.builder(skript, "date", Date.class) + .description("Creates a date from a year, month, and day, and optionally also from hour, minute, second and millisecond.", + "A time zone and DST offset can be specified as well (in minutes), if they are left out the server's time zone and DST offset are used (the created date will not retain this information).") + .examples("date(2014, 10, 1) # 0:00, 1st October 2014", + "date(1990, 3, 5, 14, 30) # 14:30, 5th May 1990", + "date(1999, 12, 31, 23, 59, 59, 999, -3*60, 0) # almost year 2000 in parts of Brazil (-3 hours offset, no DST)") + .since("2.2") + .parameter("year", Number.class) + .parameter("month", Number.class) + .parameter("day", Number.class) + .parameter("hour", Number.class, Modifier.OPTIONAL) + .parameter("minute", Number.class, Modifier.OPTIONAL) + .parameter("second", Number.class, Modifier.OPTIONAL) + .parameter("millisecond", Number.class, Modifier.OPTIONAL) + .parameter("zone_offset", Number.class, Modifier.OPTIONAL) + .parameter("dst_offset", Number.class, Modifier.OPTIONAL) + .build(args -> { + Calendar calendar = Calendar.getInstance(); + calendar.setLenient(true); + double carry = 0; + for (int i = 0; i < fields.length; i++) { + Number n = args.getOrDefault(dateParamNames[i], i < 3 ? 0 : (i == 8 ? Double.NaN : 0)); + double value = n.doubleValue() * scale[i] + offsets[i] + carry; + int v = (int) Math2.floor(value); + carry = (value - v) * relations[i]; + //noinspection MagicConstant + calendar.set(fields[i], v); + } + + return new Date(calendar.getTimeInMillis(), calendar.getTimeZone()); + })); + } + +} diff --git a/src/main/java/org/skriptlang/skript/common/function/DefaultFunction.java b/src/main/java/org/skriptlang/skript/common/function/DefaultFunction.java index ea806f86a0d..feaa94f6bac 100644 --- a/src/main/java/org/skriptlang/skript/common/function/DefaultFunction.java +++ b/src/main/java/org/skriptlang/skript/common/function/DefaultFunction.java @@ -6,6 +6,8 @@ import org.skriptlang.skript.addon.SkriptAddon; import org.skriptlang.skript.common.function.Parameter.Modifier; +import java.util.SequencedCollection; + /** * A function that has been implemented in Java, instead of in Skript. *

@@ -49,6 +51,20 @@ public sealed interface DefaultFunction return new DefaultFunctionImpl.BuilderImpl<>(source, name, returnType); } + /** + * Creates a new builder for a keyed function, + * or a function which is able to return the keys that should be used. + * + * @param name The name of the function. + * @param returnType The type of the function. + * @param The return type. + * @return The keyed builder for a function. + */ + @Contract("_, _, _ -> new") + static @NotNull KeyedBuilder keyedBuilder(@NotNull SkriptAddon source, @NotNull String name, @NotNull Class returnType) { + return new DefaultFunctionImpl.KeyedBuilderImpl<>(source, name, returnType); + } + /** * @return The addon this function was registered for. */ @@ -136,4 +152,90 @@ interface Builder { } + /** + * Represents a builder for {@link DefaultFunction DefaultFunctions} + * which changes a variable's keys during execution. + * + * @param The return type of the function. + */ + interface KeyedBuilder { + + /** + * Sets this function builder's {@link ch.njol.skript.util.Contract}. + * + * @param contract The contract. + * @return This builder. + */ + @Contract("_ -> this") + KeyedBuilder contract(@NotNull ch.njol.skript.util.Contract contract); + + /** + * Sets this function builder's description. + * + * @param description The description. + * @return This builder. + */ + @Contract("_ -> this") + KeyedBuilder description(@NotNull String @NotNull ... description); + + /** + * Sets this function builder's version history. + * + * @param since The version information. + * @return This builder. + */ + @Contract("_ -> this") + KeyedBuilder since(@NotNull String @NotNull ... since); + + /** + * Sets this function builder's examples. + * + * @param examples The examples. + * @return This builder. + */ + @Contract("_ -> this") + KeyedBuilder examples(@NotNull String @NotNull ... examples); + + /** + * Sets this function builder's keywords. + * + * @param keywords The keywords. + * @return This builder. + */ + @Contract("_ -> this") + KeyedBuilder keywords(@NotNull String @NotNull ... keywords); + + /** + * Sets this function builder's requires. + * + * @param requires The requirements. + * @return This builder. + */ + @Contract("_ -> this") + KeyedBuilder requires(@NotNull String @NotNull ... requires); + + /** + * Adds a parameter to this function builder. + * + * @param name The parameter name. + * @param type The type of the parameter. + * @param modifiers The {@link Modifier}s to apply to this parameter. + * @return This builder. + */ + @Contract("_, _, _ -> this") + KeyedBuilder parameter(@NotNull String name, @NotNull Class type, Modifier @NotNull ... modifiers); + + /** + * Completes this builder with the code to execute on call of this function. + * Adds a function for setting the returned keys by this function. + * + * @param keys The function which sets the return keys. + * @param execute The code to execute and the key consumer. + * @return The final function. + */ + DefaultFunction build(@NotNull java.util.function.Function> keys, + @NotNull java.util.function.Function execute); + + } + } diff --git a/src/main/java/org/skriptlang/skript/common/function/DefaultFunctionImpl.java b/src/main/java/org/skriptlang/skript/common/function/DefaultFunctionImpl.java index ff315999c30..37ca88545f0 100644 --- a/src/main/java/org/skriptlang/skript/common/function/DefaultFunctionImpl.java +++ b/src/main/java/org/skriptlang/skript/common/function/DefaultFunctionImpl.java @@ -19,6 +19,7 @@ final class DefaultFunctionImpl extends ch.njol.skript.lang.function.Function private final SkriptAddon source; private final SequencedMap> parameters; private final Function execute; + private final Function> keys; private final List description; private final List since; @@ -26,6 +27,8 @@ final class DefaultFunctionImpl extends ch.njol.skript.lang.function.Function private final List keywords; private final List requires; + private SequencedCollection returnedKeys; + DefaultFunctionImpl( SkriptAddon source, String name, @@ -33,6 +36,7 @@ final class DefaultFunctionImpl extends ch.njol.skript.lang.function.Function Class returnType, boolean single, @Nullable ch.njol.skript.util.Contract contract, Function execute, + Function> keys, String[] description, String[] since, String[] examples, String[] keywords, String[] requires ) { @@ -47,6 +51,7 @@ final class DefaultFunctionImpl extends ch.njol.skript.lang.function.Function this.source = source; this.parameters = parameters; this.execute = execute; + this.keys = keys; this.description = description != null ? List.of(description) : Collections.emptyList(); this.since = since != null ? List.of(since) : Collections.emptyList(); this.examples = examples != null ? List.of(examples) : Collections.emptyList(); @@ -100,6 +105,7 @@ final class DefaultFunctionImpl extends ch.njol.skript.lang.function.Function } FunctionArgumentsImpl arguments = new FunctionArgumentsImpl(args); + returnedKeys = keys.apply(arguments); T result = execute.apply(arguments); if (result == null) { @@ -133,6 +139,7 @@ public T execute(@NotNull FunctionEvent event, @NotNull FunctionArguments arg } } + returnedKeys = keys.apply(arguments); return execute.apply(arguments); } @@ -141,6 +148,11 @@ public boolean resetReturnValue() { return true; } + @Override + public @NotNull String @Nullable [] returnedKeys() { + return returnedKeys.toArray(new String[0]); + } + @Override public @NotNull String name() { return getName(); @@ -181,7 +193,7 @@ public boolean resetReturnValue() { return getSignature(); } - static class BuilderImpl implements DefaultFunctionImpl.Builder { + static class BuilderImpl implements Builder { private final SkriptAddon source; private final String name; @@ -273,30 +285,131 @@ public DefaultFunction build(@NotNull Function execute) Preconditions.checkNotNull(execute, "execute cannot be null"); return new DefaultFunctionImpl<>(source, name, parameters, - returnType, !returnType.isArray(), contract, execute, + returnType, !returnType.isArray(), contract, execute, (args) -> Collections.emptyList(), description, since, examples, keywords, requires); } - /** - * Checks whether the elements in a {@link String} array are null. - * - * @param strings The strings. - */ - private static void checkNotNull(@NotNull String[] strings, @NotNull String message) { - for (String string : strings) { - Preconditions.checkNotNull(string, message); - } + } + + static class KeyedBuilderImpl implements KeyedBuilder { + + private final SkriptAddon source; + private final String name; + private final Class returnType; + private final SequencedMap> parameters = new LinkedHashMap<>(); + + private ch.njol.skript.util.Contract contract = null; + + private String[] description; + private String[] since; + private String[] examples; + private String[] keywords; + private String[] requires; + + KeyedBuilderImpl(@NotNull SkriptAddon source, @NotNull String name, @NotNull Class returnType) { + Preconditions.checkNotNull(source, "source cannot be null"); + Preconditions.checkNotNull(name, "name cannot be null"); + Preconditions.checkNotNull(returnType, "return type cannot be null"); + + this.source = source; + this.name = name; + this.returnType = returnType; + } + + @Override + public KeyedBuilder contract(@NotNull ch.njol.skript.util.Contract contract) { + Preconditions.checkNotNull(contract, "contract cannot be null"); + + this.contract = contract; + return this; + } + + @Override + public KeyedBuilder description(@NotNull String @NotNull ... description) { + Preconditions.checkNotNull(description, "description cannot be null"); + checkNotNull(description, "description contents cannot be null"); + + this.description = description; + return this; + } + + @Override + public KeyedBuilder since(@NotNull String @NotNull ... since) { + Preconditions.checkNotNull(since, "since cannot be null"); + checkNotNull(since, "since contents cannot be null"); + + this.since = since; + return this; + } + + @Override + public KeyedBuilder examples(@NotNull String @NotNull ... examples) { + Preconditions.checkNotNull(examples, "examples cannot be null"); + checkNotNull(examples, "examples contents cannot be null"); + + this.examples = examples; + return this; + } + + @Override + public KeyedBuilder keywords(@NotNull String @NotNull ... keywords) { + Preconditions.checkNotNull(keywords, "keywords cannot be null"); + checkNotNull(keywords, "keywords contents cannot be null"); + + this.keywords = keywords; + return this; + } + + @Override + public KeyedBuilder requires(@NotNull String @NotNull ... requires) { + Preconditions.checkNotNull(keywords, "requires cannot be null"); + checkNotNull(keywords, "requires contents cannot be null"); + + this.requires = requires; + return this; + } + + @Override + public KeyedBuilder parameter(@NotNull String name, @NotNull Class type, Modifier @NotNull ... modifiers) { + Preconditions.checkNotNull(name, "name cannot be null"); + Preconditions.checkNotNull(type, "type cannot be null"); + + parameters.put(name, new DefaultParameter<>(name, type, modifiers)); + return this; + } + + @Override + public DefaultFunction build(@NotNull Function> keys, + @NotNull Function execute) { + Preconditions.checkNotNull(execute, "execute cannot be null"); + Preconditions.checkArgument(parameters.values().stream().anyMatch(it -> it.hasModifier(Modifier.KEYED)), + "build cannot be called without any keyed arguments"); + + return new DefaultFunctionImpl<>(source, name, parameters, + returnType, !returnType.isArray(), contract, execute, keys, + description, since, examples, keywords, requires); } } + /** + * Checks whether the elements in a {@link String} array are null. + * + * @param strings The strings. + */ + private static void checkNotNull(@NotNull String[] strings, @NotNull String message) { + for (String string : strings) { + Preconditions.checkNotNull(string, message); + } + } + /** * A parameter for a {@link DefaultFunction}. * - * @param name The name. - * @param type The type's class. + * @param name The name. + * @param type The type's class. * @param modifiers The modifiers. - * @param The type. + * @param The type. */ record DefaultParameter(String name, Class type, Set modifiers) implements Parameter { diff --git a/src/main/java/org/skriptlang/skript/common/function/Function.java b/src/main/java/org/skriptlang/skript/common/function/Function.java index 5266f57d2ad..4948427f244 100644 --- a/src/main/java/org/skriptlang/skript/common/function/Function.java +++ b/src/main/java/org/skriptlang/skript/common/function/Function.java @@ -5,7 +5,8 @@ import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.ApiStatus.NonExtendable; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; + +import java.util.SequencedCollection; /** * Represents a function implementation. @@ -21,7 +22,7 @@ public interface Function { /** * Executes this function with the given parameters. * - * @param event The event that is associated with this function execution. + * @param event The event that is associated with this function execution. * @param arguments The arguments to execute the function with. * @return The return value. */ @@ -42,6 +43,6 @@ public interface Function { * @return The returned keys. */ @Experimental - @NotNull String @Nullable [] returnedKeys(); + @NotNull SequencedCollection getReturnedKeys(); } diff --git a/src/test/skript/tests/regressions/8550-string colours sioobe.sk b/src/test/skript/tests/regressions/8550-string colours sioobe.sk deleted file mode 100644 index 449dece6a37..00000000000 --- a/src/test/skript/tests/regressions/8550-string colours sioobe.sk +++ /dev/null @@ -1,3 +0,0 @@ -test "empty tags with colours of string expression": - # throws exception if not fixed - set {_} to string colour of "<>"