diff --git a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java index 33c4be3fdd1..644004b8d3a 100644 --- a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java @@ -1,21 +1,3 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.classes.data; import ch.njol.skript.Skript; @@ -33,16 +15,65 @@ import ch.njol.skript.util.Utils; import ch.njol.util.StringUtils; import ch.njol.yggdrasil.Fields; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; import org.joml.Quaternionf; +import java.util.function.Function; +import java.util.regex.Matcher; import java.util.regex.Pattern; public class JavaClasses { public static final int VARIABLENAME_NUMBERACCURACY = 8; - public static final Pattern INTEGER_PATTERN = Pattern.compile("-?\\d+(_\\d+)*"); - public static final Pattern NUMBER_PATTERN = Pattern.compile("-?\\d+(_\\d+)*(?>\\.\\d+(_\\d+)*)?%?"); + + /** + * The format of an integer. + *

+ * Has an optional negative sign and may contain one underscores followed by any number of digits. + *

+ */ + public static final String INTEGER_NUMBER_PATTERN = "-?\\d+(_\\d+)*"; + + /** + * Matches an integer with an optional unit of radians or degrees. + *

+ * First, the actual number format {@code num} is specified. Then, an optional angle unit is specified. + * For this, the {@code rad} group is used. This specifies that the number is in radians. + * This is used to determine if the number should be converted to degrees. + * Degrees is not a named group because it just returns the value in the {@code num} group, which + * is the default behaviour. + * Optionally, the user can use {@code x in degrees} instead of {@code x degrees}. + *

+ */ + public static final Pattern INTEGER_PATTERN = + Pattern.compile("(?" + INTEGER_NUMBER_PATTERN + ")" + + "(?: (?:in )?(?:(?rad(?:ian)?s?)|deg(?:ree)?s?))?"); + + /** + * The format of a decimal number. + *

+ * Has an optional negative sign and may contain one underscores followed by any number of digits, + * in the whole part or the fractional part. The fractional part is optional. May be followed by a percentage sign, + * to indicate that the number is a percentage. + *

+ */ + public static final String DECIMAL_NUMBER_PATTERN = "-?\\d+(_\\d+)*(?>\\.\\d+(_\\d+)*)?%?"; + + /** + * Matches a decimal number with an optional unit of radians or degrees. + *

+ * First, the actual number format {@code num} is specified. Then, an optional angle unit is specified. + * For this, the {@code rad} group is used. This specifies that the number is in radians. + * This is used to determine if the number should be converted to degrees. + * Degrees is not a named group because it just returns the value in the {@code num} group, which + * is the default behaviour. + * Optionally, the user can use {@code x in degrees} instead of {@code x degrees}. + *

+ */ + public static final Pattern DECIMAL_PATTERN = + Pattern.compile("(?" + DECIMAL_NUMBER_PATTERN + ")" + + "(?: (?:in )?(?:(?rad(?:ian)?s?)|deg(?:ree)?s?))?"); static { Classes.registerClass(new ClassInfo<>(Object.class, "object") @@ -52,328 +83,55 @@ public class JavaClasses { .usage("") .examples("") .since("1.0")); - + Classes.registerClass(new ClassInfo<>(Number.class, "number") .user("num(ber)?s?") .name("Number") - .description("A number, e.g. 2.5, 3, or -9812454.", - "Please note that many expressions only need integers, i.e. will discard any fractional parts of any numbers without producing an error.") - .usage("[-]###[.###] (any amount of digits; very large numbers will be truncated though)") - .examples("set the player's health to 5.5", - "set {_temp} to 2*{_temp} - 2.5") + .description( + "A number, e.g. 2.5, 3, -9812454, 30 degrees or 3.14 radians.", + "Please note that many expressions only need integers, i.e. " + + "will discard any fractional parts of any numbers without producing an error.", + "Radians will be converted to degrees.") + .usage("[-]###[.###] [[in ](rad[ian][s]|deg[ree][s])] (any amount of digits; very large numbers will be truncated though)") + .examples( + "set the player's health to 5.5", + "set {_temp} to 2*{_temp} - 2.5", + "set {_angle} to 3.14 in radians # will be converted to degrees" + ) .since("1.0") // is registered after all other number classes .defaultExpression(new SimpleLiteral<>(1, true)) - .parser(new Parser() { - @Override - @Nullable - public Number parse(String s, ParseContext context) { - if (!NUMBER_PATTERN.matcher(s).matches()) - return null; - if (INTEGER_PATTERN.matcher(s).matches()) { - try { - return Long.valueOf(s.replace("_", "")); - } catch (NumberFormatException ignored) { } - } - try { - s = s.replace("_", ""); + .parser(new NumberParser()) + .serializer(new NumberSerializer())); - Double d = s.endsWith("%") ? Double.parseDouble(s.substring(0, s.length() - 1)) / 100 : Double.parseDouble(s); - if (d.isNaN() || d.isInfinite()) - return null; - return d; - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public String toString(Number n, int flags) { - return StringUtils.toString(n.doubleValue(), SkriptConfig.numberAccuracy.value()); - } - - @Override - public String toVariableNameString(Number n) { - return StringUtils.toString(n.doubleValue(), VARIABLENAME_NUMBERACCURACY); - } - }).serializer(new Serializer() { - @Override - public Fields serialize(Number n) { - throw new IllegalStateException(); // serialised natively by Yggdrasil - } - - @Override - public boolean canBeInstantiated() { - return true; - } - - @Override - public void deserialize(Number o, Fields f) { - assert false; - } - - @Override - @Nullable - public Number deserialize(String s) { - try { - return Integer.valueOf(s); - } catch (NumberFormatException ignored) {} - try { - return Double.valueOf(s); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); - Classes.registerClass(new ClassInfo<>(Long.class, "long") .user("int(eger)?s?") .name(ClassInfo.NO_DOC) .before("integer", "short", "byte") .defaultExpression(new SimpleLiteral<>((long) 1, true)) - .parser(new Parser() { - @Override - @Nullable - public Long parse(String s, ParseContext context) { - if (!INTEGER_PATTERN.matcher(s).matches()) - return null; - try { - return Long.valueOf(s.replace("_", "")); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public String toString(Long l, int flags) { - return "" + l; - } - - @Override - public String toVariableNameString(Long l) { - return "" + l; - } - }).serializer(new Serializer() { - @Override - public Fields serialize(Long n) { - throw new IllegalStateException(); // serialised natively by Yggdrasil - } - - @Override - public boolean canBeInstantiated() { - return true; - } - - @Override - public void deserialize(Long o, Fields f) { - assert false; - } + .parser(new LongParser()) + .serializer(new LongSerializer())); - @Override - @Nullable - public Long deserialize(String s) { - try { - return Long.parseLong(s); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); - Classes.registerClass(new ClassInfo<>(Integer.class, "integer") .name(ClassInfo.NO_DOC) .defaultExpression(new SimpleLiteral<>(1, true)) - .parser(new Parser() { - @Override - @Nullable - public Integer parse(String s, ParseContext context) { - if (!INTEGER_PATTERN.matcher(s).matches()) - return null; - try { - return Integer.valueOf(s.replace("_", "")); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public String toString(Integer i, int flags) { - return "" + i; - } - - @Override - public String toVariableNameString(Integer i) { - return "" + i; - } - }).serializer(new Serializer() { - @Override - public Fields serialize(Integer n) { - throw new IllegalStateException(); // serialised natively by Yggdrasil - } - - @Override - public boolean canBeInstantiated() { - return true; - } - - @Override - public void deserialize(Integer o, Fields f) { - assert false; - } + .parser(new IntegerParser()) + .serializer(new IntegerSerializer())); - @Override - @Nullable - public Integer deserialize(String s) { - try { - return Integer.parseInt(s); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); - Classes.registerClass(new ClassInfo<>(Double.class, "double") .name(ClassInfo.NO_DOC) .defaultExpression(new SimpleLiteral<>(1., true)) .after("long") .before("float", "integer", "short", "byte") - .parser(new Parser() { - @Override - @Nullable - public Double parse(String s, ParseContext context) { - if (!NUMBER_PATTERN.matcher(s).matches()) - return null; - try { - s = s.replace("_", ""); - - Double d = s.endsWith("%") ? Double.parseDouble(s.substring(0, s.length() - 1)) / 100 : Double.parseDouble(s); - if (d.isNaN() || d.isInfinite()) - return null; - return d; - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public String toString(Double d, int flags) { - return StringUtils.toString(d, SkriptConfig.numberAccuracy.value()); - } - - @Override - public String toVariableNameString(Double d) { - return StringUtils.toString(d, VARIABLENAME_NUMBERACCURACY); - } - }).serializer(new Serializer() { - @Override - public Fields serialize(Double n) { - throw new IllegalStateException(); // serialised natively by Yggdrasil - } - - @Override - public boolean canBeInstantiated() { - return true; - } - - @Override - public void deserialize(Double o, Fields f) { - assert false; - } + .parser(new DoubleParser()) + .serializer(new DoubleSerializer())); - @Override - @Nullable - public Double deserialize(String s) { - try { - return Double.parseDouble(s); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); - Classes.registerClass(new ClassInfo<>(Float.class, "float") .name(ClassInfo.NO_DOC) .defaultExpression(new SimpleLiteral<>(1f, true)) - .parser(new Parser() { - @Override - @Nullable - public Float parse(String s, ParseContext context) { - if (!NUMBER_PATTERN.matcher(s).matches()) - return null; - try { - s = s.replace("_", ""); - - Float f = s.endsWith("%") ? Float.parseFloat(s.substring(0, s.length() - 1)) / 100 : Float.parseFloat(s); - if (f.isNaN() || f.isInfinite()) { - return null; - } - return f; - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public String toString(Float f, int flags) { - return StringUtils.toString(f, SkriptConfig.numberAccuracy.value()); - } - - @Override - public String toVariableNameString(Float f) { - return StringUtils.toString(f.doubleValue(), VARIABLENAME_NUMBERACCURACY); - } - }).serializer(new Serializer() { - @Override - public Fields serialize(Float n) { - throw new IllegalStateException(); // serialised natively by Yggdrasil - } - - @Override - public boolean canBeInstantiated() { - return true; - } - - @Override - public void deserialize(Float o, Fields f) { - assert false; - } + .parser(new FloatParser()) + .serializer(new FloatSerializer())); - @Override - @Nullable - public Float deserialize(String s) { - try { - return Float.parseFloat(s); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); - Classes.registerClass(new ClassInfo<>(Boolean.class, "boolean") .user("booleans?") .name("Boolean") @@ -381,10 +139,10 @@ public boolean mustSyncDeserialization() { .usage("true/yes/on or false/no/off") .examples("set {config.%player%.use mod} to false") .since("1.0") - .parser(new Parser() { + .parser(new Parser<>() { private final RegexMessage truePattern = new RegexMessage("boolean.true.pattern"); private final RegexMessage falsePattern = new RegexMessage("boolean.false.pattern"); - + @Override @Nullable public Boolean parse(String s, ParseContext context) { @@ -394,30 +152,30 @@ public Boolean parse(String s, ParseContext context) { return Boolean.FALSE; return null; } - + private final Message trueName = new Message("boolean.true.name"); private final Message falseName = new Message("boolean.false.name"); - + @Override public String toString(Boolean b, int flags) { return b ? trueName.toString() : falseName.toString(); } - + @Override public String toVariableNameString(Boolean b) { return "" + b; } - }).serializer(new Serializer() { + }).serializer(new Serializer() { @Override public Fields serialize(Boolean n) { throw new IllegalStateException(); // serialised natively by Yggdrasil } - + @Override public boolean canBeInstantiated() { return true; } - + @Override public void deserialize(Boolean o, Fields f) { assert false; @@ -432,127 +190,25 @@ public Boolean deserialize(String s) { return Boolean.FALSE; return null; } - + @Override public boolean mustSyncDeserialization() { return false; } })); - + Classes.registerClass(new ClassInfo<>(Short.class, "short") .name(ClassInfo.NO_DOC) .defaultExpression(new SimpleLiteral<>((short) 1, true)) - .parser(new Parser() { - @Override - @Nullable - public Short parse(String s, ParseContext context) { - if (!INTEGER_PATTERN.matcher(s).matches()) - return null; - try { - return Short.valueOf(s.replace("_", "")); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public String toString(Short s, int flags) { - return "" + s; - } - - @Override - public String toVariableNameString(Short s) { - return "" + s; - } - }).serializer(new Serializer() { - @Override - public Fields serialize(Short n) { - throw new IllegalStateException(); // serialised natively by Yggdrasil - } - - @Override - public boolean canBeInstantiated() { - return true; - } - - @Override - public void deserialize(Short o, Fields f) { - assert false; - } + .parser(new ShortParser()) + .serializer(new ShortSerializer())); - @Override - @Nullable - public Short deserialize(String s) { - try { - return Short.parseShort(s); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); - Classes.registerClass(new ClassInfo<>(Byte.class, "byte") .name(ClassInfo.NO_DOC) .defaultExpression(new SimpleLiteral<>((byte) 1, true)) - .parser(new Parser() { - @Override - @Nullable - public Byte parse(String s, ParseContext context) { - if (!INTEGER_PATTERN.matcher(s).matches()) - return null; - try { - return Byte.valueOf(s.replace("_", "")); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public String toString(Byte b, int flags) { - return "" + b; - } - - @Override - public String toVariableNameString(Byte b) { - return "" + b; - } - }).serializer(new Serializer() { - @Override - public Fields serialize(Byte n) { - throw new IllegalStateException(); // serialised natively by Yggdrasil - } - - @Override - public boolean canBeInstantiated() { - return true; - } - - @Override - public void deserialize(Byte o, Fields f) { - assert false; - } + .parser(new ByteParser()) + .serializer(new ByteSerializer())); - @Override - @Nullable - public Byte deserialize(String s) { - try { - return Byte.parseByte(s); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); - Classes.registerClass(new ClassInfo<>(String.class, "string") .user("(text|string)s?") .name("Text") @@ -568,7 +224,7 @@ public boolean mustSyncDeserialization() { "message \"Hello %player%\"", "message \"The id of \"\"%type of tool%\"\" is %id of tool%.\"") .since("1.0") - .parser(new Parser() { + .parser(new Parser<>() { @Override @Nullable public String parse(String s, ParseContext context) { @@ -590,37 +246,37 @@ public String parse(String s, ParseContext context) { assert false; return null; } - + @Override public boolean canParse(ParseContext context) { return context != ParseContext.DEFAULT; } - + @Override public String toString(String s, int flags) { return s; } - + @Override public String getDebugMessage(String s) { return '"' + s + '"'; } - + @Override public String toVariableNameString(String s) { return s; } - }).serializer(new Serializer() { + }).serializer(new Serializer() { @Override public Fields serialize(String n) { throw new IllegalStateException(); // serialised natively by Yggdrasil } - + @Override public boolean canBeInstantiated() { return true; } - + @Override public void deserialize(String o, Fields f) { assert false; @@ -630,7 +286,7 @@ public void deserialize(String o, Fields f) { public String deserialize(String s) { return s; } - + @Override public boolean mustSyncDeserialization() { return false; @@ -640,35 +296,500 @@ public boolean mustSyncDeserialization() { // joml type - for display entities if (Skript.classExists("org.joml.Quaternionf")) Classes.registerClass(new ClassInfo<>(Quaternionf.class, "quaternion") - .user("quaternionf?s?") - .name("Quaternion") - .description("Quaternions are four dimensional vectors, often used for representing rotations.") - .since("INSERT VERSION") - .parser(new Parser<>() { - public boolean canParse(ParseContext context) { - return false; - } + .user("quaternionf?s?") + .name("Quaternion") + .description("Quaternions are four dimensional vectors, often used for representing rotations.") + .since("INSERT VERSION") + .parser(new Parser<>() { + public boolean canParse(ParseContext context) { + return false; + } - @Override - public String toString(Quaternionf quaternion, int flags) { - return "w:" + Skript.toString(quaternion.w()) + ", x:" + Skript.toString(quaternion.x()) + ", y:" + Skript.toString(quaternion.y()) + ", z:" + Skript.toString(quaternion.z()); - } + @Override + public String toString(Quaternionf quaternion, int flags) { + return "w:" + Skript.toString(quaternion.w()) + ", x:" + Skript.toString(quaternion.x()) + ", y:" + Skript.toString(quaternion.y()) + ", z:" + Skript.toString(quaternion.z()); + } - @Override - public String toVariableNameString(Quaternionf quaternion) { - return quaternion.w() + "," + quaternion.x() + "," + quaternion.y() + "," + quaternion.z(); - } - }) - .defaultExpression(new EventValueExpression<>(Quaternionf.class)) - .cloner(quaternion -> { - try { - // Implements cloneable, but doesn't return a Quaternionf. - // org.joml improper override. Returns Object. - return (Quaternionf) quaternion.clone(); - } catch (CloneNotSupportedException e) { - return null; - } - })); + @Override + public String toVariableNameString(Quaternionf quaternion) { + return quaternion.w() + "," + quaternion.x() + "," + quaternion.y() + "," + quaternion.z(); + } + }) + .defaultExpression(new EventValueExpression<>(Quaternionf.class)) + .cloner(quaternion -> { + try { + // Implements cloneable, but doesn't return a Quaternionf. + // org.joml improper override. Returns Object. + return (Quaternionf) quaternion.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + })); + } + + /** + * Converts a string to a number formatted as an integer. + *

+ * Applies {@code stringToNumber} for parsing the number, then tries to + * convert radians to degrees if the string contains a radian group. + *

+ * + * @param string The string with the possible number. + * @param stringToNumber The function to parse the number, e.g. {@link Integer#parseInt(String)}. + * @return The parsed string, or null if the string could not be parsed. + */ + @Contract(pure = true) + private static @Nullable T convertIntegerFormatted( + String string, + Function stringToNumber + ) { + Matcher matcher = INTEGER_PATTERN.matcher(string); + + if (!matcher.matches()) + return null; + + String number = matcher.group("num").replace("_", ""); + if (matcher.group("rad") != null) { + try { + //noinspection unchecked + return (T) (Double) Math.toDegrees(stringToNumber.apply(number).doubleValue()); + } catch (NumberFormatException ignored) { + } + } else { + try { + return stringToNumber.apply(number); + } catch (NumberFormatException ignored) { + } + } + + return null; + } + + /** + * Converts a string to a number formatted as a decimal. + *

+ * Applies {@code stringToNumber} for parsing the number. + * If the number is a percentage, it gets parsed using the double value divided by 100, and {@code fromDouble}. + * Then tries to convert radians to degrees if the string contains a radian group. + *

+ * + * @param string The string with the possible number. + * @param stringToNumber The function to parse the number, e.g. {@link Integer#parseInt(String)}. + * @return The parsed string, or null if the string could not be parsed. + */ + @Contract(pure = true) + private static @Nullable T convertDecimalFormatted( + String string, + Function stringToNumber + ) { + Matcher matcher = DECIMAL_PATTERN.matcher(string); + + if (!matcher.matches()) + return null; + + String number = matcher.group("num").replace("_", ""); + try { + T result; + if (number.endsWith("%")) { + T extracted = stringToNumber.apply(number.substring(0, number.length() - 1)); + //noinspection unchecked + result = (T) (Double) (extracted.doubleValue() / 100.0); + } else { + result = stringToNumber.apply(number); + } + + if (matcher.group("rad") != null) { + try { + //noinspection unchecked + return (T) (Double) Math.toDegrees(result.doubleValue()); + } catch (NumberFormatException ignored) { + } + } + + return result; + } catch (NumberFormatException ex) { + return null; + } + } + + private static class NumberParser extends Parser { + + @Override + public @Nullable Number parse(String string, ParseContext context) { + Matcher numberMatcher = DECIMAL_PATTERN.matcher(string); + if (!numberMatcher.matches()) + return null; + + Integer integerAttempt = convertIntegerFormatted(string, Integer::parseInt); + if (integerAttempt != null) + return integerAttempt; + + Double parsed = convertDecimalFormatted(string, Double::parseDouble); + return parsed == null || parsed.isInfinite() || parsed.isNaN() ? null : parsed; + } + + @Override + public String toString(Number number, int flags) { + return StringUtils.toString(number.doubleValue(), SkriptConfig.numberAccuracy.value()); + } + + @Override + public String toVariableNameString(Number number) { + return StringUtils.toString(number.doubleValue(), VARIABLENAME_NUMBERACCURACY); + } + + } + + private static class NumberSerializer extends Serializer { + + @Override + public Fields serialize(Number number) { + throw new IllegalStateException(); // serialised natively by Yggdrasil + } + + @Override + public boolean canBeInstantiated() { + return true; + } + + @Override + public void deserialize(Number number, Fields fields) { + assert false; + } + + @Override + public @Nullable Number deserialize(String string) { + try { + return Integer.valueOf(string); + } catch (NumberFormatException ignored) { + } + try { + return Double.valueOf(string); + } catch (NumberFormatException e) { + return null; + } + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } + + } + + private static class LongParser extends Parser { + + @Override + public @Nullable Long parse(String string, ParseContext context) { + return convertIntegerFormatted(string, Long::parseLong); + } + + @Override + public String toString(Long l, int flags) { + return l.toString(); + } + + @Override + public String toVariableNameString(Long l) { + return l.toString(); + } + + } + + private static class LongSerializer extends Serializer { + + @Override + public Fields serialize(Long l) { + throw new IllegalStateException(); // serialised natively by Yggdrasil + } + + @Override + public boolean canBeInstantiated() { + return true; + } + + @Override + public void deserialize(Long l, Fields fields) { + assert false; + } + + @Override + public @Nullable Long deserialize(String string) { + try { + return Long.parseLong(string); + } catch (NumberFormatException ex) { + return null; + } + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } + + } + + private static class IntegerParser extends Parser { + + @Override + public @Nullable Integer parse(String string, ParseContext context) { + return convertIntegerFormatted(string, Integer::parseInt); + } + + @Override + public String toString(Integer i, int flags) { + return i.toString(); + } + + @Override + public String toVariableNameString(Integer i) { + return i.toString(); + } + + } + + private static class IntegerSerializer extends Serializer { + + @Override + public Fields serialize(Integer i) { + throw new IllegalStateException(); // serialised natively by Yggdrasil + } + + @Override + public boolean canBeInstantiated() { + return true; + } + + @Override + public void deserialize(Integer i, Fields fields) { + assert false; + } + + @Override + public @Nullable Integer deserialize(String string) { + try { + return Integer.parseInt(string); + } catch (NumberFormatException ex) { + return null; + } + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } + + } + + private static class DoubleParser extends Parser { + + @Override + public @Nullable Double parse(String string, ParseContext context) { + Double parsed = convertDecimalFormatted(string, Double::parseDouble); + + return parsed == null || parsed.isInfinite() || parsed.isNaN() ? null : parsed; + } + + @Override + public String toString(Double d, int flags) { + return StringUtils.toString(d, SkriptConfig.numberAccuracy.value()); + } + + @Override + public String toVariableNameString(Double d) { + return StringUtils.toString(d, VARIABLENAME_NUMBERACCURACY); + } + + } + + private static class DoubleSerializer extends Serializer { + + @Override + public Fields serialize(Double d) { + throw new IllegalStateException(); // serialised natively by Yggdrasil + } + + @Override + public boolean canBeInstantiated() { + return true; + } + + @Override + public void deserialize(Double d, Fields fields) { + assert false; + } + + @Override + public @Nullable Double deserialize(String string) { + try { + return Double.parseDouble(string); + } catch (NumberFormatException ex) { + return null; + } + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } + + } + + private static class FloatParser extends Parser { + + @Override + public @Nullable Float parse(String string, ParseContext context) { + Float parsed = convertDecimalFormatted(string, Float::parseFloat); + + return parsed == null || parsed.isInfinite() || parsed.isNaN() ? null : parsed; + } + + @Override + public String toString(Float f, int flags) { + return StringUtils.toString(f, SkriptConfig.numberAccuracy.value()); + } + + @Override + public String toVariableNameString(Float f) { + return StringUtils.toString(f.doubleValue(), VARIABLENAME_NUMBERACCURACY); + } + + } + + private static class FloatSerializer extends Serializer { + + @Override + public Fields serialize(Float f) { + throw new IllegalStateException(); // serialised natively by Yggdrasil + } + + @Override + public boolean canBeInstantiated() { + return true; + } + + @Override + public void deserialize(Float f, Fields fields) { + assert false; + } + + @Override + public @Nullable Float deserialize(String string) { + try { + return Float.parseFloat(string); + } catch (NumberFormatException ex) { + return null; + } + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } + + } + + private static class ShortParser extends Parser { + + @Override + public @Nullable Short parse(String string, ParseContext context) { + return convertIntegerFormatted(string, Short::parseShort); + } + + @Override + public String toString(Short s, int flags) { + return s.toString(); + } + + @Override + public String toVariableNameString(Short s) { + return s.toString(); + } + + } + + private static class ShortSerializer extends Serializer { + + @Override + public Fields serialize(Short s) { + throw new IllegalStateException(); // serialised natively by Yggdrasil + } + + @Override + public boolean canBeInstantiated() { + return true; + } + + @Override + public void deserialize(Short s, Fields fields) { + assert false; + } + + @Override + public @Nullable Short deserialize(String string) { + try { + return Short.parseShort(string); + } catch (NumberFormatException ex) { + return null; + } + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } + + } + + private static class ByteParser extends Parser { + + @Override + public @Nullable Byte parse(String string, ParseContext context) { + return convertIntegerFormatted(string, Byte::parseByte); + } + + @Override + public String toString(Byte b, int flags) { + return b.toString(); + } + + @Override + public String toVariableNameString(Byte b) { + return b.toString(); + } + + } + + private static class ByteSerializer extends Serializer { + + @Override + public Fields serialize(Byte b) { + throw new IllegalStateException(); // serialised natively by Yggdrasil + } + + @Override + public boolean canBeInstantiated() { + return true; + } + + @Override + public void deserialize(Byte b, Fields fields) { + assert false; + } + + @Override + public @Nullable Byte deserialize(String string) { + try { + return Byte.parseByte(string); + } catch (NumberFormatException ex) { + return null; + } + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } } diff --git a/src/main/java/ch/njol/skript/effects/EffContinue.java b/src/main/java/ch/njol/skript/effects/EffContinue.java index b83fefec4f2..3c3fb33a407 100644 --- a/src/main/java/ch/njol/skript/effects/EffContinue.java +++ b/src/main/java/ch/njol/skript/effects/EffContinue.java @@ -1,21 +1,3 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.effects; import ch.njol.skript.Skript; @@ -60,7 +42,7 @@ public class EffContinue extends Effect { static { Skript.registerEffect(EffContinue.class, "continue [this loop|[the] [current] loop]", - "continue [the] <" + JavaClasses.INTEGER_PATTERN + ">(st|nd|rd|th) loop" + "continue [the] <" + JavaClasses.INTEGER_NUMBER_PATTERN + ">(st|nd|rd|th) loop" ); } diff --git a/src/main/java/ch/njol/skript/effects/EffExit.java b/src/main/java/ch/njol/skript/effects/EffExit.java index 264f466becb..7d9a2ac61ec 100644 --- a/src/main/java/ch/njol/skript/effects/EffExit.java +++ b/src/main/java/ch/njol/skript/effects/EffExit.java @@ -1,21 +1,3 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.effects; import ch.njol.skript.Skript; @@ -33,19 +15,18 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; -import java.util.ArrayList; import java.util.List; @Name("Exit") @Description("Exits a given amount of loops and conditionals, or the entire trigger.") @Examples({ "if player has any ore:", - "\tstop", + "\tstop", "message \"%player% has no ores!\"", "loop blocks above the player:", - "\tloop-block is not air:", - "\t\texit 2 sections", - "\tset loop-block to water" + "\tloop-block is not air:", + "\t\texit 2 sections", + "\tset loop-block to water" }) @Since("unknown (before 2.1)") public class EffExit extends Effect { @@ -54,7 +35,7 @@ public class EffExit extends Effect { Skript.registerEffect(EffExit.class, "(exit|stop) [trigger]", "(exit|stop) [1|a|the|this] (section|1:loop|2:conditional)", - "(exit|stop) <" + JavaClasses.INTEGER_PATTERN + "> (section|1:loop|2:conditional)s", + "(exit|stop) <" + JavaClasses.INTEGER_NUMBER_PATTERN + "> (section|1:loop|2:conditional)s", "(exit|stop) all (section|1:loop|2:conditional)s"); } diff --git a/src/main/java/ch/njol/skript/expressions/ExprAngle.java b/src/main/java/ch/njol/skript/expressions/ExprAngle.java new file mode 100644 index 00000000000..7916f8fd405 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprAngle.java @@ -0,0 +1,77 @@ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +@Name("Angle") +@Description({ + "Represents the passed number value in degrees.", + "If radians is specified, converts the passed value to degrees. This conversion may not be entirely accurate, " + + "due to floating point precision.", +}) +@Examples({ + "set {_angle} to 90 degrees", + "{_angle} is 90 # true", + "180 degrees is pi # true", + "pi radians is 180 degrees # true" +}) +@Since("INSERT VERSION") +public class ExprAngle extends SimpleExpression { + + static { + Skript.registerExpression(ExprAngle.class, Number.class, ExpressionType.SIMPLE, + "%number% [in] deg[ree][s]", + "%number% [in] rad[ian][s]", + "%numbers% in deg[ree][s]", + "%numbers% in rad[ian][s]"); + } + + private Expression angle; + private boolean isRadians; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, + Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + angle = (Expression) expressions[0]; + isRadians = matchedPattern % 2 != 0; + return true; + } + + @Override + protected Number @Nullable [] get(Event event) { + Number[] numbers = angle.getArray(event); + + if (isRadians) + for (int i = 0; i < numbers.length; i++) + numbers[i] = Math.toDegrees(numbers[i].doubleValue()); + + return numbers; + } + + @Override + public boolean isSingle() { + return angle.isSingle(); + } + + @Override + public Class getReturnType() { + return Number.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return angle.toString(event, debug) + " in " + (isRadians ? "degrees" : "radians"); + } + +} diff --git a/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java b/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java index bc4d550dabf..dafe28d61e6 100644 --- a/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java +++ b/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java @@ -158,7 +158,7 @@ private static Money parseMoney(String s, String addition) { @Nullable private static Double parseDouble(String s) { - if (!JavaClasses.NUMBER_PATTERN.matcher(s).matches()) + if (!JavaClasses.DECIMAL_PATTERN.matcher(s).matches()) return null; try { return Double.parseDouble(s); diff --git a/src/main/java/org/skriptlang/skript/bukkit/misc/effects/EffRotate.java b/src/main/java/org/skriptlang/skript/bukkit/misc/effects/EffRotate.java index 14ed2fc1737..7a560456640 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/misc/effects/EffRotate.java +++ b/src/main/java/org/skriptlang/skript/bukkit/misc/effects/EffRotate.java @@ -50,9 +50,9 @@ public class EffRotate extends Effect { static { Skript.registerEffect(EffRotate.class, - "rotate %vectors/quaternions/displays% around [the] [global] (:x|:y|:z)(-| )axis by %number% [degrees]", - "rotate %quaternions/displays% around [the|its|their] local (:x|:y|:z)(-| )ax(i|e)s by %number% [degrees]", - "rotate %vectors/quaternions/displays% around [the] %vector% by %number% [degrees]", + "rotate %vectors/quaternions/displays% around [the] [global] (:x|:y|:z)(-| )axis by %number%", + "rotate %quaternions/displays% around [the|its|their] local (:x|:y|:z)(-| )ax(i|e)s by %number%", + "rotate %vectors/quaternions/displays% around [the] %vector% by %number%", "rotate %quaternions/displays% by x %number%, y %number%(, [and]| and) z %number%" ); } diff --git a/src/main/java/org/skriptlang/skript/bukkit/misc/expressions/ExprRotate.java b/src/main/java/org/skriptlang/skript/bukkit/misc/expressions/ExprRotate.java index ee60bf91907..eb5cfaffab9 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/misc/expressions/ExprRotate.java +++ b/src/main/java/org/skriptlang/skript/bukkit/misc/expressions/ExprRotate.java @@ -46,9 +46,9 @@ public class ExprRotate extends SimpleExpression { static { Skript.registerExpression(ExprRotate.class, Object.class, ExpressionType.SIMPLE, - "%quaternions/vectors% rotated around [the] [global] (:x|:y|:z)(-| )axis by %number% [degrees]", - "%quaternions% rotated around [the|its|their] local (:x|:y|:z)(-| )ax(i|e)s by %number% [degrees]", - "%quaternions/vectors% rotated around [the] %vector% by %number% [degrees]", + "%quaternions/vectors% rotated around [the] [global] (:x|:y|:z)(-| )axis by %number%", + "%quaternions% rotated around [the|its|their] local (:x|:y|:z)(-| )ax(i|e)s by %number%", + "%quaternions/vectors% rotated around [the] %vector% by %number%", "%quaternions% rotated by x %number%, y %number%(, [and]| and) z %number%"); } @@ -157,6 +157,11 @@ public Class getReturnType() { return (matchedPattern == 1 || matchedPattern == 3) ? Quaternionf.class : toRotate.getReturnType(); } + @Override + public Class[] possibleReturnTypes() { + return new Class[]{Quaternionf.class, Vector.class}; + } + @Override public String toString(@Nullable Event event, boolean debug) { return switch (matchedPattern) { diff --git a/src/test/skript/tests/misc/angle.sk b/src/test/skript/tests/misc/angle.sk new file mode 100644 index 00000000000..f6deadfb12a --- /dev/null +++ b/src/test/skript/tests/misc/angle.sk @@ -0,0 +1,33 @@ +test "angle": + assert 90 degrees is 90 with "90 degrees is not 90" + assert -10.5 degrees is -10.5 with "-10.5 degrees is not -10.5" + assert 354698306983560 degrees is 354698306983560 with "354698306983560 degrees is not 354698306983560" + + assert pi radians is 180.0 degrees with "pi radians is not 180 degrees" + assert 2 * pi in radians is 360.0 degrees with "2pi radians is not 360 degrees" + + assert 180.0 degrees is pi radians with "180 degrees is not pi radians" + assert 360.0 in degrees is 2 * pi radians with "360 degrees is not 2pi radians" + + assert infinity value degrees is infinity value with "infinity degrees is not infinity" + assert -infinity value degrees is -infinity value with "-infinity degrees is not -infinity" + assert infinity value radians is infinity value with "infinity radians is not infinity" + assert -infinity value radians is -infinity value with "-infinity radians is not -infinity" + + assert ("90 degrees" parsed as number) is 90 with "90 degrees parsed as number is not 90" + assert ("3.141592653589793 radians" parsed as number) is 180.0 with "pi radians parsed as number is not 180 degrees" + assert ("90 degrees" parsed as number) is 90 degrees with "90 degrees parsed as number is not 90 degrees" + assert ("3.141592653589793 radians" parsed as number) is 180.0 degrees with "pi radians parsed as number is not 180 degrees" + assert ("90 degrees" parsed as number) is 0.5 * pi radians with "90 degrees parsed as number is not 0.5pi radians" + assert ("3.141592653589793 radians" parsed as number) is pi radians with "pi radians parsed as number is not pi radians" + + assert 90, 180 and 270 in degrees is 90, 180 and 270 with "90, 180 and 270 degrees is not 90, 180 and 270" + assert 0.5 * pi, pi and 1.5 * pi in radians is 90.0, 180.0 and 270.0 with "0.5pi, pi and 1.5pi radians is not 90, 180 and 270" + + assert ("n" parsed as number) degrees is not set with "'n' parsed as number degrees is set" + assert ("n" parsed as number) radians is not set with "'n' parsed as number radians is set" + assert ("n degrees" parsed as number) is not set with "'n' parsed as number degrees is set" + assert ("n radians" parsed as number) is not set with "'n' parsed as number radians is set" + + assert isNaN(NaN value degrees) is true with "NaN degrees is not NaN" + assert isNaN(NaN value radians) is true with "NaN radians is not NaN"