From a45c0b9af9560892d5d4b1fb5ed3fb7d26436392 Mon Sep 17 00:00:00 2001 From: _tud <98935832+UnderscoreTud@users.noreply.github.com> Date: Tue, 19 Nov 2024 18:00:55 +0300 Subject: [PATCH 01/19] Fix EffContinue (#7221) --- .../java/ch/njol/skript/effects/EffContinue.java | 12 ++++++------ .../skript/tests/syntaxes/effects/EffContinue.sk | 7 +++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/EffContinue.java b/src/main/java/ch/njol/skript/effects/EffContinue.java index 9dddbc96a16..b83fefec4f2 100644 --- a/src/main/java/ch/njol/skript/effects/EffContinue.java +++ b/src/main/java/ch/njol/skript/effects/EffContinue.java @@ -73,10 +73,6 @@ public class EffContinue extends Effect { @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - level = matchedPattern == 0 ? 1 : Integer.parseInt(parseResult.regexes.get(0).group()); - if (level < 1) - return false; - ParserInstance parser = getParser(); int loops = parser.getCurrentSections(LoopSection.class).size(); if (loops == 0) { @@ -84,8 +80,12 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye return false; } - // Section.getSections counts from the innermost section, so we need to invert the level - int levels = level == -1 ? 1 : loops - level + 1; + level = matchedPattern == 0 ? loops : Integer.parseInt(parseResult.regexes.get(0).group()); + if (level < 1) + return false; + + // ParserInstance#getSections counts from the innermost section, so we need to invert the level + int levels = loops - level + 1; if (levels <= 0) { Skript.error("Can't continue the " + StringUtils.fancyOrderNumber(level) + " loop as there " + (loops == 1 ? "is only 1 loop" : "are only " + loops + " loops") + " present"); diff --git a/src/test/skript/tests/syntaxes/effects/EffContinue.sk b/src/test/skript/tests/syntaxes/effects/EffContinue.sk index b63bc3c8603..366dd051522 100644 --- a/src/test/skript/tests/syntaxes/effects/EffContinue.sk +++ b/src/test/skript/tests/syntaxes/effects/EffContinue.sk @@ -19,3 +19,10 @@ test "continue effect": assert loop-value-2 is not 15 with "leveled continue failed #2" continue 1st loop if loop-value-1 is 10 assert loop-value is not 10 with "leveled continue failed #3" + + set {_ran} to false + loop 10 times: + loop 10 times: + continue + set {_ran} to true + assert {_ran} is true with "continue in nested loop continued outermost loop" From 1238f62bd320a39d9b1bc5f19669a35cd9a5b8bf Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Fri, 22 Nov 2024 23:07:54 -0500 Subject: [PATCH 02/19] Expression to get ARGB values of colours (#7215) * argb expression + tests * force value * component * Update src/main/java/ch/njol/skript/util/Color.java Co-authored-by: Patrick Miller * Update src/main/java/ch/njol/skript/expressions/ExprARGB.java Co-authored-by: Patrick Miller --------- Co-authored-by: Patrick Miller --- .../ch/njol/skript/expressions/ExprARGB.java | 78 +++++++++++++++++++ .../njol/skript/expressions/ExprColorOf.java | 2 +- src/main/java/ch/njol/skript/util/Color.java | 32 ++++++-- .../java/ch/njol/skript/util/ColorRGB.java | 32 +++++--- .../java/ch/njol/skript/util/SkriptColor.java | 43 ++++++---- .../tests/syntaxes/expressions/ExprARGB.sk | 20 +++++ 6 files changed, 176 insertions(+), 31 deletions(-) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprARGB.java create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprARGB.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprARGB.java b/src/main/java/ch/njol/skript/expressions/ExprARGB.java new file mode 100644 index 00000000000..6731171cdad --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprARGB.java @@ -0,0 +1,78 @@ +package ch.njol.skript.expressions; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Keywords; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.Color; +import ch.njol.util.Kleenean; + +import java.util.Locale; +import java.util.function.Function; + +@Name("Alpha/Red/Green/Blue Color Value") +@Description({ + "The alpha, red, green, or blue value of colors. Ranges from 0 to 255.", + "Alpha represents opacity." +}) +@Examples({ + "broadcast red value of rgb(100, 0, 50) # sends '100'", + "set {_red} to red's red value + 10" +}) +@Keywords({"ARGB", "RGB", "color", "colour"}) +@Since("INSERT VERSION") +public class ExprARGB extends SimplePropertyExpression { + + static { + register(ExprARGB.class, Integer.class, "(:alpha|:red|:green|:blue) (value|component)", "colors"); + } + + private RGB color; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + color = RGB.valueOf(parseResult.tags.get(0).toUpperCase(Locale.ENGLISH)); + return super.init(expressions, matchedPattern, isDelayed, parseResult); + } + + @Override + public Integer convert(Color from) { + return color.getValue(from); + } + + @Override + public Class getReturnType() { + return Integer.class; + } + + @Override + protected String getPropertyName() { + return color.name().toLowerCase(Locale.ENGLISH); + } + + /** + * helper enum for getting argb values of {@link Color}s. + */ + private enum RGB { + ALPHA(Color::getAlpha), + RED(Color::getRed), + GREEN(Color::getGreen), + BLUE(Color::getBlue); + + private final Function get; + + RGB(Function get) { + this.get = get; + } + + public int getValue(Color from) { + return get.apply(from); + } + + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprColorOf.java b/src/main/java/ch/njol/skript/expressions/ExprColorOf.java index e46cfa1fc33..b1698656dbe 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprColorOf.java +++ b/src/main/java/ch/njol/skript/expressions/ExprColorOf.java @@ -85,7 +85,7 @@ protected Color[] get(Event event, Object[] source) { List colors = new ArrayList<>(); for (FireworkEffect effect : (FireworkEffect[]) source) { effect.getColors().stream() - .map(SkriptColor::fromBukkitColor) + .map(ColorRGB::fromBukkitColor) .forEach(colors::add); } return colors.toArray(new Color[0]); diff --git a/src/main/java/ch/njol/skript/util/Color.java b/src/main/java/ch/njol/skript/util/Color.java index 1c5671aea0a..127e809652f 100644 --- a/src/main/java/ch/njol/skript/util/Color.java +++ b/src/main/java/ch/njol/skript/util/Color.java @@ -18,30 +18,48 @@ */ package ch.njol.skript.util; +import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; import org.bukkit.DyeColor; import org.jetbrains.annotations.Nullable; -import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; - public interface Color extends YggdrasilExtendedSerializable { - + /** * Gets Bukkit color representing this color. * @return Bukkit color. */ org.bukkit.Color asBukkitColor(); - - + + /** + * @return The alpha component of this color. + */ + int getAlpha(); + + /** + * @return The red component of this color. + */ + int getRed(); + + /** + * @return The green component of this color. + */ + int getGreen(); + + /** + * @return The blue component of this color. + */ + int getBlue(); + /** * Gets Bukkit dye color representing this color, if one exists. * @return Dye color or null. */ @Nullable DyeColor asDyeColor(); - + /** * @return Name of the color. */ String getName(); - + } diff --git a/src/main/java/ch/njol/skript/util/ColorRGB.java b/src/main/java/ch/njol/skript/util/ColorRGB.java index 141f753f701..180b26e7c81 100644 --- a/src/main/java/ch/njol/skript/util/ColorRGB.java +++ b/src/main/java/ch/njol/skript/util/ColorRGB.java @@ -1,6 +1,5 @@ package ch.njol.skript.util; -import ch.njol.skript.Skript; import ch.njol.skript.variables.Variables; import ch.njol.util.Math2; import ch.njol.yggdrasil.Fields; @@ -18,7 +17,6 @@ public class ColorRGB implements Color { - private static final boolean HAS_ARGB = Skript.methodExists(org.bukkit.Color.class, "getAlpha"); private static final Pattern RGB_PATTERN = Pattern.compile("(?>rgb|RGB) (\\d+), (\\d+), (\\d+)"); private org.bukkit.Color bukkit; @@ -60,13 +58,7 @@ public ColorRGB(org.bukkit.Color bukkit) { */ @Contract("_,_,_,_ -> new") public static @NotNull ColorRGB fromRGBA(int red, int green, int blue, int alpha) { - org.bukkit.Color bukkit; - if (HAS_ARGB) { - bukkit = org.bukkit.Color.fromARGB(alpha, red, green, blue); - } else { - bukkit = org.bukkit.Color.fromRGB(red, green, blue); - } - return new ColorRGB(bukkit); + return new ColorRGB(org.bukkit.Color.fromARGB(alpha, red, green, blue)); } /** @@ -93,6 +85,26 @@ public ColorRGB(org.bukkit.Color bukkit) { return new ColorRGB(bukkit); } + @Override + public int getAlpha() { + return bukkit.getAlpha(); + } + + @Override + public int getRed() { + return bukkit.getRed(); + } + + @Override + public int getGreen() { + return bukkit.getGreen(); + } + + @Override + public int getBlue() { + return bukkit.getBlue(); + } + @Override public org.bukkit.Color asBukkitColor() { return bukkit; @@ -106,7 +118,7 @@ public org.bukkit.Color asBukkitColor() { @Override public String getName() { String rgb = bukkit.getRed() + ", " + bukkit.getGreen() + ", " + bukkit.getBlue(); - if (HAS_ARGB && bukkit.getAlpha() != 255) + if (bukkit.getAlpha() != 255) return "argb " + bukkit.getAlpha() + ", " + rgb; return "rgb " + rgb; } diff --git a/src/main/java/ch/njol/skript/util/SkriptColor.java b/src/main/java/ch/njol/skript/util/SkriptColor.java index 7d03b13ed81..598fc66b6fa 100644 --- a/src/main/java/ch/njol/skript/util/SkriptColor.java +++ b/src/main/java/ch/njol/skript/util/SkriptColor.java @@ -18,6 +18,15 @@ */ package ch.njol.skript.util; +import ch.njol.skript.localization.Adjective; +import ch.njol.skript.localization.Language; +import ch.njol.skript.variables.Variables; +import ch.njol.yggdrasil.Fields; +import org.bukkit.ChatColor; +import org.bukkit.DyeColor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.io.NotSerializableException; import java.io.StreamCorruptedException; import java.util.Arrays; @@ -26,18 +35,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.regex.Pattern; - -import org.bukkit.ChatColor; -import org.bukkit.DyeColor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.localization.Adjective; -import ch.njol.skript.localization.Language; -import ch.njol.skript.variables.Variables; -import ch.njol.yggdrasil.Fields; @SuppressWarnings("null") public enum SkriptColor implements Color { @@ -96,7 +93,27 @@ public enum SkriptColor implements Color { public org.bukkit.Color asBukkitColor() { return dye.getColor(); } - + + @Override + public int getAlpha() { + return dye.getColor().getAlpha(); + } + + @Override + public int getRed() { + return dye.getColor().getRed(); + } + + @Override + public int getGreen() { + return dye.getColor().getGreen(); + } + + @Override + public int getBlue() { + return dye.getColor().getBlue(); + } + @Override public DyeColor asDyeColor() { return dye; diff --git a/src/test/skript/tests/syntaxes/expressions/ExprARGB.sk b/src/test/skript/tests/syntaxes/expressions/ExprARGB.sk new file mode 100644 index 00000000000..854fddf55ee --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprARGB.sk @@ -0,0 +1,20 @@ +test "argb": + + set {_colour} to rgb(0, 0, 0, 0) + assert {_colour}'s red value is 0 with "failed to get red" + assert {_colour}'s green value is 0 with "failed to get green" + assert blue value of {_colour} is 0 with "failed to get blue" + assert alpha value of {_colour} is 0 with "failed to get alpha" + + set {_colour} to rgb(9, 102, 55, 10) + assert {_colour}'s red value is 9 with "failed to get red" + assert {_colour}'s green value is 102 with "failed to get green" + assert blue value of {_colour} is 55 with "failed to get blue" + assert alpha value of {_colour} is 10 with "failed to get alpha" + + set {_colour} to red + assert {_colour}'s red value is 176 with "failed to get red" + assert {_colour}'s green value is 46 with "failed to get green" + assert blue value of {_colour} is 38 with "failed to get blue" + assert alpha value of {_colour} is 255 with "failed to get alpha" + From 91e8b9315156cff2972658f7d6583b61025e2d9d Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Fri, 22 Nov 2024 23:09:09 -0500 Subject: [PATCH 03/19] Prevent long overflow in arithemetic (#7210) * catch long overflows * don't compromise with precision uses Math.xExact code to catch overflows instead of relying on doubles. * Apply suggestions from code review Co-authored-by: Efnilite <35348263+Efnilite@users.noreply.github.com> --------- Co-authored-by: Efnilite <35348263+Efnilite@users.noreply.github.com> --- .../classes/data/DefaultOperations.java | 47 +++++++++++++++---- .../tests/regressions/7209-long overflow.sk | 34 ++++++++++++++ 2 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 src/test/skript/tests/regressions/7209-long overflow.sk diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java index 1588d31ece8..48bf82939fd 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java @@ -32,26 +32,53 @@ public class DefaultOperations { static { // Number - Number Arithmetics.registerOperation(Operator.ADDITION, Number.class, (left, right) -> { - if (Utils.isInteger(left, right)) - return left.longValue() + right.longValue(); + if (Utils.isInteger(left, right)) { + long result = left.longValue() + right.longValue(); + // catches overflow, from Math.addExact(long, long) + if (((left.longValue() ^ result) & (right.longValue() ^ result)) >= 0) + return result; + } return left.doubleValue() + right.doubleValue(); }); Arithmetics.registerOperation(Operator.SUBTRACTION, Number.class, (left, right) -> { - if (Utils.isInteger(left, right)) - return left.longValue() - right.longValue(); + if (Utils.isInteger(left, right)) { + long result = left.longValue() - right.longValue(); + // catches overflow, from Math.addExact(long, long) + if (((left.longValue() ^ result) & (right.longValue() ^ result)) >= 0) + return result; + } return left.doubleValue() - right.doubleValue(); }); Arithmetics.registerOperation(Operator.MULTIPLICATION, Number.class, (left, right) -> { - if (Utils.isInteger(left, right)) - return left.longValue() * right.longValue(); - return left.doubleValue() * right.doubleValue(); + if (!Utils.isInteger(left, right)) + return left.doubleValue() * right.doubleValue(); + + // catch overflow, from Math.multiplyExact(long, long) + long longLeft = left.longValue(); + long longRight = right.longValue(); + long ax = Math.abs(longLeft); + long ay = Math.abs(longRight); + + long result = left.longValue() * right.longValue(); + + if (((ax | ay) >>> 31 != 0)) { + // Some bits greater than 2^31 that might cause overflow + // Check the result using the divide operator + // and check for the special case of Long.MIN_VALUE * -1 + if (((longRight != 0) && (result / longRight != longLeft)) || + (longLeft == Long.MIN_VALUE && longRight == -1)) { + return left.doubleValue() * right.doubleValue(); + } + } + return result; }); Arithmetics.registerOperation(Operator.DIVISION, Number.class, (left, right) -> left.doubleValue() / right.doubleValue()); Arithmetics.registerOperation(Operator.EXPONENTIATION, Number.class, (left, right) -> Math.pow(left.doubleValue(), right.doubleValue())); Arithmetics.registerDifference(Number.class, (left, right) -> { - if (Utils.isInteger(left, right)) - return Math.abs(left.longValue() - right.longValue()); - return Math.abs(left.doubleValue() - right.doubleValue()); + double result = Math.abs(left.doubleValue() - right.doubleValue()); + if (Utils.isInteger(left, right) && result < Long.MAX_VALUE && result > Long.MIN_VALUE) + return (long) result; + return result; }); Arithmetics.registerDefaultValue(Number.class, () -> 0L); diff --git a/src/test/skript/tests/regressions/7209-long overflow.sk b/src/test/skript/tests/regressions/7209-long overflow.sk new file mode 100644 index 00000000000..bff56f0f57f --- /dev/null +++ b/src/test/skript/tests/regressions/7209-long overflow.sk @@ -0,0 +1,34 @@ +test "long overflow, addition": + set {_double-const} to 9000000000000000000.0 + set {_long-const} to 9000000000000000000 + + loop 100 times: + set {_double} to {_double} + {_double-const} + set {_long} to {_long} + {_long-const} + assert {_double} is {_long} with "long value did not overflow to a double" + + assert {_long} is 9000000000000000000 * 100.0 with "wrong final value" + +test "long underflow, subtraction": + set {_double-const} to 9000000000000000000.0 + set {_long-const} to 9000000000000000000 + + loop 100 times: + set {_double} to {_double} - {_double-const} + set {_long} to {_long} - {_long-const} + assert {_double} is {_long} with "long value did not overflow to a double" + + assert {_long} is 9000000000000000000 * -100.0 with "wrong final value" + +test "long overflow, multiplication": + set {_double} to 10.0 + set {_long} to 10 + + loop 100 times: + set {_double} to {_double} * 10 + set {_long} to {_long} * 10 + assert {_double} is {_long} with "long value did not overflow to a double" + + # the 6 is due to floating point error + assert {_long} is 100000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000 with "wrong final value" + From 345d9494ae54411ac89749122b573c28a3a1beb9 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Fri, 22 Nov 2024 23:41:55 -0500 Subject: [PATCH 04/19] Add entity to display converter (#7219) * add e->d converter * add regression and fix blockdata input by removing regex pattern check in entitydata parser * Update src/main/java/org/skriptlang/skript/bukkit/displays/DisplayModule.java Co-authored-by: Efnilite <35348263+Efnilite@users.noreply.github.com> --------- Co-authored-by: Efnilite <35348263+Efnilite@users.noreply.github.com> Co-authored-by: Patrick Miller --- .../ch/njol/skript/entity/EntityData.java | 50 ++++++++----------- .../skript/bukkit/displays/DisplayModule.java | 7 +++ .../7187-display entities conversion.sk | 7 +++ 3 files changed, 35 insertions(+), 29 deletions(-) create mode 100644 src/test/skript/tests/regressions/7187-display entities conversion.sk diff --git a/src/main/java/ch/njol/skript/entity/EntityData.java b/src/main/java/ch/njol/skript/entity/EntityData.java index 2892d24c156..8423c380297 100644 --- a/src/main/java/ch/njol/skript/entity/EntityData.java +++ b/src/main/java/ch/njol/skript/entity/EntityData.java @@ -18,29 +18,6 @@ */ package ch.njol.skript.entity; -import java.io.NotSerializableException; -import java.io.StreamCorruptedException; -import java.lang.reflect.Array; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.function.Consumer; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.RegionAccessor; -import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.bukkitutil.EntityUtils; @@ -67,6 +44,27 @@ import ch.njol.util.coll.iterator.SingleItemIterator; import ch.njol.yggdrasil.Fields; import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.RegionAccessor; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.io.NotSerializableException; +import java.io.StreamCorruptedException; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; @SuppressWarnings("rawtypes") public abstract class EntityData implements SyntaxElement, YggdrasilExtendedSerializable {// TODO extended horse support, zombie villagers // REMIND unit @@ -105,8 +103,6 @@ public abstract class EntityData implements SyntaxElement, Ygg // must be here to be initialised before 'new SimpleLiteral' is called in the register block below private final static List>> infos = new ArrayList<>(); - private static final Pattern REGEX_PATTERN = Pattern.compile("[a-zA-Z -]+"); - private static final List ALL_ENTITY_DATAS = new ArrayList<>(); public static Serializer serializer = new Serializer() { @@ -435,8 +431,6 @@ public static EntityDataInfo getInfo(final String codeName) { @SuppressWarnings("null") @Nullable public static EntityData parse(String s) { - if (!REGEX_PATTERN.matcher(s).matches()) - return null; Iterator>> it = infos.iterator(); return SkriptParser.parseStatic(Noun.stripIndefiniteArticle(s), it, null); } @@ -449,8 +443,6 @@ public static EntityData parse(String s) { */ @Nullable public static EntityData parseWithoutIndefiniteArticle(String s) { - if (!REGEX_PATTERN.matcher(s).matches()) - return null; Iterator>> it = infos.iterator(); return SkriptParser.parseStatic(s, it, null); } diff --git a/src/main/java/org/skriptlang/skript/bukkit/displays/DisplayModule.java b/src/main/java/org/skriptlang/skript/bukkit/displays/DisplayModule.java index 024552f3219..7d9e51b28e2 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/displays/DisplayModule.java +++ b/src/main/java/org/skriptlang/skript/bukkit/displays/DisplayModule.java @@ -7,8 +7,11 @@ import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.registrations.Classes; import org.bukkit.entity.Display; +import org.bukkit.entity.Entity; import org.bukkit.entity.ItemDisplay; import org.bukkit.entity.TextDisplay; +import org.skriptlang.skript.lang.converter.Converter; +import org.skriptlang.skript.lang.converter.Converters; import java.io.IOException; @@ -49,6 +52,10 @@ public static void load() throws IOException { .name("Item Display Transforms") .description("Represents the transform setting of an item display.") .since("INSERT VERSION")); + + Converters.registerConverter(Entity.class, Display.class, + entity -> entity instanceof Display display ? display : null, + Converter.NO_RIGHT_CHAINING); } } diff --git a/src/test/skript/tests/regressions/7187-display entities conversion.sk b/src/test/skript/tests/regressions/7187-display entities conversion.sk new file mode 100644 index 00000000000..3c5f27b23e7 --- /dev/null +++ b/src/test/skript/tests/regressions/7187-display entities conversion.sk @@ -0,0 +1,7 @@ +test "display entities conversion": + parse: + spawn block display of minecraft:stone at test-location + set transformation translation of last spawned block display to vector(-0.5,-0.5,-0.5) + assert transformation translation of last spawned block display is vector(-0.5,-0.5,-0.5) with "failed to convert block display" + delete last spawned block display + assert parse logs is not set with "failed to convert block display" From 9111b87b8226be40dcf78e2937c5ed903d9a61ee Mon Sep 17 00:00:00 2001 From: _tud <98935832+UnderscoreTud@users.noreply.github.com> Date: Sat, 23 Nov 2024 07:42:20 +0300 Subject: [PATCH 05/19] Handle invalid regex pattern in ExprJoinSplit (#7202) * handle invalid regex patterns * missing space in toString Co-authored-by: Efnilite <35348263+Efnilite@users.noreply.github.com> --------- Co-authored-by: Efnilite <35348263+Efnilite@users.noreply.github.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../skript/expressions/ExprJoinSplit.java | 67 ++++++++++++++----- .../7159-regex exceptions not handled.sk | 12 ++++ 2 files changed, 61 insertions(+), 18 deletions(-) create mode 100644 src/test/skript/tests/regressions/7159-regex exceptions not handled.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java b/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java index 8535ca69cb7..c58e13d2953 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java +++ b/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java @@ -19,8 +19,10 @@ package ch.njol.skript.expressions; import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; import ch.njol.skript.SkriptConfig; +import ch.njol.skript.lang.Literal; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -36,14 +38,11 @@ import ch.njol.util.Kleenean; import ch.njol.util.StringUtils; -/** - * @author Peter Güttinger - */ @Name("Join & Split") @Description("Joins several texts with a common delimiter (e.g. \", \"), or splits a text into multiple texts at a given delimiter.") @Examples({ - "message \"Online players: %join all players with \"\" | \"\"%\" # %all players% would use the default \"x, y, and z\"", - "set {_s::*} to the string argument split at \",\"" + "message \"Online players: %join all players' names with \"\" | \"\"%\" # %all players% would use the default \"x, y, and z\"", + "set {_s::*} to the string argument split at \",\"" }) @Since("2.1, 2.5.2 (regex support), 2.7 (case sensitivity)") public class ExprJoinSplit extends SimpleExpression { @@ -61,33 +60,47 @@ public class ExprJoinSplit extends SimpleExpression { private boolean regex; private boolean caseSensitivity; - @SuppressWarnings("null") private Expression strings; - @Nullable - private Expression delimiter; + private @Nullable Expression delimiter; + + private @Nullable Pattern pattern; @Override - @SuppressWarnings({"unchecked", "null"}) public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { join = matchedPattern == 0; regex = matchedPattern >= 3; caseSensitivity = SkriptConfig.caseSensitive.value() || parseResult.hasTag("case"); + //noinspection unchecked strings = (Expression) exprs[0]; + //noinspection unchecked delimiter = (Expression) exprs[1]; + if (!join && delimiter instanceof Literal) { + String stringPattern = ((Literal) delimiter).getSingle(); + try { + this.pattern = compilePattern(stringPattern); + } catch (PatternSyntaxException e) { + Skript.error("'" + stringPattern + "' is not a valid regular expression"); + return false; + } + } return true; } @Override - @Nullable - protected String[] get(Event event) { + protected String @Nullable [] get(Event event) { String[] strings = this.strings.getArray(event); String delimiter = this.delimiter != null ? this.delimiter.getSingle(event) : ""; if (strings.length == 0 || delimiter == null) return new String[0]; - if (join) { + if (join) return new String[] {StringUtils.join(strings, delimiter)}; - } else { - return strings[0].split(regex ? delimiter : (caseSensitivity ? "" : "(?i)") + Pattern.quote(delimiter), -1); + try { + Pattern pattern = this.pattern; + if (pattern == null) + pattern = compilePattern(delimiter); + return pattern.split(strings[0], -1); + } catch (PatternSyntaxException e) { + return new String[0]; } } @@ -103,10 +116,28 @@ public Class getReturnType() { @Override public String toString(@Nullable Event event, boolean debug) { - if (join) - return "join " + strings.toString(event, debug) + (delimiter != null ? " with " + delimiter.toString(event, debug) : ""); - return (regex ? "regex " : "") + "split " + strings.toString(event, debug) + (delimiter != null ? " at " + delimiter.toString(event, debug) : "") - + (regex ? "" : "(case sensitive: " + caseSensitivity + ")"); + StringBuilder builder = new StringBuilder(); + if (join) { + builder.append("join ").append(strings.toString(event, debug)); + if (delimiter != null) + builder.append(" with ").append(delimiter.toString(event, debug)); + return builder.toString(); + } + + assert delimiter != null; + if (regex) + builder.append("regex "); + builder.append("split ") + .append(strings.toString(event, debug)) + .append(" at ") + .append(delimiter.toString(event, debug)); + if (!regex) + builder.append(" (case sensitive: ").append(caseSensitivity).append(")"); + return builder.toString(); + } + + private Pattern compilePattern(String delimiter) { + return Pattern.compile(regex ? delimiter : (caseSensitivity ? "" : "(?i)") + Pattern.quote(delimiter)); } } diff --git a/src/test/skript/tests/regressions/7159-regex exceptions not handled.sk b/src/test/skript/tests/regressions/7159-regex exceptions not handled.sk new file mode 100644 index 00000000000..691fe8e0da8 --- /dev/null +++ b/src/test/skript/tests/regressions/7159-regex exceptions not handled.sk @@ -0,0 +1,12 @@ +test "regex exceptions not handled": + parse: + set {_split::*} to regex split "test" at "\b{_name}\b" + assert last parse logs is "'\b{_name}\b' is not a valid regular expression" with "regex split did not error with invalid regex literal" + + set {_pattern} to "\b{_name}\b" + set {_split::*} to regex split "test" at {_pattern} + assert {_split::*} is not set with "regex split returned a value with invalid regex expression" + + assert regex split "apple,banana;cherry" at "[,;]" is "apple", "banana" and "cherry" with "regex split did not split correctly with literal" + set {_pattern} to "[,;]" + assert regex split "apple,banana;cherry" at {_pattern} is "apple", "banana" and "cherry" with "regex split did not split correctly with expression" From 14993837515a0b188bfd7259bc387a7c3ff61d7d Mon Sep 17 00:00:00 2001 From: Eren <67760502+erenkarakal@users.noreply.github.com> Date: Sat, 23 Nov 2024 07:52:30 +0300 Subject: [PATCH 06/19] Fix some messages in Turkish (#7195) fix turkish.lang Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- src/main/resources/lang/turkish.lang | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/lang/turkish.lang b/src/main/resources/lang/turkish.lang index fc2ffdcaaca..4946c6f5d9e 100644 --- a/src/main/resources/lang/turkish.lang +++ b/src/main/resources/lang/turkish.lang @@ -47,7 +47,7 @@ skript command: test: Skript'in dahili testleri için kullanılır. invalid script: '%s' adlı script scripts klasöründe bulunamadı! - invalid folder: '%s' adlı klasör scripts klasöründe bulunamadı! + invalid folder: '%s' adlı klasör scripts klasöründe bulunamadı! reload: warning line info: Satır %s: (%s)\n error line info: Satır %s: (%s)\n @@ -165,7 +165,7 @@ aliases: unknown variation: %s varyasyonu daha önce tanımlanmadı. missing aliases: Bu Minecraft ID'lerinin herhangi bir alias'ı yoktur: empty alias: Alias'ın tanımlı bir Minecraft ID'si veya tag'i yok. - invalid minecraft id: Minecraft ID %s geçerli değil + invalid minecraft id: Minecraft ID %s geçerli değil useless variation: Varyasyonun Minecraft ID'si veya tag'i yok, bu yüzden gereksiz. invalid tags: Belirtilen tagler geçerli JSON'da tanımlı değil. unexpected section: Section'lara burada izin verilmiyor. From 6ba6706906658ec312714126167f73e584fbe636 Mon Sep 17 00:00:00 2001 From: _tud <98935832+UnderscoreTud@users.noreply.github.com> Date: Sat, 23 Nov 2024 07:53:46 +0300 Subject: [PATCH 07/19] Input API (#7190) * Input API * Add `Minecraft 1.21.3+` to the requirements of all the added elements * Requested changes * Add a way to check whether a key is toggled, pressed or released * Add note to event description * Requested Changes * Revert unchanged files * Remove toString method of InputKey * Requested changes * Replace StringBuilder with SyntaxStringBuilder * Fix version checks * Fix tests failing on unsupported versions * Fix tests failing on unsupported versions * replace spaces with tabs Co-authored-by: Patrick Miller * Update src/main/java/org/skriptlang/skript/bukkit/input/InputModule.java Co-authored-by: Patrick Miller * update pattern * Fix event example --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Efnilite <35348263+Efnilite@users.noreply.github.com> Co-authored-by: Patrick Miller --- src/main/java/ch/njol/skript/Skript.java | 4 +- .../ch/njol/skript/lang/ExpressionList.java | 10 +- .../ch/njol/util/coll/CollectionUtils.java | 38 ++++-- .../skript/bukkit/input/InputKey.java | 55 ++++++++ .../skript/bukkit/input/InputModule.java | 42 ++++++ .../conditions/CondIsPressingKey.java | 96 ++++++++++++++ .../input/elements/events/EvtPlayerInput.java | 124 ++++++++++++++++++ .../expressions/ExprCurrentInputKeys.java | 77 +++++++++++ src/main/resources/lang/default.lang | 11 ++ .../conditions/CondIsPressingKeyTest.java | 74 +++++++++++ .../syntaxes/events/EvtPlayerInputTest.java | 40 ++++++ .../expressions/ExprCurrentInputKeysTest.java | 54 ++++++++ .../skript/test/utils/InputHelper.java | 33 +++++ src/test/skript/junit/EvtPlayerInput.sk | 62 +++++++++ 14 files changed, 699 insertions(+), 21 deletions(-) create mode 100644 src/main/java/org/skriptlang/skript/bukkit/input/InputKey.java create mode 100644 src/main/java/org/skriptlang/skript/bukkit/input/InputModule.java create mode 100644 src/main/java/org/skriptlang/skript/bukkit/input/elements/conditions/CondIsPressingKey.java create mode 100644 src/main/java/org/skriptlang/skript/bukkit/input/elements/events/EvtPlayerInput.java create mode 100644 src/main/java/org/skriptlang/skript/bukkit/input/elements/expressions/ExprCurrentInputKeys.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/conditions/CondIsPressingKeyTest.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/events/EvtPlayerInputTest.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/ExprCurrentInputKeysTest.java create mode 100644 src/test/java/org/skriptlang/skript/test/utils/InputHelper.java create mode 100644 src/test/skript/junit/EvtPlayerInput.sk diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 8b03eaca40b..efcf5622457 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -96,6 +96,7 @@ import org.skriptlang.skript.bukkit.SkriptMetrics; import org.skriptlang.skript.bukkit.breeding.BreedingModule; import org.skriptlang.skript.bukkit.displays.DisplayModule; +import org.skriptlang.skript.bukkit.input.InputModule; import org.skriptlang.skript.lang.comparator.Comparator; import org.skriptlang.skript.lang.comparator.Comparators; import org.skriptlang.skript.lang.converter.Converter; @@ -555,8 +556,9 @@ public void onEnable() { "conditions", "effects", "events", "expressions", "entity", "sections", "structures"); getAddonInstance().loadClasses("org.skriptlang.skript.bukkit", "misc"); // todo: become proper module once registry api is merged - DisplayModule.load(); BreedingModule.load(); + DisplayModule.load(); + InputModule.load(); } catch (final Exception e) { exception(e, "Could not load required .class files: " + e.getLocalizedMessage()); setEnabled(false); diff --git a/src/main/java/ch/njol/skript/lang/ExpressionList.java b/src/main/java/ch/njol/skript/lang/ExpressionList.java index e4b68bd880c..a99a1b21662 100644 --- a/src/main/java/ch/njol/skript/lang/ExpressionList.java +++ b/src/main/java/ch/njol/skript/lang/ExpressionList.java @@ -155,15 +155,7 @@ public boolean isSingle() { @Override public boolean check(Event event, Checker checker, boolean negated) { - for (Expression expr : expressions) { - boolean result = expr.check(event, checker) ^ negated; - // exit early if we find a FALSE and we're ANDing, or a TRUE and we're ORing - if (and && !result) - return false; - if (!and && result) - return true; - } - return and; + return CollectionUtils.check(expressions, expr -> expr.check(event, checker) ^ negated, and); } @Override diff --git a/src/main/java/ch/njol/util/coll/CollectionUtils.java b/src/main/java/ch/njol/util/coll/CollectionUtils.java index 8b48a505662..ecda9c0294f 100644 --- a/src/main/java/ch/njol/util/coll/CollectionUtils.java +++ b/src/main/java/ch/njol/util/coll/CollectionUtils.java @@ -19,20 +19,13 @@ package ch.njol.util.coll; import ch.njol.util.Pair; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; import java.lang.reflect.Array; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Objects; -import java.util.Random; -import java.util.Set; +import java.util.function.Predicate; /** * Utils for collections and arrays. All methods will not print any errors for null collections/arrays, but will return false/-1/etc. @@ -102,7 +95,30 @@ public static boolean containsAll(final @Nullable T[] array, final @Nullable } return true; } - + + /** + * Checks the elements of an array against a given predicate. + * + * @param array the array to check, can be null + * @param predicate the predicate to test the elements against + * @param and if true, all elements must satisfy the predicate; if false, any element satisfying the predicate is enough + * @param the type of elements in the array + * @return true if the condition is met based on the value of the 'and' parameter, false otherwise + */ + public static boolean check(T @Nullable [] array, Predicate predicate, boolean and) { + if (array == null) + return false; + for (T value : array) { + boolean result = predicate.test(value); + // exit early if we find a FALSE and we're ANDing, or a TRUE and we're ORing + if (and && !result) + return false; + if (!and && result) + return true; + } + return and; + } + public static int indexOf(final @Nullable int[] array, final int num) { if (array == null) return -1; diff --git a/src/main/java/org/skriptlang/skript/bukkit/input/InputKey.java b/src/main/java/org/skriptlang/skript/bukkit/input/InputKey.java new file mode 100644 index 00000000000..eef0e870b50 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/input/InputKey.java @@ -0,0 +1,55 @@ +package org.skriptlang.skript.bukkit.input; + +import org.bukkit.Input; + +import java.util.EnumSet; +import java.util.Set; + +/** + * Enum representing different movement input keys. + * @see Input + */ +public enum InputKey { + + FORWARD, + BACKWARD, + RIGHT, + LEFT, + JUMP, + SNEAK, + SPRINT; + + /** + * Checks if the given {@link Input} is pressing this {@link InputKey}. + * + * @param input the input to check + * @return true if the {@link Input} is pressing this {@link InputKey}, false otherwise + */ + public boolean check(Input input) { + return switch (this) { + case FORWARD -> input.isForward(); + case BACKWARD -> input.isBackward(); + case RIGHT -> input.isRight(); + case LEFT -> input.isLeft(); + case JUMP -> input.isJump(); + case SNEAK -> input.isSneak(); + case SPRINT -> input.isSprint(); + }; + } + + /** + * Returns a set of {@link InputKey}s that match the given {@link Input}. + * + * @param input the input to check + * @return a set of {@link InputKey}s that match the given {@link Input} + */ + public static Set fromInput(Input input) { + Set keys = EnumSet.noneOf(InputKey.class); + for (InputKey key : values()) { + if (key.check(input)) + keys.add(key); + } + return keys; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/input/InputModule.java b/src/main/java/org/skriptlang/skript/bukkit/input/InputModule.java new file mode 100644 index 00000000000..862c08fc3d8 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/input/InputModule.java @@ -0,0 +1,42 @@ +package org.skriptlang.skript.bukkit.input; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.EnumClassInfo; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.registrations.EventValues; +import ch.njol.skript.util.Getter; +import org.bukkit.event.player.PlayerInputEvent; + +import java.io.IOException; + +public class InputModule { + + public static void load() throws IOException { + if (!Skript.classExists("org.bukkit.Input")) + return; + + Skript.getAddonInstance().loadClasses("org.skriptlang.skript.bukkit.input.elements"); + + Classes.registerClass(new EnumClassInfo<>(InputKey.class, "inputkey", "input keys") + .user("input ?keys?") + .name("Input Key") + .description("Represents a movement input key that is pressed by a player.") + .since("INSERT VERSION") + .requiredPlugins("Minecraft 1.21.3+")); + + EventValues.registerEventValue(PlayerInputEvent.class, InputKey[].class, new Getter<>() { + @Override + public InputKey[] get(PlayerInputEvent event) { + return InputKey.fromInput(event.getInput()).toArray(new InputKey[0]); + } + }, EventValues.TIME_NOW); + + EventValues.registerEventValue(PlayerInputEvent.class, InputKey[].class, new Getter<>() { + @Override + public InputKey[] get(PlayerInputEvent event) { + return InputKey.fromInput(event.getPlayer().getCurrentInput()).toArray(new InputKey[0]); + } + }, EventValues.TIME_PAST); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/input/elements/conditions/CondIsPressingKey.java b/src/main/java/org/skriptlang/skript/bukkit/input/elements/conditions/CondIsPressingKey.java new file mode 100644 index 00000000000..5842b0447f5 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/input/elements/conditions/CondIsPressingKey.java @@ -0,0 +1,96 @@ +package org.skriptlang.skript.bukkit.input.elements.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.*; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Input; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerInputEvent; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.input.InputKey; + +@Name("Is Pressing Key") +@Description("Checks if a player is pressing a certain input key.") +@Examples({ + "on player input:", + "\tif player is pressing forward movement key:", + "\t\tsend \"You are moving forward!\"" +}) +@Since("INSERT VERSION") +@Keywords({"press", "input"}) +@RequiredPlugins("Minecraft 1.21.2+") +public class CondIsPressingKey extends Condition { + + static { + if (Skript.classExists("org.bukkit.event.player.PlayerInputEvent")) { + Skript.registerCondition(CondIsPressingKey.class, + "%players% (is|are) pressing %inputkeys%", + "%players% (isn't|is not|aren't|are not) pressing %inputkeys%", + "%players% (was|were) pressing %inputkeys%", + "%players% (wasn't|was not|weren't|were not) pressing %inputkeys%" + ); + } else { + Skript.registerCondition(CondIsPressingKey.class, + "%players% (is|are) pressing %inputkeys%", + "%players% (isn't|is not|aren't|are not) pressing %inputkeys%" + ); + } + } + + private Expression players; + private Expression inputKeys; + private boolean past; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + players = (Expression) expressions[0]; + //noinspection unchecked + inputKeys = (Expression) expressions[1]; + past = matchedPattern > 1; + if (past && !getParser().isCurrentEvent(PlayerInputEvent.class)) + Skript.warning("Checking the past state of a player's input outside the 'player input' event has no effect."); + setNegated(matchedPattern == 1 || matchedPattern == 3); + return true; + } + + @Override + public boolean check(Event event) { + Player eventPlayer = event instanceof PlayerInputEvent inputEvent ? inputEvent.getPlayer() : null; + InputKey[] inputKeys = this.inputKeys.getAll(event); + boolean and = this.inputKeys.getAnd(); + return players.check(event, player -> { + Input input; + // If we want to get the new input of the event-player, we must get it from the event + if (!past && player.equals(eventPlayer)) { + input = ((PlayerInputEvent) event).getInput(); + } else { // Otherwise, we get the current (or past in case of an event-player) input + input = player.getCurrentInput(); + } + return CollectionUtils.check(inputKeys, inputKey -> inputKey.check(input), and); + }, isNegated()); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + builder.append(players); + if (past) { + builder.append(players.isSingle() ? "was" : "were"); + } else { + builder.append(players.isSingle() ? "is" : "are"); + } + if (isNegated()) + builder.append("not"); + builder.append("pressing"); + builder.append(inputKeys); + return builder.toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/input/elements/events/EvtPlayerInput.java b/src/main/java/org/skriptlang/skript/bukkit/input/elements/events/EvtPlayerInput.java new file mode 100644 index 00000000000..6decbb27207 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/input/elements/events/EvtPlayerInput.java @@ -0,0 +1,124 @@ +package org.skriptlang.skript.bukkit.input.elements.events; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerInputEvent; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.input.InputKey; + +import java.util.Set; + +public class EvtPlayerInput extends SkriptEvent { + + static { + if (Skript.classExists("org.bukkit.event.player.PlayerInputEvent")) { + Skript.registerEvent("Player Input", EvtPlayerInput.class, PlayerInputEvent.class, + "[player] (toggle|toggling|1:press[ing]|2:release|2:releasing) of (%-inputkeys%|(an|any) input key)", + "([player] %-inputkeys%|[an|any [player]] input key) (toggle|toggling|1:press[ing]|2:release|2:releasing)") + .description("Called when a player sends an updated input to the server.", + "Note: The input keys event value is the set of keys the player is currently pressing, not the keys that were pressed or released.") + .examples("on any input key press:", + "\tsend \"You are pressing: %event-inputkeys%\" to player") + .since("INSERT VERSION") + .requiredPlugins("Minecraft 1.21.3+"); + } + } + + private @Nullable Literal keysToCheck; + private InputType type; + + @Override + public boolean init(Literal[] args, int matchedPattern, ParseResult parseResult) { + //noinspection unchecked + keysToCheck = (Literal) args[0]; + type = InputType.values()[parseResult.mark]; + return true; + } + + @Override + public boolean check(Event event) { + PlayerInputEvent inputEvent = (PlayerInputEvent) event; + Set previousKeys = InputKey.fromInput(inputEvent.getPlayer().getCurrentInput()); + Set currentKeys = InputKey.fromInput(inputEvent.getInput()); + Set keysToCheck = this.keysToCheck != null ? Set.of(this.keysToCheck.getAll()) : null; + boolean and = this.keysToCheck != null && this.keysToCheck.getAnd(); + return type.checkInputKeys(previousKeys, currentKeys, keysToCheck, and); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + builder.append("player"); + builder.append(type.name().toLowerCase()); + builder.append(keysToCheck == null ? "any input key" : keysToCheck); + return builder.toString(); + } + + private enum InputType { + + TOGGLE { + @Override + public boolean checkKeyState(boolean inPrevious, boolean inCurrent) { + return inPrevious != inCurrent; + } + }, + PRESS { + @Override + public boolean checkKeyState(boolean inPrevious, boolean inCurrent) { + return !inPrevious && inCurrent; + } + }, + RELEASE { + @Override + public boolean checkKeyState(boolean inPrevious, boolean inCurrent) { + return inPrevious && !inCurrent; + } + }; + + /** + * Checks the state of a key based on its presence in the previous and current sets of keys. + * + * @param inPrevious true if the key was present in the previous set of keys, false otherwise + * @param inCurrent true if the key is present in the current set of keys, false otherwise + * @return true if the key state matches the condition defined by the input type, false otherwise + */ + public abstract boolean checkKeyState(boolean inPrevious, boolean inCurrent); + + /** + * Checks the input keys based on the previous and current sets of keys. + *
+ * {@code previous} and {@code current} are never the same. + * + * @param previous the set of keys before the input change + * @param current the set of keys after the input change + * @param keysToCheck the set of keys to check against, can be null + * @param and true if the keys to check must all be present, false if any key is enough + * @return true if the condition is met based on the input type, false otherwise + */ + public boolean checkInputKeys(Set previous, Set current, @Nullable Set keysToCheck, boolean and) { + if (keysToCheck == null) { + return switch (this) { + case TOGGLE -> true; + case PRESS -> previous.size() <= current.size(); + case RELEASE -> previous.size() >= current.size(); + }; + } + for (InputKey key : keysToCheck) { + boolean inPrevious = previous.contains(key); + boolean inCurrent = current.contains(key); + if (and && !checkKeyState(inPrevious, inCurrent)) { + return false; + } else if (!and && checkKeyState(inPrevious, inCurrent)) { + return true; + } + } + return and; + } + + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/input/elements/expressions/ExprCurrentInputKeys.java b/src/main/java/org/skriptlang/skript/bukkit/input/elements/expressions/ExprCurrentInputKeys.java new file mode 100644 index 00000000000..ddc4372215c --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/input/elements/expressions/ExprCurrentInputKeys.java @@ -0,0 +1,77 @@ +package org.skriptlang.skript.bukkit.input.elements.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.*; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.registrations.EventValues; +import ch.njol.util.Kleenean; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.player.PlayerInputEvent; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.input.InputKey; + +import java.util.ArrayList; +import java.util.List; + +@Name("Player Input Keys") +@Description("Get the current input keys of a player.") +@Examples("broadcast \"%player% is pressing %current input keys of player%\"") +@Since("INSERT VERSION") +@RequiredPlugins("Minecraft 1.21.2+") +public class ExprCurrentInputKeys extends PropertyExpression { + + private static final boolean SUPPORTS_TIME_STATES = Skript.classExists("org.bukkit.event.player.PlayerInputEvent"); + + static { + register(ExprCurrentInputKeys.class, InputKey.class, "[current] (inputs|input keys)", "players"); + } + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr((Expression) expressions[0]); + return true; + } + + @Override + protected InputKey[] get(Event event, Player[] source) { + Player eventPlayer = null; + if (SUPPORTS_TIME_STATES && getTime() == EventValues.TIME_NOW && event instanceof PlayerInputEvent inputEvent) + eventPlayer = inputEvent.getPlayer(); + + List inputKeys = new ArrayList<>(); + for (Player player : source) { + if (player.equals(eventPlayer)) { + inputKeys.addAll(InputKey.fromInput(((PlayerInputEvent) event).getInput())); + } else { + inputKeys.addAll(InputKey.fromInput(player.getCurrentInput())); + } + } + return inputKeys.toArray(new InputKey[0]); + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class getReturnType() { + return InputKey.class; + } + + @Override + public boolean setTime(int time) { + if (!SUPPORTS_TIME_STATES) + return super.setTime(time); + return time != EventValues.TIME_FUTURE && setTime(time, PlayerInputEvent.class); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the current input keys of " + getExpr().toString(event, debug); + } + +} diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index c463f236c01..fd9a449be22 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -2418,6 +2418,16 @@ experience cooldown change reasons: plugin: plugin pickup_orb: orb pickup, pickup orb +# -- Input Keys -- +input keys: + forward: forward movement key, forward key + backward: backward movement key, backward key + left: left movement key, left key + right: right movement key, right key + jump: jump key, jumping key + sneak: sneak key, sneaking key + sprint: sprint key, sprinting key + # -- Boolean -- boolean: true: @@ -2504,6 +2514,7 @@ types: textalignment: text alignment¦s @a itemdisplaytransform: item display transform¦s @an experiencecooldownchangereason: experience cooldown change reason¦s @a + inputkey: input key¦s @an # Skript weathertype: weather type¦s @a diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/conditions/CondIsPressingKeyTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/conditions/CondIsPressingKeyTest.java new file mode 100644 index 00000000000..8461d01954d --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/conditions/CondIsPressingKeyTest.java @@ -0,0 +1,74 @@ +package org.skriptlang.skript.test.tests.syntaxes.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.util.ContextlessEvent; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.skript.variables.Variables; +import org.bukkit.entity.Player; +import org.easymock.EasyMock; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.skriptlang.skript.bukkit.input.InputKey; +import org.skriptlang.skript.test.utils.InputHelper; + +public class CondIsPressingKeyTest extends SkriptJUnitTest { + + private static final boolean SUPPORTS_INPUT = Skript.classExists("org.bukkit.Input"); + + static { + setShutdownDelay(1); + } + + private Player testPlayer; + private InputKey[] testInputKeys; + private Condition isPressingKeyCondition; + + @Before + public void setup() { + if (!SUPPORTS_INPUT) + return; + testPlayer = EasyMock.niceMock(Player.class); + testInputKeys = new InputKey[]{InputKey.FORWARD, InputKey.JUMP}; + isPressingKeyCondition = Condition.parse("{_player} is pressing {_input-keys::*}", null); + } + + @Test + public void test() { + if (!SUPPORTS_INPUT) + return; + if (isPressingKeyCondition == null) + Assert.fail("Is pressing key condition is null"); + + ContextlessEvent event = ContextlessEvent.get(); + Variables.setVariable("player", testPlayer, event, true); + Variables.setVariable("input-keys::1", testInputKeys[0], event, true); + + EasyMock.expect(testPlayer.getCurrentInput()).andReturn(InputHelper.fromKeys(testInputKeys)); + EasyMock.replay(testPlayer); + assert isPressingKeyCondition.check(event); + EasyMock.verify(testPlayer); + + EasyMock.resetToNice(testPlayer); + EasyMock.expect(testPlayer.getCurrentInput()).andReturn(InputHelper.fromKeys(testInputKeys[1])); + EasyMock.replay(testPlayer); + assert !isPressingKeyCondition.check(event); + EasyMock.verify(testPlayer); + + EasyMock.resetToNice(testPlayer); + Variables.setVariable("input-keys::2", testInputKeys[1], event, true); + EasyMock.expect(testPlayer.getCurrentInput()).andReturn(InputHelper.fromKeys(testInputKeys)); + EasyMock.replay(testPlayer); + assert isPressingKeyCondition.check(event); + EasyMock.verify(testPlayer); + + EasyMock.resetToNice(testPlayer); + Variables.setVariable("input-keys::3", InputKey.SNEAK, event, true); + EasyMock.expect(testPlayer.getCurrentInput()).andReturn(InputHelper.fromKeys(testInputKeys)); + EasyMock.replay(testPlayer); + assert !isPressingKeyCondition.check(event); + EasyMock.verify(testPlayer); + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/events/EvtPlayerInputTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/events/EvtPlayerInputTest.java new file mode 100644 index 00000000000..782b29f8ae0 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/events/EvtPlayerInputTest.java @@ -0,0 +1,40 @@ +package org.skriptlang.skript.test.tests.syntaxes.events; + +import ch.njol.skript.Skript; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.Test; +import org.skriptlang.skript.bukkit.input.InputKey; +import org.skriptlang.skript.test.utils.InputHelper; + +public class EvtPlayerInputTest extends SkriptJUnitTest { + + private static final boolean SUPPORTS_INPUT_EVENT = Skript.classExists("org.bukkit.event.player.PlayerInputEvent"); + + static { + setShutdownDelay(1); + } + + private Player player; + + @Before + public void setup() { + if (!SUPPORTS_INPUT_EVENT) + return; + player = EasyMock.niceMock(Player.class); + } + + @Test + public void test() { + if (!SUPPORTS_INPUT_EVENT) + return; + EasyMock.expect(player.getCurrentInput()).andStubReturn(InputHelper.fromKeys(InputKey.FORWARD)); + EasyMock.replay(player); + Bukkit.getPluginManager().callEvent(InputHelper.createPlayerInputEvent(player, InputKey.FORWARD, InputKey.JUMP)); + EasyMock.verify(player); + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/ExprCurrentInputKeysTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/ExprCurrentInputKeysTest.java new file mode 100644 index 00000000000..06f57ca0ee3 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/ExprCurrentInputKeysTest.java @@ -0,0 +1,54 @@ +package org.skriptlang.skript.test.tests.syntaxes.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.util.ContextlessEvent; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.skript.variables.Variables; +import org.bukkit.entity.Player; +import org.easymock.EasyMock; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.skriptlang.skript.bukkit.input.InputKey; +import org.skriptlang.skript.test.utils.InputHelper; + +public class ExprCurrentInputKeysTest extends SkriptJUnitTest { + + private static final boolean SUPPORTS_INPUT = Skript.classExists("org.bukkit.Input"); + + static { + setShutdownDelay(1); + } + + private Player player; + private Expression inputKeyExpression; + + @Before + public void setup() { + if (!SUPPORTS_INPUT) + return; + player = EasyMock.niceMock(Player.class); + //noinspection unchecked + inputKeyExpression = new SkriptParser("input keys of {_player}").parseExpression(InputKey.class); + } + + @Test + public void test() { + if (!SUPPORTS_INPUT) + return; + if (inputKeyExpression == null) + Assert.fail("Input keys expression is null"); + + ContextlessEvent event = ContextlessEvent.get(); + Variables.setVariable("player", player, event, true); + + EasyMock.expect(player.getCurrentInput()).andReturn(InputHelper.fromKeys(InputKey.FORWARD, InputKey.JUMP)); + EasyMock.replay(player); + InputKey[] keys = inputKeyExpression.getArray(event); + Assert.assertArrayEquals(keys, new InputKey[]{InputKey.FORWARD, InputKey.JUMP}); + EasyMock.verify(player); + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/utils/InputHelper.java b/src/test/java/org/skriptlang/skript/test/utils/InputHelper.java new file mode 100644 index 00000000000..b3ba4112a1b --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/utils/InputHelper.java @@ -0,0 +1,33 @@ +package org.skriptlang.skript.test.utils; + +import org.bukkit.Input; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.event.player.PlayerInputEvent; +import org.easymock.EasyMock; +import org.skriptlang.skript.bukkit.input.InputKey; + +public class InputHelper { + + public static PlayerEvent createPlayerInputEvent(Player player, InputKey... keys) { + return new PlayerInputEvent(player, fromKeys(keys)); + } + + public static Input fromKeys(InputKey... keys) { + Input input = EasyMock.niceMock(Input.class); + for (InputKey key : keys) { + switch (key) { + case FORWARD -> EasyMock.expect(input.isForward()).andStubReturn(true); + case BACKWARD -> EasyMock.expect(input.isBackward()).andStubReturn(true); + case RIGHT -> EasyMock.expect(input.isRight()).andStubReturn(true); + case LEFT -> EasyMock.expect(input.isLeft()).andStubReturn(true); + case JUMP -> EasyMock.expect(input.isJump()).andStubReturn(true); + case SNEAK -> EasyMock.expect(input.isSneak()).andStubReturn(true); + case SPRINT -> EasyMock.expect(input.isSprint()).andStubReturn(true); + } + } + EasyMock.replay(input); + return input; + } + +} diff --git a/src/test/skript/junit/EvtPlayerInput.sk b/src/test/skript/junit/EvtPlayerInput.sk new file mode 100644 index 00000000000..87c8822ec93 --- /dev/null +++ b/src/test/skript/junit/EvtPlayerInput.sk @@ -0,0 +1,62 @@ +test "EvtPlayerInputJUnit" when running JUnit: + running minecraft "1.21.3" + set {_tests::1} to "player is pressing event-inputkeys" + set {_tests::2} to "inputs of player is event-inputkeys" + set {_tests::3} to "past inputs of player is past event-inputkeys" + set {_tests::4} to "player is pressing forward key" + set {_tests::5} to "player is pressing jump key" + set {_tests::6} to "player is pressing forward key and jump key" + set {_tests::7} to "player is pressing forward key, sneak key, or backward key" + set {_tests::8} to "player is not pressing sneak key" + set {_tests::9} to "player is not pressing forward key and sneak key" + set {_tests::10} to "player was pressing forward key" + set {_tests::11} to "player was not pressing jump key" + + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.events.EvtPlayerInputTest" completes {_tests::*} + +parse: + results: {EvtPlayerInput::parse::*} + code: + on toggle of any input key: + set {_test} to "org.skriptlang.skript.test.tests.syntaxes.events.EvtPlayerInputTest" + junit test is {_test} + + if player is pressing event-inputkeys: + complete objective "player is pressing event-inputkeys" for {_test} + + if inputs of player is event-inputkeys: + complete objective "inputs of player is event-inputkeys" for {_test} + + if past inputs of player is past event-inputkeys: + complete objective "past inputs of player is past event-inputkeys" for {_test} + + if player is pressing forward key: + complete objective "player is pressing forward key" for {_test} + + if player is pressing jump key: + complete objective "player is pressing jump key" for {_test} + + if player is pressing forward key and jump key: + complete objective "player is pressing forward key and jump key" for {_test} + + if player is pressing forward key, sneak key, or backward key: + complete objective "player is pressing forward key, sneak key, or backward key" for {_test} + + if player is not pressing sneak key: + complete objective "player is not pressing sneak key" for {_test} + + if player is not pressing forward key and sneak key: + complete objective "player is not pressing forward key and sneak key" for {_test} + + if player was pressing forward key: + complete objective "player was pressing forward key" for {_test} + + if player was not pressing jump key: + complete objective "player was not pressing jump key" for {_test} + +test "EvtPlayerInput": + if running minecraft "1.21.3": + assert {EvtPlayerInput::parse::*} is not set with "Player input event failed to parse in 1.21.3 and above" + else: + assert {EvtPlayerInput::parse::*} is set with "Player input event successfully parsed in 1.21.2 and below" + From 75d0701d2359d326738c2d94f426ff370be78b99 Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Sat, 23 Nov 2024 06:00:37 +0100 Subject: [PATCH 08/19] Async alias loading (#7084) * update * await aliases before test loading * update load * move aliases * add old method * oops * update comment * fix imports --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- src/main/java/ch/njol/skript/Skript.java | 34 +++------- .../java/ch/njol/skript/SkriptCommand.java | 21 +++--- .../java/ch/njol/skript/aliases/Aliases.java | 66 ++++++++++++++----- 3 files changed, 70 insertions(+), 51 deletions(-) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index efcf5622457..4b9418c5636 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -132,6 +132,7 @@ import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicLong; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -262,17 +263,6 @@ public static ServerPlatform getServerPlatform() { } } - /** - * Returns true if the underlying installed Java/JVM is 32-bit, false otherwise. - * Note that this depends on a internal system property and these can always be overridden by user using -D JVM options, - * more specifically, this method will return false on non OracleJDK/OpenJDK based JVMs, that don't include bit information in java.vm.name system property. - * @return Whether the installed Java/JVM is 32-bit or not. - */ - private static boolean using32BitJava() { - // Property returned should either be "Java HotSpot(TM) 32-Bit Server VM" or "OpenJDK 32-Bit Server VM" if 32-bit and using OracleJDK/OpenJDK - return System.getProperty("java.vm.name").contains("32"); - } - /** * Checks if server software and Minecraft version are supported. * Prints errors or warnings to console if something is wrong. @@ -503,6 +493,8 @@ public void onEnable() { // ... but also before platform check, because there is a config option to ignore some errors SkriptConfig.load(); + CompletableFuture aliases = Aliases.loadAsync(); + // Now override the verbosity if test mode is enabled if (TestMode.VERBOSITY != null) SkriptLogger.setVerbosity(Verbosity.valueOf(TestMode.VERBOSITY)); @@ -515,20 +507,6 @@ public void onEnable() { updater.updateCheck(console); } - try { - Aliases.load(); // Loaded before anything that might use them - } catch (StackOverflowError e) { - if (using32BitJava()) { - Skript.error(""); - Skript.error("There was a StackOverflowError that occured while loading aliases."); - Skript.error("As you are currently using 32-bit Java, please update to 64-bit Java to resolve the error."); - Skript.error("Please report this issue to our GitHub only if updating to 64-bit Java does not fix the issue."); - Skript.error(""); - } else { - throw e; // Uh oh, this shouldn't happen. Re-throw the error. - } - } - // If loading can continue (platform ok), check for potentially thrown error if (classLoadError != null) { exception(classLoadError); @@ -605,6 +583,12 @@ public void run() { } finishedLoadingHooks = true; + try { + aliases.get(); // wait for aliases to load + } catch (InterruptedException | ExecutionException e) { + exception(e, "Could not load aliases concurrently"); + } + if (TestMode.ENABLED) { info("Preparing Skript for testing..."); tainted = true; diff --git a/src/main/java/ch/njol/skript/SkriptCommand.java b/src/main/java/ch/njol/skript/SkriptCommand.java index 16050ef8e14..51962e6ad46 100644 --- a/src/main/java/ch/njol/skript/SkriptCommand.java +++ b/src/main/java/ch/njol/skript/SkriptCommand.java @@ -159,15 +159,15 @@ public boolean onCommand(CommandSender sender, Command command, String label, St reloading(sender, "config, aliases and scripts", logHandler); SkriptConfig.load(); Aliases.clear(); - Aliases.load(); - - ScriptLoader.unloadScripts(ScriptLoader.getLoadedScripts()); - ScriptLoader.loadScripts(Skript.getInstance().getScriptsFolder(), OpenCloseable.combine(logHandler, timingLogHandler)) - .thenAccept(info -> { - if (info.files == 0) - Skript.warning(Skript.m_no_scripts.toString()); - reloaded(sender, logHandler, timingLogHandler, "config, aliases and scripts"); - }); + Aliases.loadAsync().thenRun(() -> { + ScriptLoader.unloadScripts(ScriptLoader.getLoadedScripts()); + ScriptLoader.loadScripts(Skript.getInstance().getScriptsFolder(), OpenCloseable.combine(logHandler, timingLogHandler)) + .thenAccept(info -> { + if (info.files == 0) + Skript.warning(Skript.m_no_scripts.toString()); + reloaded(sender, logHandler, timingLogHandler, "config, aliases and scripts"); + }); + }); } else if (args[1].equalsIgnoreCase("scripts")) { reloading(sender, "scripts", logHandler); @@ -185,8 +185,7 @@ public boolean onCommand(CommandSender sender, Command command, String label, St } else if (args[1].equalsIgnoreCase("aliases")) { reloading(sender, "aliases", logHandler); Aliases.clear(); - Aliases.load(); - reloaded(sender, logHandler, timingLogHandler, "aliases"); + Aliases.loadAsync().thenRun(() -> reloaded(sender, logHandler, timingLogHandler, "aliases")); } else { // Reloading an individual Script or folder File scriptFile = getScriptFromArgs(sender, args); if (scriptFile == null) diff --git a/src/main/java/ch/njol/skript/aliases/Aliases.java b/src/main/java/ch/njol/skript/aliases/Aliases.java index b92a4196f84..ad41fdb8508 100644 --- a/src/main/java/ch/njol/skript/aliases/Aliases.java +++ b/src/main/java/ch/njol/skript/aliases/Aliases.java @@ -8,34 +8,29 @@ import ch.njol.skript.config.SectionNode; import ch.njol.skript.entity.EntityData; import ch.njol.skript.lang.parser.ParserInstance; -import ch.njol.skript.localization.ArgsMessage; -import ch.njol.skript.localization.Language; -import ch.njol.skript.localization.Message; -import ch.njol.skript.localization.Noun; -import ch.njol.skript.localization.RegexMessage; +import ch.njol.skript.localization.*; import ch.njol.skript.log.BlockingLogHandler; import ch.njol.skript.util.EnchantmentType; import ch.njol.skript.util.Utils; import ch.njol.skript.util.Version; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.script.Script; + import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; import java.net.URISyntaxException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.nio.file.*; import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.jetbrains.annotations.Nullable; -import org.skriptlang.skript.lang.script.Script; public abstract class Aliases { static final boolean USING_ITEM_COMPONENTS = Skript.isRunningMinecraft(1, 20, 5); @@ -371,7 +366,10 @@ public static void clear() { /** * Loads aliases from Skript's standard locations. * Exceptions will be logged, but not thrown. + * + * @deprecated Freezes server on call. Use {@link #loadAsync()} instead. */ + @Deprecated public static void load() { try { long start = System.currentTimeMillis(); @@ -382,6 +380,44 @@ public static void load() { } } + /** + * Loads aliases from Skript's standard locations asynchronously. + * Exceptions will be logged, but not thrown. + * + * @return A future that completes when the aliases are loaded. + * The returned value is true if the loading was successful, false otherwise. + */ + public static CompletableFuture loadAsync() { + return CompletableFuture.supplyAsync(() -> { + try { + long start = System.currentTimeMillis(); + loadInternal(); + Skript.info("Loaded " + provider.getAliasCount() + " aliases in " + (System.currentTimeMillis() - start) + "ms"); + return true; + } catch (StackOverflowError e) { + /* + * Returns true if the underlying installed Java/JVM is 32-bit, false otherwise. + * Note that this depends on a internal system property and these can always be overridden by user using -D JVM options, + * more specifically, this method will return false on non OracleJDK/OpenJDK based JVMs, that don't include bit information in java.vm.name system property + */ + if (System.getProperty("java.vm.name").contains("32")) { + Skript.error(""); + Skript.error("There was a StackOverflowError that occurred while loading aliases."); + Skript.error("As you are currently using 32-bit Java, please update to 64-bit Java to resolve the error."); + Skript.error("Please report this issue to our GitHub only if updating to 64-bit Java does not fix the issue."); + Skript.error(""); + } else { + Skript.exception(e); + Bukkit.getPluginManager().disablePlugin(Skript.getInstance()); + } + return false; + } catch (IOException e) { + Skript.exception(e); + return false; + } + }); + } + /** * Temporarily create an alias for materials which do not have aliases yet. */ From e2ddd355cf7b141034e682eb8d0e03ffa8a54cff Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sat, 23 Nov 2024 00:02:23 -0500 Subject: [PATCH 09/19] Require skull texture lookup on paper servers (#7152) * require skull texture lookup on paper servers * 1.19.4+ --- src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java index a74a604664d..e447300d6ef 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java @@ -21,6 +21,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.util.slot.Slot; +import com.destroystokyo.paper.profile.PlayerProfile; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.OfflinePlayer; @@ -49,6 +50,8 @@ public class ItemUtils { // Introduced in Paper 1.21 public static final boolean HAS_RESET = Skript.methodExists(Damageable.class, "resetDamage"); public static final boolean CAN_CREATE_PLAYER_PROFILE = Skript.methodExists(Bukkit.class, "createPlayerProfile", UUID.class, String.class); + // paper does not do texture lookups by default + public static final boolean REQUIRES_TEXTURE_LOOKUP = Skript.classExists("com.destroystokyo.paper.profile.PlayerProfile") && Skript.isRunningMinecraft(1, 19, 4); /** * Gets damage/durability of an item, or 0 if it does not have damage. @@ -165,7 +168,12 @@ public static void setHeadOwner(ItemType skull, OfflinePlayer player) { SkullMeta skullMeta = (SkullMeta) meta; - if (player.getName() != null) { + if (REQUIRES_TEXTURE_LOOKUP) { + PlayerProfile profile = player.getPlayerProfile(); + if (!profile.hasTextures()) + profile.complete(true); // BLOCKING MOJANG API CALL + skullMeta.setPlayerProfile(profile); + } else if (player.getName() != null) { skullMeta.setOwningPlayer(player); } else if (CAN_CREATE_PLAYER_PROFILE) { //noinspection deprecation From 49b2a377488ebec42ce7d1ed7d10cd0190f22237 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sat, 23 Nov 2024 00:20:45 -0500 Subject: [PATCH 10/19] Fix some syntaxes overwriting variable list indices and add new helper method for changing expressions. (#7120) * changeInPlace, fix indices being overwritten, regr tests * can't count * requested changes * Apply suggestions from code review Co-authored-by: Patrick Miller --------- Co-authored-by: Patrick Miller --- .../ch/njol/skript/effects/EffEnchant.java | 35 ++++++------ .../ch/njol/skript/effects/EffReplace.java | 51 ++++++++++------- .../skript/expressions/ExprVectorLength.java | 44 +++++++++------ .../skript/expressions/ExprVectorXYZ.java | 55 +++++++++++-------- .../java/ch/njol/skript/lang/Expression.java | 51 +++++++++++++++++ .../java/ch/njol/skript/lang/Variable.java | 46 +++++++++++----- .../pull-7120-changes overwriting indices.sk | 31 +++++++++++ 7 files changed, 222 insertions(+), 91 deletions(-) create mode 100644 src/test/skript/tests/regressions/pull-7120-changes overwriting indices.sk diff --git a/src/main/java/ch/njol/skript/effects/EffEnchant.java b/src/main/java/ch/njol/skript/effects/EffEnchant.java index 016b8c66be8..0e83dfcfefb 100644 --- a/src/main/java/ch/njol/skript/effects/EffEnchant.java +++ b/src/main/java/ch/njol/skript/effects/EffEnchant.java @@ -18,11 +18,6 @@ */ package ch.njol.skript.effects; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.event.Event; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.Changer.ChangeMode; @@ -36,6 +31,11 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.EnchantmentType; import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Function; /** * @author Peter Güttinger @@ -72,29 +72,26 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override protected void execute(Event event) { - ItemType[] items = this.items.getArray(event); - if (items.length == 0) // short circuit - return; + Function changeFunction; if (enchantments != null) { EnchantmentType[] types = enchantments.getArray(event); if (types.length == 0) return; - for (ItemType item : items) { - for (EnchantmentType type : types) { - Enchantment enchantment = type.getType(); - assert enchantment != null; - item.addEnchantments(new EnchantmentType(enchantment, type.getLevel())); - } - } + changeFunction = item -> { + item.addEnchantments(types); + return item; + }; } else { - for (ItemType item : items) { + changeFunction = item -> { item.clearEnchantments(); - } + return item; + }; } - this.items.change(event, items.clone(), ChangeMode.SET); + + this.items.changeInPlace(event, changeFunction); } - + @Override public String toString(@Nullable Event event, boolean debug) { return enchantments == null ? "disenchant " + items.toString(event, debug) : "enchant " + items.toString(event, debug) + " with " + enchantments; diff --git a/src/main/java/ch/njol/skript/effects/EffReplace.java b/src/main/java/ch/njol/skript/effects/EffReplace.java index 0bb52d4eb26..3274bc6dbf9 100644 --- a/src/main/java/ch/njol/skript/effects/EffReplace.java +++ b/src/main/java/ch/njol/skript/effects/EffReplace.java @@ -36,9 +36,11 @@ import org.bukkit.event.Event; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Map; +import java.util.function.Function; import java.util.regex.Matcher; @Name("Replace") @@ -106,20 +108,9 @@ private void replace(Event event, Object[] needles, Expression haystackExpr) if (replacement == null || haystack == null || haystack.length == 0 || needles == null || needles.length == 0) return; if (replaceString) { - if (replaceFirst) { - for (int x = 0; x < haystack.length; x++) - for (Object n : needles) { - assert n != null; - haystack[x] = StringUtils.replaceFirst((String)haystack[x], (String)n, Matcher.quoteReplacement((String)replacement), caseSensitive); - } - } else { - for (int x = 0; x < haystack.length; x++) - for (Object n : needles) { - assert n != null; - haystack[x] = StringUtils.replace((String) haystack[x], (String) n, (String) replacement, caseSensitive); - } - } - haystackExpr.change(event, haystack, ChangeMode.SET); + Function replaceFunction = getReplaceFunction(needles, (String) replacement); + //noinspection unchecked + ((Expression) haystackExpr).changeInPlace(event, replaceFunction); } else { for (Inventory inv : (Inventory[]) haystack) for (ItemType needle : (ItemType[]) needles) @@ -137,14 +128,36 @@ private void replace(Event event, Object[] needles, Expression haystackExpr) } } } - + + private @NotNull Function getReplaceFunction(Object[] needles, String replacement) { + Function replaceFunction; + if (replaceFirst) { + replaceFunction = haystackString -> { + for (Object needle : needles) { + assert needle != null; + haystackString = StringUtils.replaceFirst(haystackString, (String) needle, Matcher.quoteReplacement(replacement), caseSensitive); + } + return haystackString; + }; + } else { + replaceFunction = haystackString -> { + for (Object needle : needles) { + assert needle != null; + haystackString = StringUtils.replace(haystackString, (String) needle, replacement, caseSensitive); + } + return haystackString; + }; + } + return replaceFunction; + } + @Override public String toString(@Nullable Event event, boolean debug) { if (replaceFirst) - return "replace first " + needles.toString(event, debug) + " in " + haystack.toString(event, debug) + " with " + replacement.toString(event, debug) - + "(case sensitive: " + caseSensitive + ")"; - return "replace " + needles.toString(event, debug) + " in " + haystack.toString(event, debug) + " with " + replacement.toString(event, debug) - + "(case sensitive: " + caseSensitive + ")"; + return "replace first " + needles.toString(event, debug) + " in " + haystack.toString(event, debug) + + " with " + replacement.toString(event, debug) + "(case sensitive: " + caseSensitive + ")"; + return "replace " + needles.toString(event, debug) + " in " + haystack.toString(event, debug) + + " with " + replacement.toString(event, debug) + "(case sensitive: " + caseSensitive + ")"; } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java b/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java index 6e0af7d63cc..62e5696e098 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java @@ -18,18 +18,20 @@ */ package ch.njol.skript.expressions; -import ch.njol.util.VectorMath; -import org.bukkit.event.Event; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Nullable; - import ch.njol.skript.classes.Changer.ChangeMode; 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.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.util.VectorMath; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Function; @Name("Vectors - Length") @Description("Gets or sets the length of a vector.") @@ -61,39 +63,49 @@ public Class[] acceptChange(ChangeMode mode) { } @Override - public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { assert delta != null; - final Vector[] vectors = getExpr().getArray(event); double deltaLength = ((Number) delta[0]).doubleValue(); + + Function changeFunction; switch (mode) { case REMOVE: deltaLength = -deltaLength; //$FALL-THROUGH$ case ADD: - for (Vector vector : vectors) { - if (VectorMath.isZero(vector) || (deltaLength < 0 && vector.lengthSquared() < deltaLength * deltaLength)) { + final double finalDeltaLength = deltaLength; + final double finalDeltaLengthSquared = deltaLength * deltaLength; + changeFunction = vector -> { + if (VectorMath.isZero(vector) || (finalDeltaLength < 0 && vector.lengthSquared() < finalDeltaLengthSquared)) { vector.zero(); } else { - double newLength = deltaLength + vector.length(); + double newLength = finalDeltaLength + vector.length(); if (!vector.isNormalized()) vector.normalize(); vector.multiply(newLength); } - } + return vector; + }; break; case SET: - for (Vector vector : vectors) { - if (deltaLength < 0 || VectorMath.isZero(vector)) { + final double finalDeltaLength1 = deltaLength; + changeFunction = vector -> { + if (finalDeltaLength1 < 0 || VectorMath.isZero(vector)) { vector.zero(); } else { if (!vector.isNormalized()) vector.normalize(); - vector.multiply(deltaLength); + vector.multiply(finalDeltaLength1); } - } + return vector; + }; break; + default: + return; } - getExpr().change(event, vectors, ChangeMode.SET); + + //noinspection unchecked,DataFlowIssue + ((Expression) getExpr()).changeInPlace(event, changeFunction); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java b/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java index 82a0c03d790..cc2b5deb9cb 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorXYZ.java @@ -18,10 +18,6 @@ */ package ch.njol.skript.expressions; -import org.bukkit.event.Event; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Nullable; - import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; @@ -33,6 +29,11 @@ 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.util.Vector; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Function; @Name("Vectors - XYZ Component") @Description("Gets or changes the x, y or z component of a vector.") @@ -84,39 +85,45 @@ public Class[] acceptChange(ChangeMode mode) { @Override public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { assert delta != null; - Vector[] vectors = getExpr().getArray(event); - if (vectors.length == 0) - return; double deltaValue = ((Number) delta[0]).doubleValue(); + Function changeFunction; switch (mode) { case REMOVE: deltaValue = -deltaValue; //$FALL-THROUGH$ case ADD: - for (Vector vector : vectors) { - if (axis == 0) - vector.setX(vector.getX() + deltaValue); - else if (axis == 1) - vector.setY(vector.getY() + deltaValue); - else - vector.setZ(vector.getZ() + deltaValue); - } + final double finalDeltaValue1 = deltaValue; + changeFunction = vector -> { + if (axis == 0) { + vector.setX(vector.getX() + finalDeltaValue1); + } else if (axis == 1) { + vector.setY(vector.getY() + finalDeltaValue1); + } else { + vector.setZ(vector.getZ() + finalDeltaValue1); + } + return vector; + }; break; case SET: - for (Vector vector : vectors) { - if (axis == 0) - vector.setX(deltaValue); - else if (axis == 1) - vector.setY(deltaValue); - else - vector.setZ(deltaValue); - } + final double finalDeltaValue = deltaValue; + changeFunction = vector -> { + if (axis == 0) { + vector.setX(finalDeltaValue); + } else if (axis == 1) { + vector.setY(finalDeltaValue); + } else { + vector.setZ(finalDeltaValue); + } + return vector; + }; break; default: assert false; return; } - getExpr().change(event, vectors, ChangeMode.SET); + + //noinspection unchecked,DataFlowIssue + ((Expression) getExpr()).changeInPlace(event, changeFunction); } @Override diff --git a/src/main/java/ch/njol/skript/lang/Expression.java b/src/main/java/ch/njol/skript/lang/Expression.java index da39fcf669a..1f68484e045 100644 --- a/src/main/java/ch/njol/skript/lang/Expression.java +++ b/src/main/java/ch/njol/skript/lang/Expression.java @@ -35,11 +35,14 @@ import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.converter.Converter; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Spliterators; +import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -345,6 +348,54 @@ default Map[]> getAcceptedChangeModes() { */ void change(Event event, @Nullable Object[] delta, ChangeMode mode); + /** + * Changes the contents of an expression using the given {@link Function}. + * Intended for changes that change properties of the values of the expression, rather than completely + * changing the expression. For example, {@code set vector length of {_v} to 1}, rather than + * {@code set {_v} to vector(0,1,0)}. + *
+ * This is a 1 to 1 transformation and should not add or remove elements. + * For {@link Variable}s, this will retain indices. For non-{@link Variable}s, it will + * evaluate {@link #getArray(Event)}, apply the change function on each, and call + * {@link #change(Event, Object[], ChangeMode)} with the modified values and {@link ChangeMode#SET}. + * + * @param event The event to use for local variables and evaluation + * @param changeFunction A 1-to-1 function that transforms a single input to a single output. + * @param The output type of the change function. Must be a type returned + * by {{@link #acceptChange(ChangeMode)}} for {@link ChangeMode#SET}. + */ + default void changeInPlace(Event event, Function changeFunction) { + changeInPlace(event, changeFunction, false); + } + + /** + * Changes the contents of an expression using the given {@link Function}. + * Intended for changes that change properties of the values of the expression, rather than completely + * changing the expression. For example, {@code set vector length of {_v} to 1}, rather than + * {@code set {_v} to vector(0,1,0)}. + *
+ * This is a 1 to 1 transformation and should not add or remove elements. + * For {@link Variable}s, this will retain indices. For non-{@link Variable}s, it will + * evaluate the expression, apply the change function on each value, and call + * {@link #change(Event, Object[], ChangeMode)} with the modified values and {@link ChangeMode#SET}. + * + * @param event The event to use for local variables and evaluation + * @param changeFunction A 1-to-1 function that transforms a single input to a single output. + * @param getAll Whether to evaluate with {@link #getAll(Event)} or {@link #getArray(Event)}. + * @param The output type of the change function. Must be a type returned + * by {{@link #acceptChange(ChangeMode)}} for {@link ChangeMode#SET}. + */ + default void changeInPlace(Event event, Function changeFunction, boolean getAll) { + T[] values = getAll ? getAll(event) : getArray(event); + if (values.length == 0) + return; + List newValues = new ArrayList<>(); + for (T value : values) { + newValues.add(changeFunction.apply(value)); + } + change(event, newValues.toArray(), ChangeMode.SET); + } + /** * This method is called before this expression is set to another one. * The return value is what will be used for change. You can use modified diff --git a/src/main/java/ch/njol/skript/lang/Variable.java b/src/main/java/ch/njol/skript/lang/Variable.java index d85d6140aeb..1b7a55f42b6 100644 --- a/src/main/java/ch/njol/skript/lang/Variable.java +++ b/src/main/java/ch/njol/skript/lang/Variable.java @@ -18,16 +18,6 @@ */ package ch.njol.skript.lang; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; -import java.util.TreeMap; - import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.SkriptConfig; @@ -35,9 +25,6 @@ import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.Changer.ChangerUtils; import ch.njol.skript.classes.ClassInfo; -import org.skriptlang.skript.lang.arithmetic.Arithmetics; -import org.skriptlang.skript.lang.arithmetic.OperationInfo; -import org.skriptlang.skript.lang.arithmetic.Operator; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.lang.util.SimpleExpression; @@ -59,12 +46,26 @@ import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.arithmetic.Arithmetics; +import org.skriptlang.skript.lang.arithmetic.OperationInfo; +import org.skriptlang.skript.lang.arithmetic.Operator; import org.skriptlang.skript.lang.comparator.Comparators; import org.skriptlang.skript.lang.comparator.Relation; import org.skriptlang.skript.lang.converter.Converters; import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.script.ScriptWarning; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.TreeMap; +import java.util.function.Function; + public class Variable implements Expression { private final static String SINGLE_SEPARATOR_CHAR = ":"; @@ -666,6 +667,25 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) throw } } + @Override + public void changeInPlace(Event event, Function changeFunction) { + if (!list) { + T value = getSingle(event); + if (value == null) + return; + set(event, changeFunction.apply(value)); + return; + } + variablesIterator(event).forEachRemaining(pair -> { + String index = pair.getKey(); + T value = Converters.convert(pair.getValue(), types); + if (value == null) + return; + Object newValue = changeFunction.apply(value); + setIndex(event, index, newValue); + }); + } + @Override @Nullable public T getSingle(Event event) { diff --git a/src/test/skript/tests/regressions/pull-7120-changes overwriting indices.sk b/src/test/skript/tests/regressions/pull-7120-changes overwriting indices.sk new file mode 100644 index 00000000000..47b02990e7a --- /dev/null +++ b/src/test/skript/tests/regressions/pull-7120-changes overwriting indices.sk @@ -0,0 +1,31 @@ +test "EffEnchant overwriting var indices": + set {_test::1} to diamond sword + set {_test::a} to gold sword + set {_test::hello world} to block at spawn of world "world" + enchant {_test::*} with sharpness 1 + assert {_test::*} is diamond sword of sharpness 1, gold sword of sharpness 1, and block at spawn of world "world" with "failed to enchant items" + assert indices of {_test::*} is "1", "a", and "hello world" with "enchanting modified indices" + +test "EffReplace overwriting var indices": + set {_test::1} to "abc" + set {_test::a} to "cde" + set {_test::hello world} to block at spawn of world "world" + replace all "c" in {_test::*} with "z" + assert {_test::*} is "abz", "zde", and block at spawn of world "world" with "failed to replace strings" + assert indices of {_test::*} is "1", "a", and "hello world" with "replacing modified indices" + +test "ExprVectorLength overwriting var indices": + set {_test::1} to vector(0,3,0) + set {_test::a} to vector(0,0,4) + set {_test::hello world} to "abc" + set vector length of {_test::*} to 1 + assert {_test::*} is vector(0,1,0), vector(0,0,1), and "abc" with "failed to change vector length" + assert indices of {_test::*} is "1", "a", and "hello world" with "changing vector length modified indices" + +test "ExprVectorXYZ overwriting var indices": + set {_test::1} to vector(0,3,0) + set {_test::a} to vector(0,0,4) + set {_test::hello world} to "abc" + set x component of {_test::*} to 1 + assert {_test::*} is vector(1,3,0), vector(1,0,4), and "abc" with "failed to change vector x" + assert indices of {_test::*} is "1", "a", and "hello world" with "changing vector x modified indices" From fde44082d0112fa3c3f362f0792a598a08a55db8 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 <82696841+TheAbsolutionism@users.noreply.github.com> Date: Sat, 23 Nov 2024 00:23:20 -0500 Subject: [PATCH 11/19] Pretty Quote Error (#7129) * Starter Commit * Cleanup * Change to Error Error instead of warn * Test * More Tests * Test Update * Remove - --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Moderocky Co-authored-by: Efnilite <35348263+Efnilite@users.noreply.github.com> --- .../ch/njol/skript/lang/SkriptParser.java | 4 +++ .../tests/misc/pull-7129-pretty-quotes.sk | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 src/test/skript/tests/misc/pull-7129-pretty-quotes.sk diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index c56f56e81e4..e713edeb750 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -302,6 +302,10 @@ public boolean hasTag(String tag) { } private static @Nullable Expression parseExpression(Class[] types, String expr) {; + if (expr.startsWith("“") || expr.startsWith("”") || expr.endsWith("”") || expr.endsWith("“")) { + Skript.error("Pretty quotes are not allowed, change to regular quotes (\")"); + return null; + } if (expr.startsWith("\"") && expr.length() != 1 && nextQuote(expr, 1) == expr.length() - 1) { return VariableString.newInstance("" + expr.substring(1, expr.length() - 1)); } else { diff --git a/src/test/skript/tests/misc/pull-7129-pretty-quotes.sk b/src/test/skript/tests/misc/pull-7129-pretty-quotes.sk new file mode 100644 index 00000000000..7bf6da5b3e5 --- /dev/null +++ b/src/test/skript/tests/misc/pull-7129-pretty-quotes.sk @@ -0,0 +1,30 @@ + +command /prettyquotecommandtest “prettyquoteliteral” : + trigger: + set {PrettyQuoteArg} to arg + +test "pretty quote usage": + set {_error} to "Pretty quotes are not allowed, change to regular quotes" + parse: + set {_test} to "“Embedded Pretty Quotes”" + assert last parse logs does not contain {_error} with "Pretty quotes should work inside strings" + + parse: + set {_test} to "" #“Pretty Quote Comment” + assert last parse logs does not contain {_error} with "Pretty quotes should work in comments" + + parse: + set {_test“”} to "" + assert last parse logs does not contain {_error} with "Pretty quotes should work in var names" + + parse: + set {_test} to “Pretty Quotes” + assert last parse logs contain {_error} with "Pretty quote string usage did not produce expected error" + + parse: + set {_test} to "test%“test”%" + assert last parse logs contain {_error} with "Pretty quote %%string%% usage did not produce expected error" + + execute command "prettyquotecommandtest “prettyquoteliteral” “prettyquotearg”" + assert {PrettyQuoteArg} is "“prettyquotearg”" with "Pretty quotes should work in command args" + clear {PrettyQuoteArg} From 59b1ff43cc9e9dd8a985704f87378a87b1de32ab Mon Sep 17 00:00:00 2001 From: Moderocky Date: Sat, 23 Nov 2024 05:30:33 +0000 Subject: [PATCH 12/19] Secspressions! (#7037) * Add expression section basics. * Sections & tests. * Script loader thing. * Remove finaliser. * Add backup method. * Retain logs from parse attempts. * Add failure tests. * Add more failure tests. * Error only if a match was found. * Revert change. * Filter for specific section errors. * Check expected failure errors. * Add some documentation. * Change error message. * Stupid annotation :( * Fix Sovde's mistake. Sovde displayed poor judgement when merging! * Apply suggestions from code review Co-authored-by: Patrick Miller * Remove method. * Test stuff. * Add proper support for loading code normally --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Patrick Miller --- .../java/ch/njol/skript/ScriptLoader.java | 166 +++++++++--------- .../expressions/base/SectionExpression.java | 166 ++++++++++++++++++ .../njol/skript/lang/ExpressionSection.java | 83 +++++++++ .../java/ch/njol/skript/lang/Section.java | 46 ++++- .../java/ch/njol/skript/lang/Statement.java | 43 ++--- .../java/ch/njol/skript/log/LogEntry.java | 5 - .../njol/skript/log/RetainingLogHandler.java | 77 +++++--- .../skript/test/runner/EffRunRunnable.java | 44 +++++ .../skript/test/runner/ExprSecRunnable.java | 79 +++++++++ .../skript/tests/misc/expression sections.sk | 46 +++++ 10 files changed, 619 insertions(+), 136 deletions(-) create mode 100644 src/main/java/ch/njol/skript/expressions/base/SectionExpression.java create mode 100644 src/main/java/ch/njol/skript/lang/ExpressionSection.java create mode 100644 src/main/java/ch/njol/skript/test/runner/EffRunRunnable.java create mode 100644 src/main/java/ch/njol/skript/test/runner/ExprSecRunnable.java create mode 100644 src/test/skript/tests/misc/expression sections.sk diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index 9d246d9565e..04de09b2b86 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.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; import ch.njol.skript.config.Config; @@ -28,11 +10,9 @@ import ch.njol.skript.lang.Statement; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.TriggerSection; +import ch.njol.skript.lang.function.EffFunctionCall; import ch.njol.skript.lang.parser.ParserInstance; -import ch.njol.skript.log.CountingLogHandler; -import ch.njol.skript.log.LogEntry; -import ch.njol.skript.log.RetainingLogHandler; -import ch.njol.skript.log.SkriptLogger; +import ch.njol.skript.log.*; import ch.njol.skript.sections.SecLoop; import ch.njol.skript.structures.StructOptions.OptionsData; import ch.njol.skript.util.ExceptionUtils; @@ -58,17 +38,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; +import java.util.*; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; @@ -85,7 +55,7 @@ public class ScriptLoader { public static final String DISABLED_SCRIPT_PREFIX = "-"; public static final int DISABLED_SCRIPT_PREFIX_LENGTH = DISABLED_SCRIPT_PREFIX.length(); - + /** * A class for keeping track of the general content of a script: *
    @@ -99,12 +69,12 @@ public static class ScriptInfo { public ScriptInfo() { } - + public ScriptInfo(int numFiles, int numStructures) { files = numFiles; structures = numStructures; } - + /** * Copy constructor. * @param other ScriptInfo to copy from @@ -113,30 +83,30 @@ public ScriptInfo(ScriptInfo other) { files = other.files; structures = other.structures; } - + public void add(ScriptInfo other) { files += other.files; structures += other.structures; } - + public void subtract(ScriptInfo other) { files -= other.files; structures -= other.structures; } - + @Override public String toString() { return "ScriptInfo{files=" + files + ",structures=" + structures + "}"; } } - + /** * @see ParserInstance#get() */ private static ParserInstance getParser() { return ParserInstance.get(); } - + /* * Enabled/disabled script tracking */ @@ -175,7 +145,7 @@ private boolean isSubDir(File directory, File subDir) { return false; } })); - + /** * Filter for loaded scripts and folders. */ @@ -235,7 +205,7 @@ public static Set