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 1/8] 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 2/8] 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 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 3/8] 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 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 4/8] 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 5/8] 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 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 6/8] 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 7/8] 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