From 6f5d41e0a8020b5e7a44d73fbbe8bd019e00d4a4 Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Thu, 17 Jul 2025 20:52:08 +0200 Subject: [PATCH 01/20] Verbose asserts for contains (#8000) --- .../njol/skript/conditions/CondContains.java | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/conditions/CondContains.java b/src/main/java/ch/njol/skript/conditions/CondContains.java index 0e713bfeb44..1aa5d9ac245 100644 --- a/src/main/java/ch/njol/skript/conditions/CondContains.java +++ b/src/main/java/ch/njol/skript/conditions/CondContains.java @@ -3,6 +3,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.SkriptConfig; import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.lang.VerboseAssert; import ch.njol.skript.lang.util.common.AnyContains; import ch.njol.skript.util.LiteralUtils; import org.skriptlang.skript.lang.comparator.Relation; @@ -24,7 +25,9 @@ import org.jetbrains.annotations.Nullable; import java.util.Arrays; +import java.util.List; import java.util.Objects; +import java.util.StringJoiner; @Name("Contains") @Description("Checks whether an inventory contains an item, a text contains another piece of text, " @@ -34,7 +37,7 @@ "player has 4 flint and 2 iron ingots", "{list::*} contains 5"}) @Since("1.0") -public class CondContains extends Condition { +public class CondContains extends Condition implements VerboseAssert { static { Skript.registerCondition(CondContains.class, @@ -157,6 +160,29 @@ public boolean check(Event event) { }; } + @Override + public String getExpectedMessage(Event event) { + StringJoiner joiner = new StringJoiner(" "); + joiner.add("to"); + if (isNegated()) { + joiner.add("not"); + } + joiner.add("find %s".formatted(VerboseAssert.getExpressionValue(items, event))); + return joiner.toString(); + } + + @Override + public String getReceivedMessage(Event event) { + StringJoiner joiner = new StringJoiner(" "); + if (!isNegated()) { + joiner.add("no"); + } else { + joiner.add("a"); + } + joiner.add("match in %s".formatted(VerboseAssert.getExpressionValue(containers, event))); + return joiner.toString(); + } + @Override public String toString(@Nullable Event e, boolean debug) { return containers.toString(e, debug) + (isNegated() ? " doesn't contain " : " contains ") + items.toString(e, debug); From b6974f16a1961e001c2391319777afb93ead09f1 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 17 Jul 2025 13:08:56 -0600 Subject: [PATCH 02/20] Add term 'launch' to projectile launch event (#8009) --- src/main/java/ch/njol/skript/events/SimpleEvents.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index a67711130da..993b9316d4e 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -248,7 +248,7 @@ public class SimpleEvents { .examples("on projectile collide:", "\tteleport shooter of event-projectile to event-entity") .since("2.5"); - Skript.registerEvent("Shoot", SimpleEvent.class, ProjectileLaunchEvent.class, "[projectile] shoot") + Skript.registerEvent("Shoot", SimpleEvent.class, ProjectileLaunchEvent.class, "[projectile] (shoot|launch)") .description("Called whenever a projectile is shot. Use the shooter expression to get who shot the projectile.") .examples("on shoot:", "\tif projectile is an arrow:", From b4a514a8043b8938c6ea039970bb88925a76dec6 Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Thu, 24 Jul 2025 17:46:03 +0200 Subject: [PATCH 03/20] Fix function overloading for convertable arguments (#8046) --- .../lang/function/FunctionReference.java | 6 +- .../lang/function/FunctionRegistry.java | 80 ++++++++++++++----- .../njol/skript/lang/function/Functions.java | 10 ++- .../lang/function/FunctionRegistryTest.java | 46 ++++++++++- .../skript/tests/misc/function overloading.sk | 22 +++++ 5 files changed, 136 insertions(+), 28 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java index 7cc7fdfc745..e31ffdfb707 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java @@ -4,10 +4,7 @@ import ch.njol.skript.SkriptAPIException; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.config.Node; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.KeyProviderExpression; -import ch.njol.skript.lang.KeyedValue; -import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.*; import ch.njol.skript.lang.function.FunctionRegistry.Retrieval; import ch.njol.skript.lang.function.FunctionRegistry.RetrievalResult; import ch.njol.skript.log.RetainingLogHandler; @@ -326,7 +323,6 @@ private Function getRegisteredFunction() { } Retrieval> attempt = FunctionRegistry.getRegistry().getFunction(script, functionName, parameterTypes); - if (attempt.result() == RetrievalResult.EXACT) { return attempt.retrieved(); } diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java b/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java index 0274257cea5..09ba1f4978b 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java @@ -280,14 +280,13 @@ record Retrieval( @NotNull String name, @NotNull Class... args ) { - if (namespace == null) { - return getFunction(GLOBAL_NAMESPACE, FunctionIdentifier.of(name, false, args)); + Retrieval> attempt = null; + if (namespace != null) { + attempt = getFunction(new NamespaceIdentifier(namespace), + FunctionIdentifier.of(name, true, args)); } - - Retrieval> attempt = getFunction(new NamespaceIdentifier(namespace), - FunctionIdentifier.of(name, true, args)); - if (attempt.result() == RetrievalResult.NOT_REGISTERED) { - return getFunction(GLOBAL_NAMESPACE, FunctionIdentifier.of(name, false, args)); + if (attempt == null || attempt.result() == RetrievalResult.NOT_REGISTERED) { + attempt = getFunction(GLOBAL_NAMESPACE, FunctionIdentifier.of(name, false, args)); } return attempt; } @@ -312,7 +311,7 @@ record Retrieval( return new Retrieval<>(RetrievalResult.NOT_REGISTERED, null, null); } - Set candidates = candidates(provided, existing); + Set candidates = candidates(provided, existing, false); if (candidates.isEmpty()) { Skript.debug("Failed to find a function for '%s'", provided.name); return new Retrieval<>(RetrievalResult.NOT_REGISTERED, null, null); @@ -353,14 +352,45 @@ public Retrieval> getSignature( @NotNull String name, @NotNull Class... args ) { - if (namespace == null) { - return getSignature(GLOBAL_NAMESPACE, FunctionIdentifier.of(name, false, args)); + Retrieval> attempt = null; + if (namespace != null) { + attempt = getSignature(new NamespaceIdentifier(namespace), + FunctionIdentifier.of(name, true, args), false); + } + if (attempt == null || attempt.result() == RetrievalResult.NOT_REGISTERED) { + attempt = getSignature(GLOBAL_NAMESPACE, FunctionIdentifier.of(name, false, args), false); + } + return attempt; + } + + /** + * Gets the signature for a function with the given name and arguments. If no local function is found, + * checks for global functions. If {@code namespace} is null, only global signatures will be checked. + *

+ * This function checks performs no argument conversions, and is only used for determining whether a + * signature already exists with the exact specified arguments. In almost all cases, {@link #getSignature(String, String, Class[])} + * should be used. + *

+ * + * @param namespace The namespace to get the function from. + * Usually represents the path of the script this function is registered in. + * @param name The name of the function. + * @param args The types of the arguments of the function. + * @return The signature for the function with the given name and argument types, or null if no such function exists. + */ + Retrieval> getExactSignature( + @Nullable String namespace, + @NotNull String name, + @NotNull Class... args + ) { + Retrieval> attempt = null; + if (namespace != null) { + attempt = getSignature(new NamespaceIdentifier(namespace), + FunctionIdentifier.of(name, true, args), true); } - Retrieval> attempt = getSignature(new NamespaceIdentifier(namespace), - FunctionIdentifier.of(name, true, args)); - if (attempt.result() == RetrievalResult.NOT_REGISTERED) { - return getSignature(GLOBAL_NAMESPACE, FunctionIdentifier.of(name, false, args)); + if (attempt == null || attempt.result() == RetrievalResult.NOT_REGISTERED) { + attempt = getSignature(GLOBAL_NAMESPACE, FunctionIdentifier.of(name, false, args), true); } return attempt; } @@ -370,10 +400,12 @@ public Retrieval> getSignature( * * @param namespace The namespace to get the function from. * @param provided The provided identifier of the function. + * @param exact When false, will convert arguments to different types to attempt to find a match. + * When true, will not convert arguments. * @return The signature for the function with the given name and argument types, or null if no such signature exists * in the specified namespace. */ - private Retrieval> getSignature(@NotNull NamespaceIdentifier namespace, @NotNull FunctionIdentifier provided) { + private Retrieval> getSignature(@NotNull NamespaceIdentifier namespace, @NotNull FunctionIdentifier provided, boolean exact) { Preconditions.checkNotNull(namespace, "namespace cannot be null"); Preconditions.checkNotNull(provided, "provided cannot be null"); @@ -383,7 +415,7 @@ private Retrieval> getSignature(@NotNull NamespaceIdentifier namesp return new Retrieval<>(RetrievalResult.NOT_REGISTERED, null, null); } - Set candidates = candidates(provided, ns.identifiers.get(provided.name)); + Set candidates = candidates(provided, ns.identifiers.get(provided.name), exact); if (candidates.isEmpty()) { Skript.debug("Failed to find a signature for '%s'", provided.name); return new Retrieval<>(RetrievalResult.NOT_REGISTERED, null, null); @@ -415,11 +447,14 @@ private Retrieval> getSignature(@NotNull NamespaceIdentifier namesp * * @param provided The provided function. * @param existing The existing functions with the same name. + * @param exact When false, will convert arguments to different types to attempt to find a match. + * When true, will not convert arguments. * @return An unmodifiable list of candidates for the provided function. */ private static @Unmodifiable @NotNull Set candidates( @NotNull FunctionIdentifier provided, - Set existing + Set existing, + boolean exact ) { Set candidates = new HashSet<>(); @@ -459,8 +494,15 @@ private Retrieval> getSignature(@NotNull NamespaceIdentifier namesp candidateType = candidate.args[i]; } - if (!Converters.converterExists(provided.args[i], candidateType)) { - continue candidates; + Class providedArg = provided.args[i]; + if (exact) { + if (providedArg != candidateType) { + continue candidates; + } + } else { + if (!Converters.converterExists(providedArg, candidateType)) { + continue candidates; + } } } diff --git a/src/main/java/ch/njol/skript/lang/function/Functions.java b/src/main/java/ch/njol/skript/lang/function/Functions.java index 909ae8127da..637f61b005d 100644 --- a/src/main/java/ch/njol/skript/lang/function/Functions.java +++ b/src/main/java/ch/njol/skript/lang/function/Functions.java @@ -161,14 +161,18 @@ public static JavaFunction registerFunction(JavaFunction function) { Parameter[] parameters = signature.parameters; if (parameters.length == 1 && !parameters[0].isSingleValue()) { - existing = FunctionRegistry.getRegistry().getSignature(signature.script, signature.getName(), parameters[0].type.getC().arrayType()); + existing = FunctionRegistry.getRegistry().getExactSignature(signature.script, signature.getName(), parameters[0].type.getC().arrayType()); } else { Class[] types = new Class[parameters.length]; for (int i = 0; i < parameters.length; i++) { - types[i] = parameters[i].type.getC(); + if (parameters[i].isSingleValue()) { + types[i] = parameters[i].type.getC(); + } else { + types[i] = parameters[i].type.getC().arrayType(); + } } - existing = FunctionRegistry.getRegistry().getSignature(signature.script, signature.getName(), types); + existing = FunctionRegistry.getRegistry().getExactSignature(signature.script, signature.getName(), types); } // if this function has already been registered, only allow it if one function is local and one is global. diff --git a/src/test/java/ch/njol/skript/lang/function/FunctionRegistryTest.java b/src/test/java/ch/njol/skript/lang/function/FunctionRegistryTest.java index e7b39f27f1a..d4896dbf59e 100644 --- a/src/test/java/ch/njol/skript/lang/function/FunctionRegistryTest.java +++ b/src/test/java/ch/njol/skript/lang/function/FunctionRegistryTest.java @@ -1,10 +1,12 @@ package ch.njol.skript.lang.function; import ch.njol.skript.SkriptAPIException; -import ch.njol.skript.lang.function.FunctionRegistry.RetrievalResult; import ch.njol.skript.lang.function.FunctionRegistry.FunctionIdentifier; +import ch.njol.skript.lang.function.FunctionRegistry.RetrievalResult; import ch.njol.skript.lang.util.SimpleLiteral; import ch.njol.skript.registrations.DefaultClasses; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; import org.junit.Test; @@ -419,4 +421,46 @@ public void testRemoveGlobalScriptFunctions8015() { assertEquals(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME).result()); } + private static final Function TEST_FUNCTION_P = new SimpleJavaFunction<>(FUNCTION_NAME, + new Parameter[]{ + new Parameter<>("a", DefaultClasses.PLAYER, true, null) + }, DefaultClasses.BOOLEAN, true) { + @Override + public Boolean @Nullable [] executeSimple(Object[][] params) { + return new Boolean[]{true}; + } + }; + + private static final Function TEST_FUNCTION_OP = new SimpleJavaFunction<>(FUNCTION_NAME, + new Parameter[]{ + new Parameter<>("a", DefaultClasses.OFFLINE_PLAYER, true, null) + }, DefaultClasses.BOOLEAN, true) { + @Override + public Boolean @Nullable [] executeSimple(Object[][] params) { + return new Boolean[]{true}; + } + }; + + @Test + public void testGetExactSignature() { + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, Player.class).result()); + assertNull(registry.getSignature(null, FUNCTION_NAME, Player.class).retrieved()); + assertNull(registry.getFunction(null, FUNCTION_NAME, Player.class).retrieved()); + assertSame(RetrievalResult.NOT_REGISTERED, registry.getSignature(null, FUNCTION_NAME, OfflinePlayer.class).result()); + assertNull(registry.getSignature(null, FUNCTION_NAME, OfflinePlayer.class).retrieved()); + assertNull(registry.getFunction(null, FUNCTION_NAME, OfflinePlayer.class).retrieved()); + + registry.register(null, TEST_FUNCTION_P); + + assertSame(RetrievalResult.EXACT, registry.getExactSignature(null, FUNCTION_NAME, Player.class).result()); + assertEquals(TEST_FUNCTION_P.getSignature(), registry.getExactSignature(null, FUNCTION_NAME, Player.class).retrieved()); + assertNull(registry.getExactSignature(null, FUNCTION_NAME, OfflinePlayer.class).retrieved()); + + assertEquals(TEST_FUNCTION_P.getSignature(), registry.getSignature(null, FUNCTION_NAME, Player.class).retrieved()); + assertEquals(TEST_FUNCTION_P.getSignature(), registry.getSignature(null, FUNCTION_NAME, OfflinePlayer.class).retrieved()); + + registry.remove(TEST_FUNCTION_P.getSignature()); + registry.remove(TEST_FUNCTION_OP.getSignature()); + } + } diff --git a/src/test/skript/tests/misc/function overloading.sk b/src/test/skript/tests/misc/function overloading.sk index d7b3836508d..2e47b7d8621 100644 --- a/src/test/skript/tests/misc/function overloading.sk +++ b/src/test/skript/tests/misc/function overloading.sk @@ -63,3 +63,25 @@ parse: test "function overloading with different return types": assert size of {FunctionOverloading3::parse::*} = 1 assert {FunctionOverloading3::parse::1} contains "Function 'overloaded3' with the same argument types already exists in script" + +function overloading_supertype1(p: offlineplayer): + stop + +parse: + results: {FunctionOverloadingSupertype1::parse::*} + code: + function overloading_supertype1(n: player): + stop + +function overloading_supertype2(p: player): + stop + +parse: + results: {FunctionOverloadingSupertype2::parse::*} + code: + function overloading_supertype2(n: offlineplayer): + stop + +test "function overloading with supertype": + assert {FunctionOverloadingSupertype1::parse::*} is not set + assert {FunctionOverloadingSupertype2::parse::*} is not set From 65866967f90c1af44f31e1898bf3635b5224e18c Mon Sep 17 00:00:00 2001 From: SirSmurfy2 <82696841+Absolutionism@users.noreply.github.com> Date: Fri, 25 Jul 2025 10:43:12 -0400 Subject: [PATCH 04/20] Paper 1.21.8 Update (#8054) --- build.gradle | 4 ++-- gradle.properties | 2 +- .../java/ch/njol/skript/util/BlockStateBlock.java | 15 +++++++++++++++ .../ch/njol/skript/util/DelayedChangeBlock.java | 15 +++++++++++++++ .../{paper-1.21.7.json => paper-1.21.8.json} | 4 ++-- 5 files changed, 35 insertions(+), 5 deletions(-) rename src/test/skript/environments/java21/{paper-1.21.7.json => paper-1.21.8.json} (85%) diff --git a/build.gradle b/build.gradle index 465c05465c8..73d3fbdedce 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ dependencies { shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.1.0' shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.4.0' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.21.7-R0.1-SNAPSHOT' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.21.8-R0.1-SNAPSHOT' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' // bundled with Minecraft 1.19.4+ for display entity transforms @@ -246,7 +246,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi def java21 = 21 def java17 = 17 -def latestEnv = 'java21/paper-1.21.7.json' +def latestEnv = 'java21/paper-1.21.8.json' def latestJava = java21 def oldestJava = java17 diff --git a/gradle.properties b/gradle.properties index dcba4acbd6a..669a8aae315 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,5 +7,5 @@ groupid=ch.njol name=skript version=2.12.0 jarName=Skript.jar -testEnv=java21/paper-1.21.7 +testEnv=java21/paper-1.21.8 testEnvJavaVersion=21 diff --git a/src/main/java/ch/njol/skript/util/BlockStateBlock.java b/src/main/java/ch/njol/skript/util/BlockStateBlock.java index d51eccacef4..23973db4781 100644 --- a/src/main/java/ch/njol/skript/util/BlockStateBlock.java +++ b/src/main/java/ch/njol/skript/util/BlockStateBlock.java @@ -338,6 +338,21 @@ public void run() { } } + @Override + public boolean breakNaturally(@NotNull ItemStack tool, boolean triggerEffect, boolean dropExperience, boolean forceEffect) { + if (delayChanges) { + Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() { + @Override + public void run() { + state.getBlock().breakNaturally(tool, triggerEffect, dropExperience, forceEffect); + } + }); + return true; + } else { + return false; + } + } + @Override public void tick() { state.getBlock().tick(); diff --git a/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java b/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java index 687639d94a0..f4916284a85 100644 --- a/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java +++ b/src/main/java/ch/njol/skript/util/DelayedChangeBlock.java @@ -339,6 +339,21 @@ public void run() { } } + @Override + public boolean breakNaturally(@NotNull ItemStack tool, boolean triggerEffect, boolean dropExperience, boolean forceEffect) { + if (newState != null) { + return false; + } else { + Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() { + @Override + public void run() { + block.breakNaturally(tool, triggerEffect, dropExperience, forceEffect); + } + }); + return true; + } + } + @Override public void tick() { block.tick(); diff --git a/src/test/skript/environments/java21/paper-1.21.7.json b/src/test/skript/environments/java21/paper-1.21.8.json similarity index 85% rename from src/test/skript/environments/java21/paper-1.21.7.json rename to src/test/skript/environments/java21/paper-1.21.8.json index 30ad4211522..935cdc8fba7 100644 --- a/src/test/skript/environments/java21/paper-1.21.7.json +++ b/src/test/skript/environments/java21/paper-1.21.8.json @@ -1,11 +1,11 @@ { - "name": "paper-1.21.7", + "name": "paper-1.21.8", "resources": [ {"source": "server.properties.generic", "target": "server.properties"} ], "paperDownloads": [ { - "version": "1.21.7", + "version": "1.21.8", "target": "paperclip.jar" } ], From 0115e305d41328d2e6adb8b093dfed1a789d27c9 Mon Sep 17 00:00:00 2001 From: Pesek <42549665+Pesekjak@users.noreply.github.com> Date: Fri, 25 Jul 2025 16:52:26 +0200 Subject: [PATCH 05/20] Checkstyle tab width fix (#8066) --- checkstyle.xml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/checkstyle.xml b/checkstyle.xml index 42794bbeccb..a4006e8fedd 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -9,6 +9,8 @@ + + @@ -25,7 +27,7 @@ - + @@ -35,11 +37,11 @@ - - - - - + + + + + From 74ad10ce0adf35f5c4635885d4b38a084b1faf35 Mon Sep 17 00:00:00 2001 From: Patrick Miller Date: Fri, 25 Jul 2025 11:12:26 -0400 Subject: [PATCH 06/20] Fix potential resolution failure with single list parameter function calls (#8071) --- .../ch/njol/skript/lang/function/FunctionRegistry.java | 2 +- ...-single list parameter functions can fail to resolve.sk | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 src/test/skript/tests/regressions/8070-single list parameter functions can fail to resolve.sk diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java b/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java index 09ba1f4978b..c7619fed68f 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java @@ -470,7 +470,7 @@ private Retrieval> getSignature(@NotNull NamespaceIdentifier namesp // make sure all types in the passed array are valid for the array parameter Class arrayType = candidate.args[0].componentType(); for (Class arrayArg : provided.args) { - if (!Converters.converterExists(arrayType, arrayArg)) { + if (!Converters.converterExists(arrayArg, arrayType)) { continue candidates; } } diff --git a/src/test/skript/tests/regressions/8070-single list parameter functions can fail to resolve.sk b/src/test/skript/tests/regressions/8070-single list parameter functions can fail to resolve.sk new file mode 100644 index 00000000000..b8aa7389ea3 --- /dev/null +++ b/src/test/skript/tests/regressions/8070-single list parameter functions can fail to resolve.sk @@ -0,0 +1,7 @@ +local function test(p: potion effect types): + stop + +test "single list parameter functions": + parse: + test(night vision and glowing) + assert last parse logs is not set with "function call failed to parse" From 2d8f8eb588487b26411ba46045bb278bc27d3c4f Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 25 Jul 2025 10:04:49 -0600 Subject: [PATCH 07/20] Make has potion effect types optional (#8010) --- .../njol/skript/conditions/CondHasPotion.java | 51 +++++++++++-------- .../syntaxes/conditions/CondHasPotion.sk | 8 +++ 2 files changed, 38 insertions(+), 21 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/conditions/CondHasPotion.sk diff --git a/src/main/java/ch/njol/skript/conditions/CondHasPotion.java b/src/main/java/ch/njol/skript/conditions/CondHasPotion.java index b71b6298d27..f8e33aefe26 100644 --- a/src/main/java/ch/njol/skript/conditions/CondHasPotion.java +++ b/src/main/java/ch/njol/skript/conditions/CondHasPotion.java @@ -4,39 +4,46 @@ import ch.njol.skript.conditions.base.PropertyCondition; import ch.njol.skript.conditions.base.PropertyCondition.PropertyType; import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Example; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.util.Kleenean; + import org.bukkit.entity.LivingEntity; import org.bukkit.event.Event; import org.bukkit.potion.PotionEffectType; import org.jetbrains.annotations.Nullable; @Name("Has Potion") -@Description("Checks whether the given living entities have specific potion effects.") -@Examples({"if player has potion speed:", - "\tsend \"You are sonic!\"", - "", - "if all players have potion effects speed and haste:", - "\tbroadcast \"You are ready to MINE!\""}) +@Description("Checks whether the given living entities have potion effects.") +@Example(""" + if player has potion speed: + send "You are sonic!" + if all players have potion effects speed and haste: + broadcast "You are ready to MINE!" + """) @Since("2.6.1") public class CondHasPotion extends Condition { static { - Skript.registerCondition(CondHasPotion.class, - "%livingentities% (has|have) potion[s] [effect[s]] %potioneffecttypes%", - "%livingentities% (doesn't|does not|do not|don't) have potion[s] [effect[s]] %potioneffecttypes%"); + Skript.registerCondition( + CondHasPotion.class, + PropertyCondition.getPatterns( + PropertyType.HAVE, + "([any] potion effect[s]|potion[s] [effect[s]] %-potioneffecttypes%)", + "livingentities" + ) + ); } - - private Expression livingEntities; + private Expression potionEffects; + private Expression livingEntities; @Override - @SuppressWarnings({"unchecked", "null"}) + @SuppressWarnings("unchecked") public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { livingEntities = (Expression) exprs[0]; potionEffects = (Expression) exprs[1]; @@ -45,17 +52,19 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye } @Override - public boolean check(Event e) { - return livingEntities.check(e, - livingEntity -> potionEffects.check(e, - livingEntity::hasPotionEffect - ), isNegated()); + public boolean check(Event event) { + if (potionEffects == null) { + return livingEntities.check(event, entity -> !entity.getActivePotionEffects().isEmpty(), isNegated()); + } + return livingEntities.check(event, + entity -> potionEffects.check(event, entity::hasPotionEffect), + isNegated()); } @Override - public String toString(@Nullable Event e, boolean debug) { - return PropertyCondition.toString(this, PropertyType.HAVE, e, debug, livingEntities, - "potion " + potionEffects.toString(e, debug)); + public String toString(@Nullable Event event, boolean debug) { + String effects = (potionEffects == null) ? "any potion effect" : "potion " + potionEffects.toString(event, debug); + return PropertyCondition.toString(this, PropertyType.HAVE, event, debug, livingEntities, effects); } } diff --git a/src/test/skript/tests/syntaxes/conditions/CondHasPotion.sk b/src/test/skript/tests/syntaxes/conditions/CondHasPotion.sk new file mode 100644 index 00000000000..eee7b58b5fc --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondHasPotion.sk @@ -0,0 +1,8 @@ +test "has potion": + spawn a pig at event-location + apply ambient strength of tier 5 to last spawned pig for 10 seconds + + assert last spawned pig has any potion effect with "pig has no potion effect" + assert last spawned pig has potion effect strength with "pig has strength potion effect" + + clear last spawned pig From ce32116228a2fa8d3d9927874ab6dba02f2730a8 Mon Sep 17 00:00:00 2001 From: Patrick Miller Date: Fri, 25 Jul 2025 12:37:58 -0400 Subject: [PATCH 08/20] Fix expression section claim releasing (#8082) --- src/main/java/ch/njol/skript/lang/Statement.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/lang/Statement.java b/src/main/java/ch/njol/skript/lang/Statement.java index b8154d85789..1435e331dc8 100644 --- a/src/main/java/ch/njol/skript/lang/Statement.java +++ b/src/main/java/ch/njol/skript/lang/Statement.java @@ -51,9 +51,23 @@ public abstract class Statement extends TriggerItem implements SyntaxElement { var iterator = Skript.instance().syntaxRegistry().syntaxes(org.skriptlang.skript.registration.SyntaxRegistry.STATEMENT).iterator(); Section.SectionContext sectionContext = ParserInstance.get().getData(Section.SectionContext.class); if (node != null) { + var wrappedIterator = new Iterator<>() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public org.skriptlang.skript.registration.SyntaxInfo next() { + // it is possible that the section would have been claimed during the attempt to parse the previous info + // as a result, we need to "unclaim" it + sectionContext.owner = null; + return iterator.next(); + } + }; statement = sectionContext.modify(node, items, () -> { //noinspection unchecked,rawtypes - Statement parsed = (Statement) SkriptParser.parse(input, (Iterator) iterator, defaultError); + Statement parsed = (Statement) SkriptParser.parse(input, (Iterator) wrappedIterator, defaultError); if (parsed != null && !sectionContext.claimed()) { Skript.error("The line '" + input + "' is a valid statement but cannot function as a section (:) because there is no syntax in the line to manage it."); return null; From dbbe9161df871421d383fee74dfe95be31b6c703 Mon Sep 17 00:00:00 2001 From: Patrick Miller Date: Fri, 25 Jul 2025 14:15:04 -0400 Subject: [PATCH 09/20] Improve ExprXOf Literal Handling (#8081) --- .../ch/njol/skript/expressions/ExprXOf.java | 85 +++++++++++++------ .../simplification/SimplifiedLiteral.java | 29 +++---- .../njol/skript/lang/util/SimpleLiteral.java | 14 +-- .../tests/syntaxes/expressions/ExprXOf.sk | 5 ++ 4 files changed, 82 insertions(+), 51 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprXOf.java b/src/main/java/ch/njol/skript/expressions/ExprXOf.java index 29aea04ada8..d8df50519c9 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprXOf.java +++ b/src/main/java/ch/njol/skript/expressions/ExprXOf.java @@ -4,6 +4,7 @@ import ch.njol.skript.aliases.ItemType; 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.entity.EntityType; @@ -12,77 +13,105 @@ import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.simplification.SimplifiedLiteral; import ch.njol.util.Kleenean; -import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; -@Name("X of Item") -@Description("An expression to be able to use a certain amount of items where the amount can be any expression. Please note that this expression is not stable and might be replaced in the future.") +@Name("X of Item/Entity Type") +@Description("An expression for using an item or entity type with a different amount.") @Examples("give level of player of iron pickaxes to the player") @Since("1.2") +@Keywords("amount") public class ExprXOf extends PropertyExpression { static { - Skript.registerExpression(ExprXOf.class, Object.class, ExpressionType.PATTERN_MATCHES_EVERYTHING, "%number% of %itemstacks/itemtypes/entitytype%"); + Skript.registerExpression(ExprXOf.class, Object.class, ExpressionType.PATTERN_MATCHES_EVERYTHING, + "%number% of %itemstacks/itemtypes/entitytype%"); } - @SuppressWarnings("NotNullFieldNotInitialized") + private Class[] possibleReturnTypes; private Expression amount; @Override - @SuppressWarnings("unchecked") public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - setExpr(exprs[1]); + //noinspection unchecked amount = (Expression) exprs[0]; + Expression type = exprs[1]; + setExpr(type); + // "x of y" is also an ItemType syntax - return !(amount instanceof Literal) || !(getExpr() instanceof Literal); + if (amount instanceof Literal && amount.getSource() instanceof Literal && + type instanceof Literal && type.getSource() instanceof Literal) { + return false; + } + + // build possible return types + List> possibleReturnTypes = new ArrayList<>(); + if (type.canReturn(ItemStack.class)) { + possibleReturnTypes.add(ItemStack.class); + } + if (type.canReturn(ItemType.class)) { + possibleReturnTypes.add(ItemType.class); + } + if (type.canReturn(EntityType.class)) { + possibleReturnTypes.add(EntityType.class); + } + this.possibleReturnTypes = possibleReturnTypes.toArray(new Class[0]); + + return true; } @Override - protected Object[] get(Event e, Object[] source) { - Number a = amount.getSingle(e); - if (a == null) + protected Object[] get(Event event, Object[] source) { + Number amount = this.amount.getSingle(event); + if (amount == null) return (Object[]) Array.newInstance(getReturnType(), 0); - return get(source, o -> { - if (o instanceof ItemStack) { - ItemStack is = ((ItemStack) o).clone(); - is.setAmount(a.intValue()); - return is; - } else if (o instanceof ItemType) { - ItemType type = ((ItemType) o).clone(); - type.setAmount(a.intValue()); + return get(source, object -> { + if (object instanceof ItemStack itemStack) { + itemStack = itemStack.clone(); + itemStack.setAmount(amount.intValue()); + return itemStack; + } else if (object instanceof ItemType itemType) { + ItemType type = itemType.clone(); + type.setAmount(amount.intValue()); return type; } else { - EntityType t = ((EntityType) o).clone(); - t.amount = a.intValue(); - return t; + EntityType entityType = ((EntityType) object).clone(); + entityType.amount = amount.intValue(); + return entityType; } }); } @Override public Class getReturnType() { - return getExpr().getReturnType(); + return possibleReturnTypes.length == 1 ? possibleReturnTypes[0] : Object.class; } @Override public Class[] possibleReturnTypes() { - return getExpr().possibleReturnTypes(); + return Arrays.copyOf(possibleReturnTypes, possibleReturnTypes.length); } @Override - public boolean canReturn(Class returnType) { - return getExpr().canReturn(returnType); + public String toString(@Nullable Event event, boolean debug) { + return amount.toString(event, debug) + " of " + getExpr().toString(event, debug); } @Override - public String toString(@Nullable Event e, boolean debug) { - return amount.toString(e, debug) + " of " + getExpr().toString(e, debug); + public Expression simplify() { + if (amount instanceof Literal && getExpr() instanceof Literal) { + return SimplifiedLiteral.fromExpression(this); + } + return super.simplify(); } } diff --git a/src/main/java/ch/njol/skript/lang/simplification/SimplifiedLiteral.java b/src/main/java/ch/njol/skript/lang/simplification/SimplifiedLiteral.java index 906c02447c4..c3de86c901a 100644 --- a/src/main/java/ch/njol/skript/lang/simplification/SimplifiedLiteral.java +++ b/src/main/java/ch/njol/skript/lang/simplification/SimplifiedLiteral.java @@ -27,11 +27,10 @@ public class SimplifiedLiteral extends SimpleLiteral { * @return a new simplified literal */ public static SimplifiedLiteral fromExpression(Expression original) { - Event event = ContextlessEvent.get(); - if (original instanceof SimplifiedLiteral literal) return literal; + Event event = ContextlessEvent.get(); T[] values = original.getAll(event); //noinspection unchecked @@ -42,8 +41,6 @@ public static SimplifiedLiteral fromExpression(Expression original) { original); } - Expression sourceExpr; - /** * Creates a new simplified literal. * @param data the data of the literal @@ -52,50 +49,50 @@ public static SimplifiedLiteral fromExpression(Expression original) { * @param source the source expression this literal was created from. Used for toString values. */ public SimplifiedLiteral(T[] data, Class type, boolean and, Expression source) { - super(data, type, and); - sourceExpr = source; + super(data, type, and, source); } @Override public Class @Nullable [] acceptChange(Changer.ChangeMode mode) { - return sourceExpr.acceptChange(mode); + return source.acceptChange(mode); } @Override public Object @Nullable [] beforeChange(Expression changed, Object @Nullable [] delta) { - return sourceExpr.beforeChange(changed, delta); + return source.beforeChange(changed, delta); } @Override public void change(Event event, Object @Nullable [] delta, Changer.ChangeMode mode) throws UnsupportedOperationException { - sourceExpr.change(event, delta, mode); + source.change(event, delta, mode); } @Override public boolean isLoopOf(String input) { - return sourceExpr.isLoopOf(input); + return source.isLoopOf(input); } @Override public void changeInPlace(Event event, Function changeFunction) { - sourceExpr.changeInPlace(event, changeFunction); + getSource().changeInPlace(event, changeFunction); } @Override public void changeInPlace(Event event, Function changeFunction, boolean getAll) { - sourceExpr.changeInPlace(event, changeFunction, getAll); + getSource().changeInPlace(event, changeFunction, getAll); } @Override - public Expression getSource() { - return sourceExpr; + public Expression getSource() { + //noinspection unchecked + return (Expression) source; } @Override public String toString(@Nullable Event event, boolean debug) { if (debug) - return "[" + sourceExpr.toString(event, true) + " (SIMPLIFIED)]"; - return sourceExpr.toString(event, false); + return "[" + source.toString(event, true) + " (SIMPLIFIED)]"; + return source.toString(event, false); } } diff --git a/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java b/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java index 4591da55fd0..0271947ba02 100644 --- a/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java +++ b/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java @@ -34,7 +34,7 @@ public class SimpleLiteral implements Literal, DefaultExpression { private final boolean isDefault; private final boolean and; - private @Nullable Expression source = null; + protected final Expression source; /** * The data of the literal. May not be null or contain null, but may be empty. @@ -42,12 +42,17 @@ public class SimpleLiteral implements Literal, DefaultExpression { protected transient T[] data; public SimpleLiteral(T[] data, Class type, boolean and) { + this(data, type, and, null); + } + + public SimpleLiteral(T[] data, Class type, boolean and, @Nullable Expression source) { assert data != null; assert type != null; this.data = data; this.type = type; this.and = data.length <= 1 || and; this.isDefault = false; + this.source = source == null ? this : source; } public SimpleLiteral(T data, boolean isDefault) { @@ -66,11 +71,6 @@ public SimpleLiteral(T data, boolean isDefault, @Nullable Expression source) this.source = source == null ? this : source; } - public SimpleLiteral(T[] data, Class to, boolean and, @Nullable Expression source) { - this(data, to, and); - this.source = source == null ? this : source; - } - @Override public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { throw new UnsupportedOperationException(); @@ -203,7 +203,7 @@ public int getTime() { @Override public NonNullIterator iterator(final Event event) { - return new NonNullIterator() { + return new NonNullIterator<>() { private int i = 0; @Override diff --git a/src/test/skript/tests/syntaxes/expressions/ExprXOf.sk b/src/test/skript/tests/syntaxes/expressions/ExprXOf.sk index af08af80093..48fd38b7b13 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprXOf.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprXOf.sk @@ -11,3 +11,8 @@ test "X of": assert 2 of {_z} is 2 stone with "item failed" assert ExprXOf(2) is 2 diamonds with "function return failed - got '%ExprXOf(2)%'" + + # test simplified literal handling + parse: + set {_none} to (8 * 8) of stone + assert last parse logs is not set with "failed to parse with simplified literals" From fd988301a875ea5d501189168b37c75ae0bd4c97 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Wed, 30 Jul 2025 13:07:09 -0600 Subject: [PATCH 10/20] Fixes looping all itemtypes (#8069) --- .../java/ch/njol/skript/aliases/ItemType.java | 42 +++++++++---------- .../tests/regressions/7418-loop-itemtype.sk | 6 +++ 2 files changed, 26 insertions(+), 22 deletions(-) create mode 100644 src/test/skript/tests/regressions/7418-loop-itemtype.sk diff --git a/src/main/java/ch/njol/skript/aliases/ItemType.java b/src/main/java/ch/njol/skript/aliases/ItemType.java index ad25e2c8d0c..4c083d940d8 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemType.java +++ b/src/main/java/ch/njol/skript/aliases/ItemType.java @@ -522,25 +522,30 @@ void remove(int index) { @Override public Iterator containerIterator() { return new Iterator() { - @SuppressWarnings("null") - Iterator iter = types.iterator(); + + final Iterator iter = types.iterator(); + ItemStack nextItem = null; @Override public boolean hasNext() { - return iter.hasNext(); + while (nextItem == null && iter.hasNext()) { + ItemData data = iter.next(); + ItemStack is = data.getStack(); + if (is != null) { + nextItem = is.clone(); + nextItem.setAmount(getAmount()); + } + } + return nextItem != null; } @Override public ItemStack next() { - ItemStack is = null; - while (is == null) { - if (!hasNext()) - throw new NoSuchElementException(); - is = iter.next().getStack(); - } - is = is.clone(); - is.setAmount(getAmount()); - return is; + if (!hasNext()) + throw new NoSuchElementException(); + ItemStack result = nextItem; + nextItem = null; + return result; } @Override @@ -557,17 +562,10 @@ public void remove() { */ public Iterable getAll() { if (!isAll()) { - final ItemStack i = getRandom(); - if (i == null) - return EmptyIterable.get(); - return new SingleItemIterable<>(i); + ItemStack i = getRandom(); + return (i == null) ? EmptyIterable.get() : new SingleItemIterable<>(i); } - return new Iterable() { - @Override - public Iterator iterator() { - return containerIterator(); - } - }; + return this::containerIterator; } /** diff --git a/src/test/skript/tests/regressions/7418-loop-itemtype.sk b/src/test/skript/tests/regressions/7418-loop-itemtype.sk new file mode 100644 index 00000000000..8872fedd701 --- /dev/null +++ b/src/test/skript/tests/regressions/7418-loop-itemtype.sk @@ -0,0 +1,6 @@ +test "loop all itemtypes": + loop all itemtypes: + set {_i::%loop-iteration%} to loop-value + assert size of {_i::*} > 1000 with "There should be at LEAST 1000 ItemTypes" + assert {_i::*} contains diamond sword with "The list should contain a diamond sword" + assert {_i::*} contains stone with "The list should contain stone" From d8d7bdd2146db2e7b97d9a195d8fdce142e40afb Mon Sep 17 00:00:00 2001 From: SirSmurfy2 <82696841+Absolutionism@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:15:15 -0400 Subject: [PATCH 11/20] Happy Ghast Equipment (#8086) --- .../java/ch/njol/skript/effects/EffEquip.java | 57 ++++++++++++++----- .../skript/expressions/ExprArmorSlot.java | 18 +++--- .../regressions/8086-happy ghast equipment.sk | 15 +++++ 3 files changed, 67 insertions(+), 23 deletions(-) create mode 100644 src/test/skript/tests/regressions/8086-happy ghast equipment.sk diff --git a/src/main/java/ch/njol/skript/effects/EffEquip.java b/src/main/java/ch/njol/skript/effects/EffEquip.java index 2068f6ead5c..5ab958dbc58 100644 --- a/src/main/java/ch/njol/skript/effects/EffEquip.java +++ b/src/main/java/ch/njol/skript/effects/EffEquip.java @@ -1,7 +1,6 @@ package ch.njol.skript.effects; import ch.njol.skript.Skript; -import ch.njol.skript.aliases.ItemData; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.bukkitutil.PlayerUtils; import ch.njol.skript.doc.Description; @@ -19,6 +18,7 @@ import org.bukkit.entity.Horse; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Llama; +import org.bukkit.entity.Mob; import org.bukkit.entity.Player; import org.bukkit.entity.Steerable; import org.bukkit.entity.Wolf; @@ -43,21 +43,31 @@ "unequip diamond chestplate from player", "unequip player's armor" }) -@Since("1.0, 2.7 (multiple entities, unequip), 2.10 (wolves)") +@Since({ + "1.0, 2.7 (multiple entities, unequip), 2.10 (wolves)", + "INSERT VERSION (happy ghasts)" +}) public class EffEquip extends Effect { - private static ItemType CHESTPLATE; - private static ItemType LEGGINGS; - private static ItemType BOOTS; - private static ItemType CARPET = new ItemType(Tag.WOOL_CARPETS); - private static ItemType WOLF_ARMOR; + private static final ItemType CHESTPLATE; + private static final ItemType LEGGINGS; + private static final ItemType BOOTS; + private static final ItemType CARPET = new ItemType(Tag.WOOL_CARPETS); + private static final ItemType WOLF_ARMOR; private static final ItemType HORSE_ARMOR = new ItemType(Material.LEATHER_HORSE_ARMOR, Material.IRON_HORSE_ARMOR, Material.GOLDEN_HORSE_ARMOR, Material.DIAMOND_HORSE_ARMOR); private static final ItemType SADDLE = new ItemType(Material.SADDLE); private static final ItemType CHEST = new ItemType(Material.CHEST); + private static final ItemType HAPPY_GHAST_HARNESS; + + private static final Class HAPPY_GHAST_CLASS; static { - boolean hasWolfArmor = Skript.fieldExists(Material.class, "WOLF_ARMOR"); - WOLF_ARMOR = hasWolfArmor ? new ItemType(Material.WOLF_ARMOR) : new ItemType(); + // added in 1.20.5 + if (Skript.fieldExists(Material.class, "WOLF_ARMOR")) { + WOLF_ARMOR = new ItemType(Material.WOLF_ARMOR); + } else { + WOLF_ARMOR = new ItemType(); + } // added in 1.20.6 if (Skript.fieldExists(Tag.class, "ITEM_CHEST_ARMOR")) { @@ -71,7 +81,7 @@ public class EffEquip extends Effect { Material.GOLDEN_CHESTPLATE, Material.IRON_CHESTPLATE, Material.DIAMOND_CHESTPLATE, - Material.NETHERITE_CHESTPLATE, + Material.NETHERITE_CHESTPLATE, Material.ELYTRA ); @@ -81,7 +91,7 @@ public class EffEquip extends Effect { Material.GOLDEN_LEGGINGS, Material.IRON_LEGGINGS, Material.DIAMOND_LEGGINGS, - Material.NETHERITE_LEGGINGS + Material.NETHERITE_LEGGINGS ); BOOTS = new ItemType( @@ -90,12 +100,25 @@ public class EffEquip extends Effect { Material.GOLDEN_BOOTS, Material.IRON_BOOTS, Material.DIAMOND_BOOTS, - Material.NETHERITE_BOOTS + Material.NETHERITE_BOOTS ); } + + // added in 1.21.6 + if (Skript.fieldExists(Tag.class, "ITEMS_HARNESSES")) { + HAPPY_GHAST_HARNESS = new ItemType(Tag.ITEMS_HARNESSES); + try { + HAPPY_GHAST_CLASS = Class.forName("org.bukkit.entity.HappyGhast"); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } else { + HAPPY_GHAST_HARNESS = new ItemType(); + HAPPY_GHAST_CLASS = null; + } } - private static final ItemType[] ALL_EQUIPMENT = new ItemType[] {CHESTPLATE, LEGGINGS, BOOTS, HORSE_ARMOR, SADDLE, CHEST, CARPET, WOLF_ARMOR}; + private static final ItemType[] ALL_EQUIPMENT = new ItemType[] {CHESTPLATE, LEGGINGS, BOOTS, HORSE_ARMOR, SADDLE, CHEST, CARPET, WOLF_ARMOR, HAPPY_GHAST_HARNESS}; static { Skript.registerEffect(EffEquip.class, @@ -177,6 +200,14 @@ protected void execute(Event event) { equipment.setItem(EquipmentSlot.BODY, equip ? item : null); } } + } else if (HAPPY_GHAST_CLASS != null && HAPPY_GHAST_CLASS.isInstance(entity)) { + EntityEquipment equipment = ((Mob) entity).getEquipment(); + for (ItemType itemType : itemTypes) { + for (ItemStack itemStack : itemType.getAll()) { + if (HAPPY_GHAST_HARNESS.isOfType(itemStack)) + equipment.setItem(EquipmentSlot.BODY, equip ? itemStack : null); + } + } } else { EntityEquipment equipment = entity.getEquipment(); if (equipment == null) diff --git a/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java b/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java index b0d3a1dbb96..22be9847ad6 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java +++ b/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java @@ -15,15 +15,7 @@ import ch.njol.skript.util.slot.Slot; import ch.njol.util.Kleenean; import org.bukkit.Material; -import org.bukkit.entity.AbstractHorse; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Horse; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Llama; -import org.bukkit.entity.Pig; -import org.bukkit.entity.Strider; -import org.bukkit.entity.TraderLlama; -import org.bukkit.entity.Wolf; +import org.bukkit.entity.*; import org.bukkit.event.Event; import org.bukkit.inventory.EntityEquipment; import org.jetbrains.annotations.Nullable; @@ -43,6 +35,7 @@ "
  • Horses: Horse armour (doesn't work on zombie or skeleton horses)
  • ", "
  • Wolves: Wolf Armor
  • ", "
  • Llamas (regular or trader): Carpet
  • ", + "
  • Happy Ghasts: Harness
  • ", "", "Saddle is a special slot that can only be used for: pigs, striders and horse types (horse, camel, llama, mule, donkey)." }) @@ -51,7 +44,10 @@ "helmet of player is neither tag values of tag \"paper:helmets\" nor air # player is wearing a block, e.g. from another plugin" }) @Keywords("armor") -@Since("1.0, 2.8.0 (armor), 2.10 (body armor), 2.12 (saddle)") +@Since({ + "1.0, 2.8.0 (armor), 2.10 (body armor), 2.12 (saddle)", + "INSERT VERSION (happy ghast)" +}) public class ExprArmorSlot extends PropertyExpression { private static final Set> BODY_ENTITIES = @@ -71,6 +67,8 @@ public class ExprArmorSlot extends PropertyExpression { static { if (Material.getMaterial("WOLF_ARMOR") != null) BODY_ENTITIES.add(Wolf.class); + if (Skript.classExists("org.bukkit.entity.HappyGhast")) + BODY_ENTITIES.add(HappyGhast.class); register(ExprArmorSlot.class, Slot.class, "(%-*equipmentslots%|[the] armo[u]r[s]) [item:item[s]]", "livingentities"); } diff --git a/src/test/skript/tests/regressions/8086-happy ghast equipment.sk b/src/test/skript/tests/regressions/8086-happy ghast equipment.sk new file mode 100644 index 00000000000..ba4f346320e --- /dev/null +++ b/src/test/skript/tests/regressions/8086-happy ghast equipment.sk @@ -0,0 +1,15 @@ +test "happy ghast equipment" when running minecraft "1.21.6": + spawn a happy ghast at test-location: + set {_entity} to entity + + equip {_entity} with a red harness + assert the body slot of {_entity} is a red harness with "Equip effect did not equip harness to happy ghast" + unequip a red harness from {_entity} + assert the body slot of {_entity} is air with "Equip effect did not unequip harness from happy ghast" + + set the body slot of {_entity} to a blue harness + assert the body slot of {_entity} is a blue harness with "ExprArmorSlot did not set body slot of happy ghast" + clear the body slot of {_entity} + assert the body slot of {_entity} is air with "ExprArmorSlot did not clear body slot of happy ghast" + + clear entity within {_entity} From 625e0479c3a632c677d9c2c839e808c7e2ccb7ba Mon Sep 17 00:00:00 2001 From: sweetestpiper Date: Thu, 31 Jul 2025 07:25:53 +1200 Subject: [PATCH 12/20] Correct verb spellings (#8088) --- src/main/resources/config.sk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index 0544dde5fd9..4fe3498160b 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -335,7 +335,7 @@ databases: backup interval: 2 hours # Creates a backup of the file every so often. This can be useful if you ever want to revert variables to an older state. - # Variables are saved constantly no matter what is set here, thus a server crash will never make you loose any variables. + # Variables are saved constantly no matter what is set here, thus a server crash will never make you lose any variables. # Set this to 0 to disable this feature. backups to keep: -1 @@ -385,7 +385,7 @@ databases: default: # The default "database" is a simple text file, with each variable on a separate line and the variable's name, type, and value separated by commas. # This is the last database in this list to catch all variables that have not been saved anywhere else. - # You can modify this database freely, but make sure to know what you're doing if you don't want to loose any variables. + # You can modify this database freely, but make sure to know what you're doing if you don't want to lose any variables. type: CSV From a9f9694ca473f42bce9761aef0caedb077b624b3 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Wed, 30 Jul 2025 12:35:02 -0700 Subject: [PATCH 13/20] Catch rounding edge cases (#8087) --- .../njol/skript/classes/data/DefaultFunctions.java | 12 +++++++++--- .../skript/tests/regressions/8085-rounding nan.sk | 11 +++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 src/test/skript/tests/regressions/8085-rounding nan.sk diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java index a28f3c4edf9..df5f2118eb7 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -59,10 +59,16 @@ public Long[] executeSimple(Object[][] params) { Functions.registerFunction(new SimpleJavaFunction("round", new Parameter[] {new Parameter<>("n", DefaultClasses.NUMBER, true, null), new Parameter<>("d", DefaultClasses.NUMBER, true, new SimpleLiteral(0, false))}, DefaultClasses.NUMBER, true) { @Override public Number[] executeSimple(Object[][] params) { - if (params[0][0] instanceof Long) - return new Long[] {(Long) params[0][0]}; + if (params[0][0] instanceof Long longValue) + return new Long[] {longValue}; double value = ((Number) params[0][0]).doubleValue(); - int placement = ((Number) params[1][0]).intValue(); + if (!Double.isFinite(value)) + return new Double[] {value}; + + double placementDouble = ((Number) params[1][0]).doubleValue(); + if (!Double.isFinite(placementDouble) || placementDouble >= Integer.MAX_VALUE || placementDouble <= Integer.MIN_VALUE) + return new Double[] {Double.NaN}; + int placement = (int) placementDouble; if (placement == 0) return new Long[] {Math2.round(value)}; if (placement >= 0) { diff --git a/src/test/skript/tests/regressions/8085-rounding nan.sk b/src/test/skript/tests/regressions/8085-rounding nan.sk new file mode 100644 index 00000000000..eb6bebd4a6a --- /dev/null +++ b/src/test/skript/tests/regressions/8085-rounding nan.sk @@ -0,0 +1,11 @@ +test "rounding nan": + assert isNaN(round(NaN, 1)) is true with "rounding NaN should return NaN" + assert isNaN(round(NaN)) is true with "rounding NaN should return NaN" + assert isNaN(round(NaN, NaN)) is true with "rounding NaN with NaN should return NaN" + assert isNaN(round(1.1, NaN)) is true with "rounding 1.1 with NaN should return NaN" + + assert round(positive infinity) is positive infinity with "rounding positive infinity should return positive infinity" + assert round(negative infinity) is negative infinity with "rounding negative infinity should return negative infinity" + assert isNaN(round(1.1, positive infinity)) is true with "rounding 1.1 to positive infinity should return positive infinity" + assert isNaN(round(1.1, negative infinity)) is true with "rounding 1.1 to negative infinity should return negative infinity" + assert isNaN(round(1.1, 2147483648)) is true with "rounding 1.1 to too large a place should return NaN" From 98d58461b83fb126c10bac8b5ecaf87910b1ad60 Mon Sep 17 00:00:00 2001 From: Patrick Miller Date: Wed, 30 Jul 2025 15:46:11 -0400 Subject: [PATCH 14/20] Improve default syntax ordering (#8076) --- .../skript/registration/SyntaxRegister.java | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/skriptlang/skript/registration/SyntaxRegister.java b/src/main/java/org/skriptlang/skript/registration/SyntaxRegister.java index 2b1dd9dcd44..974ffb72a0e 100644 --- a/src/main/java/org/skriptlang/skript/registration/SyntaxRegister.java +++ b/src/main/java/org/skriptlang/skript/registration/SyntaxRegister.java @@ -14,13 +14,45 @@ */ final class SyntaxRegister> { + private static int calculateComplexityScore(SyntaxInfo info) { + return info.patterns().stream() + .mapToInt(SyntaxRegister::calculateComplexityScore) + .max() + .orElseThrow(); // a syntax info should have at least one pattern + } + + private static int calculateComplexityScore(String pattern) { + int score = 0; + char[] chars = pattern.toCharArray(); + for (int i = 0; i < chars.length; i++) { + if (chars[i] == '%') { + // weigh "%thing% %thing%" or "%thing% [%thing%]" heavier + if ((i - 2 >= 0 && chars[i - 2] == '%') || (i - 3 >= 0 && chars[i - 3] == '%')) { + score += 3; + } else { + score++; + } + } + } + return score; + } + private static final Comparator> SET_COMPARATOR = (a,b) -> { if (a == b) { // only considered equal if registering the same infos return 0; } - int result = a.priority().compareTo(b.priority()); - // when elements have the same priority, order by hashcode - return result != 0 ? result : Integer.compare(a.hashCode(), b.hashCode()); + // priority is the primary factor in determining ordering + int priorityResult = a.priority().compareTo(b.priority()); + if (priorityResult != 0) { + return priorityResult; + } + // otherwise, consider the complexity of the syntax + int scoreResult = Integer.compare(calculateComplexityScore(a), calculateComplexityScore(b)); + if (scoreResult != 0) { + return scoreResult; + } + // otherwise, order by hashcode + return Integer.compare(a.hashCode(), b.hashCode()); }; final Set syntaxes = new ConcurrentSkipListSet<>(SET_COMPARATOR); From bed01fbee267b1cec8f3ddddb300e19d0b7543b8 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 1 Aug 2025 11:20:44 -0600 Subject: [PATCH 15/20] Fixing being able to wait indefinite (#8043) * Fixing being able to wait indefinite * Update src/main/java/ch/njol/skript/effects/Delay.java Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> * Update Delay.java --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Efnilite <35348263+Efnilite@users.noreply.github.com> --- src/main/java/ch/njol/skript/effects/Delay.java | 7 ++++++- src/main/java/ch/njol/skript/expressions/LitEternity.java | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/effects/Delay.java b/src/main/java/ch/njol/skript/effects/Delay.java index 72e1a0dd786..94179d8fdb0 100644 --- a/src/main/java/ch/njol/skript/effects/Delay.java +++ b/src/main/java/ch/njol/skript/effects/Delay.java @@ -47,7 +47,12 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye duration = (Expression) exprs[0]; if (duration instanceof Literal) { // If we can, do sanity check for delays - long millis = ((Literal) duration).getSingle().getAs(Timespan.TimePeriod.MILLISECOND); + Timespan timespan = ((Literal) duration).getSingle(); + if (timespan.isInfinite()) { + Skript.error("Delaying for an eternity is not allowed. Use the 'stop' effect instead."); + return false; + } + long millis = timespan.getAs(Timespan.TimePeriod.MILLISECOND); if (millis < 50) { Skript.warning("Delays less than one tick are not possible, defaulting to one tick."); } diff --git a/src/main/java/ch/njol/skript/expressions/LitEternity.java b/src/main/java/ch/njol/skript/expressions/LitEternity.java index 76f5acb48f0..243017b070a 100644 --- a/src/main/java/ch/njol/skript/expressions/LitEternity.java +++ b/src/main/java/ch/njol/skript/expressions/LitEternity.java @@ -15,7 +15,8 @@ import org.jetbrains.annotations.Nullable; @Name("An Eternity") -@Description({"Represents a timespan with an infinite duration. " + +@Description({ + "Represents a timespan with an infinite duration. " + "An eternity is also created when arithmetic results in a timespan larger than about 292 million years.", "Infinite timespans generally follow the rules of infinity, where most math operations do nothing. " + "However, operations that would return NaN with numbers will instead return a timespan of 0 seconds.", From 334ced24eb9aa9bd61997c9cf25ff6a2e6673250 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Fri, 1 Aug 2025 10:52:03 -0700 Subject: [PATCH 16/20] Limit literal specification regex pattern to improve matching behavior (#8068) --- .../ch/njol/skript/lang/SkriptParser.java | 145 +++++++++--------- .../tests/misc/literal specification.sk | 7 + 2 files changed, 83 insertions(+), 69 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index cb10b71cc86..e6f2bda4a02 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -467,31 +467,12 @@ private static boolean checkExperimentalSyntax(T eleme log.printError(); return null; } - if (types[0] == Object.class) { - // Do check if a literal with this name actually exists before returning an UnparsedLiteral - if (!allowUnparsedLiteral || Classes.parseSimple(expr, Object.class, context) == null) { - log.printError(); - return null; - } - log.clear(); - LogEntry logError = log.getError(); - return (Literal) new UnparsedLiteral(expr, logError != null && (error == null || logError.quality > error.quality) ? logError : error); - } - for (Class type : types) { - log.clear(); - assert type != null; - T parsedObject = Classes.parse(expr, type, context); - if (parsedObject != null) { - log.printLog(); - return new SimpleLiteral<>(parsedObject, false); - } - } - log.printError(); - return null; + return parseAsLiteral(allowUnparsedLiteral, log, error, types); } } - private static final Pattern LITERAL_SPECIFICATION_PATTERN = Pattern.compile("(?[^(]+) \\((?[^)]+)\\)"); + private static final String INVALID_LSPEC_CHARS = "[^,():/\"'\\[\\]}{]"; + private static final Pattern LITERAL_SPECIFICATION_PATTERN = Pattern.compile("(?" + INVALID_LSPEC_CHARS + "+) \\((?[\\w\\p{L} ]+)\\)"); private @Nullable Expression parseSingleExpr(boolean allowUnparsedLiteral, @Nullable LogEntry error, ExprInfo exprInfo) { if (expr.isEmpty()) // Empty expressions return nothing, obviously @@ -675,56 +656,80 @@ private static boolean checkExperimentalSyntax(T eleme log.printError(); return null; } - if (expr.endsWith(")") && expr.indexOf("(") != -1) { - Matcher classInfoMatcher = LITERAL_SPECIFICATION_PATTERN.matcher(expr); - if (classInfoMatcher.matches()) { - String literalString = classInfoMatcher.group("literal"); - String unparsedClassInfo = Noun.stripDefiniteArticle(classInfoMatcher.group("classinfo")); - Expression result = parseSpecifiedLiteral(literalString, unparsedClassInfo, types); - if (result != null) { - log.printLog(); - return result; - } + return parseAsLiteral(allowUnparsedLiteral, log, error, nonNullTypes); + } + } + + /** + * Helper method for {@link #parseSingleExpr(boolean, LogEntry, Class[])} and {@link #parseSingleExpr(boolean, LogEntry, ExprInfo)}. + * Attempts to parse {@link #expr} as a literal. Prints errors. + * + * @param allowUnparsedLiteral If {@code true}, will allow unparsed literals to be returned. + * @param log The current {@link ParseLogHandler} to use for logging. + * @param error A {@link LogEntry} containing a default error to be printed if failed to parse. + * @param types The valid types to parse the literal as. + * @return {@link Expression} of type {@code T} if successful, otherwise {@code null}.
    + * @param The type of the literal to parse.
    + */ + @SafeVarargs + private @Nullable Expression parseAsLiteral( + boolean allowUnparsedLiteral, + ParseLogHandler log, + @Nullable LogEntry error, + Class... types + ) { + if (expr.endsWith(")") && expr.contains("(")) { + Matcher classInfoMatcher = LITERAL_SPECIFICATION_PATTERN.matcher(expr); + if (classInfoMatcher.matches()) { + String literalString = classInfoMatcher.group("literal"); + String unparsedClassInfo = Noun.stripDefiniteArticle(classInfoMatcher.group("classinfo")); + Expression result = parseSpecifiedLiteral(literalString, unparsedClassInfo, types); + if (result != null) { + log.printLog(); + return result; } } - if (exprInfo.classes.length == 1 && exprInfo.classes[0].getC() == Object.class) { - if (!allowUnparsedLiteral) { - log.printError(); - return null; - } - return getUnparsedLiteral(log, error); + } + if (types.length == 1 && types[0] == Object.class) { + if (!allowUnparsedLiteral) { + log.printError(); + return null; } - boolean containsObjectClass = false; - for (ClassInfo classInfo : exprInfo.classes) { - log.clear(); - assert classInfo.getC() != null; - if (classInfo.getC().equals(Object.class)) { - // If 'Object.class' is an option, needs to be treated as previous behavior - // But we also want to be sure every other 'ClassInfo' is attempted to be parsed beforehand - containsObjectClass = true; - continue; - } - Object parsedObject = Classes.parse(expr, classInfo.getC(), context); - if (parsedObject != null) { - log.printLog(); - return new SimpleLiteral<>(parsedObject, false, new UnparsedLiteral(expr)); - } + //noinspection unchecked + return (Expression) getUnparsedLiteral(log, error); + } + boolean containsObjectClass = false; + for (Class type : types) { + log.clear(); + if (type == Object.class) { + // If 'Object.class' is an option, needs to be treated as previous behavior + // But we also want to be sure every other 'ClassInfo' is attempted to be parsed beforehand + containsObjectClass = true; + continue; } - if (allowUnparsedLiteral && containsObjectClass) - return getUnparsedLiteral(log, error); - if (expr.startsWith("\"") && expr.endsWith("\"") && expr.length() > 1) { - for (ClassInfo aClass : exprInfo.classes) { - if (!aClass.getC().isAssignableFrom(String.class)) - continue; - VariableString string = VariableString.newInstance(expr.substring(1, expr.length() - 1)); - if (string instanceof LiteralString) - return string; - break; - } + //noinspection unchecked + T parsedObject = (T) Classes.parse(expr, type, context); + if (parsedObject != null) { + log.printLog(); + return new SimpleLiteral<>(parsedObject, false, new UnparsedLiteral(expr)); } - log.printError(); - return null; } + if (allowUnparsedLiteral && containsObjectClass) + //noinspection unchecked + return (Expression) getUnparsedLiteral(log, error); + if (expr.startsWith("\"") && expr.endsWith("\"") && expr.length() > 1) { + for (Class type : types) { + if (!type.isAssignableFrom(String.class)) + continue; + VariableString string = VariableString.newInstance(expr.substring(1, expr.length() - 1)); + if (string instanceof LiteralString) + //noinspection unchecked + return (Expression) string; + break; + } + } + log.printError(); + return null; } /** @@ -759,10 +764,11 @@ private static boolean checkExperimentalSyntax(T eleme * @param types An {@link Array} of the acceptable {@link Class}es * @return {@link SimpleLiteral} or {@code null} if any checks fail */ - private @Nullable Expression parseSpecifiedLiteral( + @SafeVarargs + private @Nullable Expression parseSpecifiedLiteral( String literalString, String unparsedClassInfo, - Class ... types + Class ... types ) { ClassInfo classInfo = Classes.parse(unparsedClassInfo, ClassInfo.class, context); if (classInfo == null) { @@ -778,7 +784,8 @@ private static boolean checkExperimentalSyntax(T eleme Skript.error(expr + " " + Language.get("is") + " " + notOfType(types)); return null; } - Object parsedObject = classInfoParser.parse(literalString, context); + //noinspection unchecked + T parsedObject = (T) classInfoParser.parse(literalString, context); if (parsedObject != null) return new SimpleLiteral<>(parsedObject, false, new UnparsedLiteral(literalString)); return null; diff --git a/src/test/skript/tests/misc/literal specification.sk b/src/test/skript/tests/misc/literal specification.sk index a98ea2fe7f6..be175cc20b5 100644 --- a/src/test/skript/tests/misc/literal specification.sk +++ b/src/test/skript/tests/misc/literal specification.sk @@ -30,6 +30,13 @@ test "literal specification": assert firework (the item type) is an item type with "Literal specification should work with definite articles" assert firework (an entity type) is an entity type with "Literal specification should work with indefinite article" +local function test(entity: entitytype, item: itemtype): + assert {_entity} is an entity type with "Literal specification should work with entitytype" + assert {_item} is an item type with "Literal specification should work with itemtype" + +test "literal specification in functions": + test(firework (entity type), firework (item type)) + test "literal specification error": parse: set {_block} to oak log (block) From 23a00b2f6bb2440558f0742b4c98786b3956ae2f Mon Sep 17 00:00:00 2001 From: SirSmurfy2 <82696841+Absolutionism@users.noreply.github.com> Date: Fri, 1 Aug 2025 15:13:35 -0400 Subject: [PATCH 17/20] Match Any Default Expression (#8089) --- .../skript/lang/DefaultExpressionUtils.java | 196 ++++++++++ .../ch/njol/skript/lang/SkriptParser.java | 104 +++-- .../njol/skript/lang/util/SimpleLiteral.java | 6 +- .../lang/DefaultExpressionErrorTest.java | 114 ++++++ .../lang/GetDefaultExpressionsTest.java | 354 ++++++++++++++++++ 5 files changed, 751 insertions(+), 23 deletions(-) create mode 100644 src/main/java/ch/njol/skript/lang/DefaultExpressionUtils.java create mode 100644 src/test/java/ch/njol/skript/lang/DefaultExpressionErrorTest.java create mode 100644 src/test/java/ch/njol/skript/lang/GetDefaultExpressionsTest.java diff --git a/src/main/java/ch/njol/skript/lang/DefaultExpressionUtils.java b/src/main/java/ch/njol/skript/lang/DefaultExpressionUtils.java new file mode 100644 index 00000000000..5de7eebcbab --- /dev/null +++ b/src/main/java/ch/njol/skript/lang/DefaultExpressionUtils.java @@ -0,0 +1,196 @@ +package ch.njol.skript.lang; + +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.lang.SkriptParser.ExprInfo; +import ch.njol.util.StringUtils; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * Utility class for {@link DefaultExpression}. + */ +final class DefaultExpressionUtils { + + /** + * Check if {@code expr} is valid with the settings from {@code exprInfo}. + * + * @param expr The {@link DefaultExpression} to check. + * @param exprInfo The {@link ExprInfo} to check {@code expr} against its settings. + * @param index The index of the {@link ClassInfo} in {@code exprInfo} used to grab {@code expr}. + * @return {@link DefaultExpressionError} if it's not valid, otherwise {@code null}. + */ + static @Nullable DefaultExpressionError isValid(DefaultExpression expr, ExprInfo exprInfo, int index) { + if (expr == null) { + return DefaultExpressionError.NOT_FOUND; + } else if (!(expr instanceof Literal) && (exprInfo.flagMask & SkriptParser.PARSE_EXPRESSIONS) == 0) { + return DefaultExpressionError.NOT_LITERAL; + } else if (expr instanceof Literal && (exprInfo.flagMask & SkriptParser.PARSE_LITERALS) == 0) { + return DefaultExpressionError.LITERAL; + } else if (!exprInfo.isPlural[index] && !expr.isSingle()) { + return DefaultExpressionError.NOT_SINGLE; + } else if (exprInfo.time != 0 && !expr.setTime(exprInfo.time)) { + return DefaultExpressionError.TIME_STATE; + } + return null; + } + + enum DefaultExpressionError { + /** + * Error type for when a {@link DefaultExpression} can not be found for a {@link Class}. + */ + NOT_FOUND { + @Override + public String getError(List codeNames, String pattern) { + StringBuilder builder = new StringBuilder(); + String combinedComma = getCombinedComma(codeNames); + String combinedSlash = StringUtils.join(codeNames, "/"); + builder.append(plurality(codeNames, "The class '", "The classes '")); + builder.append(combinedComma) + .append("'") + .append(plurality(codeNames, " does ", " do ")) + .append("not provide a default expression. Either allow null (with %-") + .append(combinedSlash) + .append("%) or make it mandatory [pattern: ") + .append(pattern) + .append("]"); + return builder.toString(); + } + }, + + /** + * Error type for when the {@link DefaultExpression} for a {@link Class} is not a {@link Literal} + * and the pattern only accepts {@link Literal}s. + */ + NOT_LITERAL { + @Override + public String getError(List codeNames, String pattern) { + StringBuilder builder = new StringBuilder(); + builder.append(defaultExpression(codeNames, " is not a literal. ", " are not literals. ")) + .append("Either allow null (with %-*") + .append(StringUtils.join(codeNames, "/")) + .append("%) or make it mandatory [pattern: ") + .append(pattern) + .append("]"); + return builder.toString(); + } + }, + + /** + * Error type for when the {@link DefaultExpression} for a {@link Class} is a {@link Literal} + * and the pattern does not accept {@link Literal}s. + */ + LITERAL { + @Override + public String getError(List codeNames, String pattern) { + StringBuilder builder = new StringBuilder(); + builder.append(defaultExpression(codeNames, " is a literal. ", " are literals. ")) + .append("Either allow null (with %-~") + .append(StringUtils.join(codeNames, "/")) + .append("%) or make it mandatory [pattern: ") + .append(pattern) + .append("]"); + return builder.toString(); + } + }, + + /** + * Error type for when the {@link DefaultExpression} for a {@link Class} is plural + * but the pattern only accepts single. + */ + NOT_SINGLE { + @Override + public String getError(List codeNames, String pattern) { + StringBuilder builder = new StringBuilder(); + builder.append(defaultExpression(codeNames, " is not a single-element expression. ", " are not single-element expressions. ")) + .append("Change your pattern to allow multiple elements or make the expression mandatory [pattern: ") + .append(pattern) + .append("]"); + return builder.toString(); + } + }, + + /** + * Error type for when the {@link DefaultExpression} for a {@link Class} does not accept time states + * but the pattern infers it. + */ + TIME_STATE { + @Override + public String getError(List codeNames, String pattern) { + StringBuilder builder = new StringBuilder(defaultExpression(codeNames, " does ", " do ")); + builder.append("not have distinct time states. [pattern: ") + .append(pattern) + .append("]"); + return builder.toString(); + } + }; + + /** + * Returns an error message for the given type. + * + * @param codeNames The codeNames of {@link ClassInfo}s to include in the error message. + * @param pattern The pattern to include in the error message. + * @return error message. + */ + public abstract String getError(List codeNames, String pattern); + + /** + * Utility method for constructing error messages in the format of + * + * The default expression(s) of (codenames) (single/plural) + * single -> The default expression of item type is + * plural -> the default expressions of item type and entity are + * + * + * @param codeNames The list of codenames to be included in the error message. + * @param single The string to be formatted at the end if there is only one codename. + * @param plural The string to be formatted at the end if there is more than one codename. + * @return The formatted error message. + */ + private static String defaultExpression(List codeNames, String single, String plural) { + StringBuilder builder = new StringBuilder(); + String combinedComma = getCombinedComma(codeNames); + builder.append("The default ") + .append(plurality(codeNames, "expression ", "expressions ")) + .append("of '") + .append(combinedComma) + .append("'") + .append(plurality(codeNames, single, plural)); + return builder.toString(); + } + + /** + * Utility method for grabbing {@code single} if {@code codeNames} is singular, otherwise {@code plural}. + * + * @param codeNames The list of codenames to be checked. + * @param single The string to be used if there is only one codename. + * @param plural The string to be used if there is more than one codename. + * @return {@code single} or {@code plural}. + */ + private static String plurality(List codeNames, String single, String plural) { + return codeNames.size() > 1 ? plural : single; + } + + /** + * Utility method for combining {@code codeNames} into one string following this format. + *

    + * 1: x + * 2: x and y + * 3 or more: x, y, and z + *

    + * @param codeNames {@link List} of codenames to combine. + * @return The combined string. + */ + private static String getCombinedComma(List codeNames) { + assert !codeNames.isEmpty(); + if (codeNames.size() == 1) { + return codeNames.get(0); + } else if (codeNames.size() == 2) { + return StringUtils.join(codeNames, " and "); + } else { + return StringUtils.join(codeNames, ", ", ", and "); + } + } + } + +} diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index e6f2bda4a02..26f738fa3b1 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -10,6 +10,7 @@ import ch.njol.skript.command.ScriptCommand; import ch.njol.skript.command.ScriptCommandEvent; import ch.njol.skript.expressions.ExprParse; +import ch.njol.skript.lang.DefaultExpressionUtils.DefaultExpressionError; import ch.njol.skript.lang.function.ExprFunctionCall; import ch.njol.skript.lang.function.FunctionReference; import ch.njol.skript.lang.function.Functions; @@ -17,6 +18,7 @@ import ch.njol.skript.lang.parser.ParseStackOverflowException; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.lang.parser.ParsingStack; +import ch.njol.skript.lang.simplification.Simplifiable; import ch.njol.skript.lang.util.SimpleLiteral; import ch.njol.skript.localization.Language; import ch.njol.skript.localization.Message; @@ -48,10 +50,17 @@ import org.skriptlang.skript.lang.script.ScriptWarning; import org.skriptlang.skript.registration.SyntaxInfo; import org.skriptlang.skript.registration.SyntaxRegistry; -import ch.njol.skript.lang.simplification.Simplifiable; import java.lang.reflect.Array; -import java.util.*; +import java.util.ArrayList; +import java.util.Deque; +import java.util.EnumMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; @@ -238,10 +247,17 @@ public boolean hasTag(String tag) { types = parseResult.source.getElements(TypePatternElement.class);; ExprInfo exprInfo = types.get(i).getExprInfo(); if (!exprInfo.isOptional) { - DefaultExpression expr = getDefaultExpression(exprInfo, pattern); - if (!expr.init()) + List> exprs = getDefaultExpressions(exprInfo, pattern); + DefaultExpression matchedExpr = null; + for (DefaultExpression expr : exprs) { + if (expr.init()) { + matchedExpr = expr; + break; + } + } + if (matchedExpr == null) continue patternsLoop; - parseResult.exprs[i] = expr; + parseResult.exprs[i] = matchedExpr; } } } @@ -327,27 +343,71 @@ private static boolean checkExperimentalSyntax(T eleme return experimentalSyntax.isSatisfiedBy(experiments); } + /** + * Returns the {@link DefaultExpression} from the first {@link ClassInfo} stored in {@code exprInfo}. + * + * @param exprInfo The {@link ExprInfo} to check for {@link DefaultExpression}. + * @param pattern The pattern used to create {@link ExprInfo}. + * @return {@link DefaultExpression}. + * @throws SkriptAPIException If the {@link DefaultExpression} is not valid, produces an error message for the reasoning of failure. + */ private static @NotNull DefaultExpression getDefaultExpression(ExprInfo exprInfo, String pattern) { - DefaultExpression expr; - // check custom default values first. DefaultValueData data = getParser().getData(DefaultValueData.class); - expr = data.getDefaultValue(exprInfo.classes[0].getC()); - - // then check classinfo + ClassInfo classInfo = exprInfo.classes[0]; + DefaultExpression expr = data.getDefaultValue(classInfo.getC()); if (expr == null) - expr = exprInfo.classes[0].getDefaultExpression(); + expr = classInfo.getDefaultExpression(); - if (expr == null) - throw new SkriptAPIException("The class '" + exprInfo.classes[0].getCodeName() + "' does not provide a default expression. Either allow null (with %-" + exprInfo.classes[0].getCodeName() + "%) or make it mandatory [pattern: " + pattern + "]"); - if (!(expr instanceof Literal) && (exprInfo.flagMask & PARSE_EXPRESSIONS) == 0) - throw new SkriptAPIException("The default expression of '" + exprInfo.classes[0].getCodeName() + "' is not a literal. Either allow null (with %-*" + exprInfo.classes[0].getCodeName() + "%) or make it mandatory [pattern: " + pattern + "]"); - if (expr instanceof Literal && (exprInfo.flagMask & PARSE_LITERALS) == 0) - throw new SkriptAPIException("The default expression of '" + exprInfo.classes[0].getCodeName() + "' is a literal. Either allow null (with %-~" + exprInfo.classes[0].getCodeName() + "%) or make it mandatory [pattern: " + pattern + "]"); - if (!exprInfo.isPlural[0] && !expr.isSingle()) - throw new SkriptAPIException("The default expression of '" + exprInfo.classes[0].getCodeName() + "' is not a single-element expression. Change your pattern to allow multiple elements or make the expression mandatory [pattern: " + pattern + "]"); - if (exprInfo.time != 0 && !expr.setTime(exprInfo.time)) - throw new SkriptAPIException("The default expression of '" + exprInfo.classes[0].getCodeName() + "' does not have distinct time states. [pattern: " + pattern + "]"); - return expr; + DefaultExpressionError errorType = DefaultExpressionUtils.isValid(expr, exprInfo, 0); + if (errorType == null) { + assert expr != null; + return expr; + } + + throw new SkriptAPIException(errorType.getError(List.of(classInfo.getCodeName()), pattern)); + } + + /** + * Returns all {@link DefaultExpression}s from all the {@link ClassInfo}s embedded in {@code exprInfo} that are valid. + * + * @param exprInfo The {@link ExprInfo} to check for {@link DefaultExpression}s. + * @param pattern The pattern used to create {@link ExprInfo}. + * @return All available {@link DefaultExpression}s. + * @throws SkriptAPIException If no {@link DefaultExpression}s are valid, produces an error message for the reasoning of failure. + */ + static @NotNull List> getDefaultExpressions(ExprInfo exprInfo, String pattern) { + if (exprInfo.classes.length == 1) + return new ArrayList<>(List.of(getDefaultExpression(exprInfo, pattern))); + + DefaultValueData data = getParser().getData(DefaultValueData.class); + + EnumMap> failed = new EnumMap<>(DefaultExpressionError.class); + List> passed = new ArrayList<>(); + for (int i = 0; i < exprInfo.classes.length; i++) { + ClassInfo classInfo = exprInfo.classes[i]; + DefaultExpression expr = data.getDefaultValue(classInfo.getC()); + if (expr == null) + expr = classInfo.getDefaultExpression(); + + String codeName = classInfo.getCodeName(); + DefaultExpressionError errorType = DefaultExpressionUtils.isValid(expr, exprInfo, i); + + if (errorType != null) { + failed.computeIfAbsent(errorType, list -> new ArrayList<>()).add(codeName); + } else { + passed.add(expr); + } + } + + if (!passed.isEmpty()) + return passed; + + List errors = new ArrayList<>(); + for (Entry> entry : failed.entrySet()) { + String error = entry.getKey().getError(entry.getValue(), pattern); + errors.add(error); + } + throw new SkriptAPIException(StringUtils.join(errors, "\n")); } private static final Pattern VARIABLE_PATTERN = Pattern.compile("((the )?var(iable)? )?\\{.+\\}", Pattern.CASE_INSENSITIVE); diff --git a/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java b/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java index 0271947ba02..f0298cf5ae8 100644 --- a/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java +++ b/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java @@ -46,12 +46,16 @@ public SimpleLiteral(T[] data, Class type, boolean and) { } public SimpleLiteral(T[] data, Class type, boolean and, @Nullable Expression source) { + this(data, type, and, false, source); + } + + public SimpleLiteral(T[] data, Class type, boolean and, boolean isDefault, @Nullable Expression source) { assert data != null; assert type != null; this.data = data; this.type = type; this.and = data.length <= 1 || and; - this.isDefault = false; + this.isDefault = isDefault; this.source = source == null ? this : source; } diff --git a/src/test/java/ch/njol/skript/lang/DefaultExpressionErrorTest.java b/src/test/java/ch/njol/skript/lang/DefaultExpressionErrorTest.java new file mode 100644 index 00000000000..43c9647f370 --- /dev/null +++ b/src/test/java/ch/njol/skript/lang/DefaultExpressionErrorTest.java @@ -0,0 +1,114 @@ +package ch.njol.skript.lang; + +import ch.njol.skript.lang.DefaultExpressionUtils.DefaultExpressionError; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +public class DefaultExpressionErrorTest extends SkriptJUnitTest { + + @Test + public void testNotFound() { + Assert.assertEquals( + DefaultExpressionError.NOT_FOUND.getError(List.of("itemtype"), "itemtype"), + "The class 'itemtype' does not provide a default expression. Either allow null (with %-itemtype%) " + + "or make it mandatory [pattern: itemtype]" + ); + + Assert.assertEquals( + DefaultExpressionError.NOT_FOUND.getError(List.of("itemtype", "entity"), "itemtype/entity"), + "The classes 'itemtype and entity' do not provide a default expression. Either allow null (with %-itemtype/entity%) " + + "or make it mandatory [pattern: itemtype/entity]" + ); + + Assert.assertEquals( + DefaultExpressionError.NOT_FOUND.getError(List.of("itemtype", "entity", "object"), "itemtype/entity/object"), + "The classes 'itemtype, entity, and object' do not provide a default expression. Either allow null " + + "(with %-itemtype/entity/object%) or make it mandatory [pattern: itemtype/entity/object]" + ); + } + + @Test + public void testNotLiteral() { + Assert.assertEquals( + DefaultExpressionError.NOT_LITERAL.getError(List.of("itemtype"), "itemtype"), + "The default expression of 'itemtype' is not a literal. Either allow null (with %-*itemtype%) " + + "or make it mandatory [pattern: itemtype]" + ); + + Assert.assertEquals( + DefaultExpressionError.NOT_LITERAL.getError(List.of("itemtype", "entity"), "itemtype/entity"), + "The default expressions of 'itemtype and entity' are not literals. Either allow null (with %-*itemtype/entity%) " + + "or make it mandatory [pattern: itemtype/entity]" + ); + + Assert.assertEquals( + DefaultExpressionError.NOT_LITERAL.getError(List.of("itemtype", "entity", "object"), "itemtype/entity/object"), + "The default expressions of 'itemtype, entity, and object' are not literals. Either allow null " + + "(with %-*itemtype/entity/object%) or make it mandatory [pattern: itemtype/entity/object]" + ); + } + + @Test + public void testLiteral() { + Assert.assertEquals( + DefaultExpressionError.LITERAL.getError(List.of("itemtype"), "itemtype"), + "The default expression of 'itemtype' is a literal. Either allow null (with %-~itemtype%) " + + "or make it mandatory [pattern: itemtype]" + ); + + Assert.assertEquals( + DefaultExpressionError.LITERAL.getError(List.of("itemtype", "entity"), "itemtype/entity"), + "The default expressions of 'itemtype and entity' are literals. Either allow null (with %-~itemtype/entity%) " + + "or make it mandatory [pattern: itemtype/entity]" + ); + + Assert.assertEquals( + DefaultExpressionError.LITERAL.getError(List.of("itemtype", "entity", "object"), "itemtype/entity/object"), + "The default expressions of 'itemtype, entity, and object' are literals. Either allow null " + + "(with %-~itemtype/entity/object%) or make it mandatory [pattern: itemtype/entity/object]" + ); + } + + @Test + public void testNotSingle() { + Assert.assertEquals( + DefaultExpressionError.NOT_SINGLE.getError(List.of("itemtype"), "itemtype"), + "The default expression of 'itemtype' is not a single-element expression. Change your pattern to allow " + + "multiple elements or make the expression mandatory [pattern: itemtype]" + ); + + Assert.assertEquals( + DefaultExpressionError.NOT_SINGLE.getError(List.of("itemtype", "entity"), "itemtype/entity"), + "The default expressions of 'itemtype and entity' are not single-element expressions. Change your pattern " + + "to allow multiple elements or make the expression mandatory [pattern: itemtype/entity]" + ); + + Assert.assertEquals( + DefaultExpressionError.NOT_SINGLE.getError(List.of("itemtype", "entity", "object"), "itemtype/entity/object"), + "The default expressions of 'itemtype, entity, and object' are not single-element expressions. Change your pattern " + + "to allow multiple elements or make the expression mandatory [pattern: itemtype/entity/object]" + ); + } + + @Test + public void testTimeState() { + Assert.assertEquals( + DefaultExpressionError.TIME_STATE.getError(List.of("itemtype"), "itemtype"), + "The default expression of 'itemtype' does not have distinct time states. [pattern: itemtype]" + ); + + Assert.assertEquals( + DefaultExpressionError.TIME_STATE.getError(List.of("itemtype", "entity"), "itemtype/entity"), + "The default expressions of 'itemtype and entity' do not have distinct time states. [pattern: itemtype/entity]" + ); + + Assert.assertEquals( + DefaultExpressionError.TIME_STATE.getError(List.of("itemtype", "entity", "object"), "itemtype/entity/object"), + "The default expressions of 'itemtype, entity, and object' do not have distinct time states. [pattern: itemtype/entity/object]" + ); + } + +} diff --git a/src/test/java/ch/njol/skript/lang/GetDefaultExpressionsTest.java b/src/test/java/ch/njol/skript/lang/GetDefaultExpressionsTest.java new file mode 100644 index 00000000000..ce0a8bbdeae --- /dev/null +++ b/src/test/java/ch/njol/skript/lang/GetDefaultExpressionsTest.java @@ -0,0 +1,354 @@ +package ch.njol.skript.lang; + +import ch.njol.skript.SkriptAPIException; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.SkriptParser.ExprInfo; +import ch.njol.skript.lang.util.SimpleLiteral; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.util.StringUtils; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.function.BiConsumer; + +import static ch.njol.skript.lang.SkriptParser.getDefaultExpressions; + +public class GetDefaultExpressionsTest extends SkriptJUnitTest { + + private static final ClassInfo INFO_SINGLE = new ClassInfo<>(Object.class, "infosingle") + .defaultExpression(new EventValueExpression<>(Object.class)); + + private static final ClassInfo INFO_PLURAL = new ClassInfo<>(Object.class, "infoplural") + .defaultExpression(new EventValueExpression<>(Object[].class)); + + private static final ClassInfo INFO_SINGLE_LITERAL = new ClassInfo<>(Object.class, "infosingleliteral") + .defaultExpression(new SimpleLiteral<>(0, true)); + + private static final ClassInfo INFO_PLURAL_LITERAL = new ClassInfo<>(Object.class, "infopluralliteral") + .defaultExpression(new SimpleLiteral<>(new Object[]{0, 1}, Object.class, true, true, null)); + + private ExprInfo createExprInfo(boolean isPlural, ClassInfo... infos) { + ExprInfo exprInfo = new ExprInfo(infos.length); + for (int i = 0; i < infos.length; i++) { + exprInfo.classes[i] = infos[i]; + exprInfo.isPlural[i] = isPlural; + } + return exprInfo; + } + + private ExprInfo createRespectiveExprInfo(ClassInfo... infos) { + ExprInfo exprInfo = new ExprInfo(infos.length); + for (int i = 0; i < infos.length; i++) { + exprInfo.classes[i] = infos[i]; + exprInfo.isPlural[i] = infos[i].getCodeName().contains("plural"); + } + return exprInfo; + } + + private ExprInfo createAlternateExprInfo(ClassInfo... infos) { + ExprInfo exprInfo = new ExprInfo(infos.length); + for (int i = 0; i < infos.length; i++) { + exprInfo.classes[i] = infos[i]; + exprInfo.isPlural[i] = !infos[i].getCodeName().contains("plural"); + } + return exprInfo; + } + + private String getPattern(ExprInfo exprInfo) { + List list = Arrays.stream(exprInfo.classes).map(ClassInfo::getCodeName).toList(); + return StringUtils.join(list, "/"); + } + + private void test(ExprInfo exprInfo, BiConsumer consumer) { + String pattern = getPattern(exprInfo); + consumer.accept(exprInfo, pattern); + } + + @Test + public void testOneInfo() { + test(createExprInfo(false, INFO_SINGLE), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + test(createExprInfo(true, INFO_SINGLE), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + + test(createExprInfo(false, INFO_PLURAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // fail (NOT_SINGLE) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + }); + test(createExprInfo(true, INFO_PLURAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + + test(createExprInfo(false, INFO_SINGLE_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 2; // pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + test(createExprInfo(true, INFO_SINGLE_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 2; // pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + + test(createExprInfo(false, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 2; // fail (NOT_SINGLE) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + }); + test(createExprInfo(true, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 2; // pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + } + + @Test + public void testTwoInfos() { + test(createExprInfo(false, INFO_SINGLE, INFO_PLURAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (NOT_LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, fail (NOT_SINGLE) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + test(createExprInfo(true, INFO_SINGLE, INFO_PLURAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (NOT_LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, pass + Assert.assertEquals(2, getDefaultExpressions(exprInfo, string).size()); + }); + test(createRespectiveExprInfo(INFO_SINGLE, INFO_PLURAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (NOT_LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, pass + Assert.assertEquals(2, getDefaultExpressions(exprInfo, string).size()); + }); + test(createAlternateExprInfo(INFO_SINGLE, INFO_PLURAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (NOT_LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, fail (NOT_SINGLE) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + + test(createExprInfo(false, INFO_SINGLE, INFO_SINGLE_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, fail (LITERAL) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + test(createExprInfo(true, INFO_SINGLE, INFO_SINGLE_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, fail (LITERAL) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + test(createRespectiveExprInfo(INFO_SINGLE, INFO_SINGLE_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, fail (LITERAL) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + test(createAlternateExprInfo(INFO_SINGLE, INFO_SINGLE_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, fail (LITERAL) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + + test(createExprInfo(false, INFO_SINGLE, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, fail (LITERAL) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), fail (NOT_SINGLE) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + }); + test(createExprInfo(true, INFO_SINGLE, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, fail (LITERAL) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + test(createRespectiveExprInfo(INFO_SINGLE, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, fail (LITERAL) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + test(createAlternateExprInfo(INFO_SINGLE, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, fail (LITERAL) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), fail (NOT_SINGLE) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + }); + + test(createExprInfo(false, INFO_PLURAL, INFO_SINGLE_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // fail (NOT_SINGLE), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + test(createExprInfo(true, INFO_PLURAL, INFO_SINGLE_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, fail (LITERAL) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + test(createRespectiveExprInfo(INFO_PLURAL, INFO_SINGLE_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, fail (LITERAL) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + test(createAlternateExprInfo(INFO_PLURAL, INFO_SINGLE_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // fail (NOT_SINGLE), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + + test(createExprInfo(false, INFO_PLURAL, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // fail (NOT_SINGLE), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), fail (NOT_SINGLE) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + }); + test(createExprInfo(true, INFO_PLURAL, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, fail (LITERAL) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + test(createRespectiveExprInfo(INFO_PLURAL, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, fail (LITERAL) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), pass + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + test(createAlternateExprInfo(INFO_PLURAL, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // fail (NOT_SINGLE), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), fail (NOT_SINGLE) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + }); + + test(createExprInfo(false, INFO_SINGLE_LITERAL, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // fail (LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 2; // pass, fail (NOT_SINGLE) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + test(createExprInfo(true, INFO_SINGLE_LITERAL, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // fail (LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 2; // pass, pass + Assert.assertEquals(2, getDefaultExpressions(exprInfo, string).size()); + }); + test(createRespectiveExprInfo(INFO_SINGLE_LITERAL, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // fail (LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 2; // pass, pass + Assert.assertEquals(2, getDefaultExpressions(exprInfo, string).size()); + }); + test(createAlternateExprInfo(INFO_SINGLE_LITERAL, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // fail (LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 2; // pass, fail (NOT_SINGLE) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + } + + @Test + public void testAllInfos() { + test(createExprInfo(false, INFO_SINGLE, INFO_PLURAL, INFO_SINGLE_LITERAL, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (NOT_LITERAL), fail (LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, fail (NOT_SINGLE), fail (LITERAL), fail (LITERAL) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), fail (NOT_LITERAL), pass, fail (NOT_SINGLE) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + test(createExprInfo(true, INFO_SINGLE, INFO_PLURAL, INFO_SINGLE_LITERAL, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (NOT_LITERAL), fail (LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, pass, fail (LITERAL), fail (LITERAL) + Assert.assertEquals(2, getDefaultExpressions(exprInfo, string).size()); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), fail (NOT_LITERAL), pass, pass + Assert.assertEquals(2, getDefaultExpressions(exprInfo, string).size()); + }); + test(createRespectiveExprInfo(INFO_SINGLE, INFO_PLURAL, INFO_SINGLE_LITERAL, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (NOT_LITERAL), fail (LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, pass, fail (LITERAL), fail (LITERAL) + Assert.assertEquals(2, getDefaultExpressions(exprInfo, string).size()); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), fail (NOT_LITERAL), pass, pass + Assert.assertEquals(2, getDefaultExpressions(exprInfo, string).size()); + }); + test(createAlternateExprInfo(INFO_SINGLE, INFO_PLURAL, INFO_SINGLE_LITERAL, INFO_PLURAL_LITERAL), (exprInfo, string) -> { + exprInfo.flagMask = 0; // fail (NOT_LITERAL), fail (NOT_LITERAL), fail (LITERAL), fail (LITERAL) + Assert.assertThrows(SkriptAPIException.class, () -> getDefaultExpressions(exprInfo, string)); + exprInfo.flagMask = 1; // pass, fail (NOT_SINGLE), fail (LITERAL), fail (LITERAL) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + exprInfo.flagMask = 2; // fail (NOT_LITERAL), fail (NOT_LITERAL), pass, fail (NOT_SINGLE) + Assert.assertEquals(1, getDefaultExpressions(exprInfo, string).size()); + }); + } + +} From eed9e4f87b75ff512f003c86d3ea7dd96e35a006 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Fri, 1 Aug 2025 12:24:57 -0700 Subject: [PATCH 18/20] Fix ExprLoopValue matching too loosely due to canReturn() (#8090) --- .../skript/expressions/ExprLoopValue.java | 39 ++++++++++--------- .../skript/tests/regressions/8090-loop of.sk | 10 +++++ 2 files changed, 31 insertions(+), 18 deletions(-) create mode 100644 src/test/skript/tests/regressions/8090-loop of.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java b/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java index 73da7828dfa..cc313f63243 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java +++ b/src/main/java/ch/njol/skript/expressions/ExprLoopValue.java @@ -95,47 +95,50 @@ enum LoopState { public boolean init(Expression[] vars, int matchedPattern, Kleenean isDelayed, ParseResult parser) { selectedState = loopStates[matchedPattern]; name = parser.expr; - String s = "" + parser.regexes.get(0).group(); - int i = -1; - Matcher m = LOOP_PATTERN.matcher(s); + String loopOf = parser.regexes.get(0).group(); + int expectedDepth = -1; + Matcher m = LOOP_PATTERN.matcher(loopOf); if (m.matches()) { - s = "" + m.group(1); - i = Utils.parseInt("" + m.group(2)); + loopOf = m.group(1); + expectedDepth = Utils.parseInt(m.group(2)); } - if ("counter".equalsIgnoreCase(s) || "iteration".equalsIgnoreCase(s)) // ExprLoopIteration - in case of classinfo conflicts + if ("counter".equalsIgnoreCase(loopOf) || "iteration".equalsIgnoreCase(loopOf)) // ExprLoopIteration - in case of classinfo conflicts return false; - Class c = Classes.getClassFromUserInput(s); - int j = 1; + Class expectedClass = Classes.getClassFromUserInput(loopOf); + int candidateDepth = 1; SecLoop loop = null; - for (SecLoop l : getParser().getCurrentSections(SecLoop.class)) { - if ((c != null && l.getLoopedExpression().canReturn(c)) || "value".equalsIgnoreCase(s) || l.getLoopedExpression().isLoopOf(s)) { - if (j < i) { - j++; + for (SecLoop candidate : getParser().getCurrentSections(SecLoop.class)) { + if ((expectedClass != null && expectedClass.isAssignableFrom(candidate.getLoopedExpression().getReturnType())) + || "value".equalsIgnoreCase(loopOf) + || candidate.getLoopedExpression().isLoopOf(loopOf) + ) { + if (candidateDepth < expectedDepth) { + candidateDepth++; continue; } if (loop != null) { - Skript.error("There are multiple loops that match loop-" + s + ". Use loop-" + s + "-1/2/3/etc. to specify which loop's value you want."); + Skript.error("There are multiple loops that match loop-" + loopOf + ". Use loop-" + loopOf + "-1/2/3/etc. to specify which loop's value you want."); return false; } - loop = l; - if (j == i) + loop = candidate; + if (candidateDepth == expectedDepth) break; } } if (loop == null) { - Skript.error("There's no loop that matches 'loop-" + s + "'"); + Skript.error("There's no loop that matches 'loop-" + loopOf + "'"); return false; } if (selectedState == LoopState.NEXT && !loop.supportsPeeking()) { - Skript.error("The expression '" + loop.getExpression().toString() + "' does not allow the usage of 'next loop-" + s + "'."); + Skript.error("The expression '" + loop.getExpression().toString() + "' does not allow the usage of 'next loop-" + loopOf + "'."); return false; } if (loop.isKeyedLoop()) { isKeyedLoop = true; - if (((KeyProviderExpression) loop.getLoopedExpression()).isIndexLoop(s)) + if (((KeyProviderExpression) loop.getLoopedExpression()).isIndexLoop(loopOf)) isIndex = true; } this.loop = loop; diff --git a/src/test/skript/tests/regressions/8090-loop of.sk b/src/test/skript/tests/regressions/8090-loop of.sk new file mode 100644 index 00000000000..3f64922c43d --- /dev/null +++ b/src/test/skript/tests/regressions/8090-loop of.sk @@ -0,0 +1,10 @@ +parse: + results: {8090::*} + code: + on join: + loop all players: + loop {_l::*}: + broadcast loop-player + +test "loose loop of matching": + assert {8090::*} is not set with "loop-player within a player loop and var loop should not error." From b56720fd1877bd20c497bd8bdbe3f385c6fe1d93 Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Fri, 1 Aug 2025 21:37:38 +0200 Subject: [PATCH 19/20] Fix function calling and improve error with single list params (#8096) --- .../skript/lang/function/FunctionReference.java | 2 ++ .../skript/lang/function/FunctionRegistry.java | 3 ++- .../skript/tests/misc/function overloading.sk | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java index e31ffdfb707..a8446966e9c 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java @@ -7,6 +7,7 @@ import ch.njol.skript.lang.*; import ch.njol.skript.lang.function.FunctionRegistry.Retrieval; import ch.njol.skript.lang.function.FunctionRegistry.RetrievalResult; +import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; @@ -323,6 +324,7 @@ private Function getRegisteredFunction() { } Retrieval> attempt = FunctionRegistry.getRegistry().getFunction(script, functionName, parameterTypes); + if (attempt.result() == RetrievalResult.EXACT) { return attempt.retrieved(); } diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java b/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java index c7619fed68f..32d8f9409b1 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java @@ -475,7 +475,8 @@ private Retrieval> getSignature(@NotNull NamespaceIdentifier namesp } } - return Set.of(candidate); + candidates.add(candidate); + continue; } // if argument counts are not possible, skip diff --git a/src/test/skript/tests/misc/function overloading.sk b/src/test/skript/tests/misc/function overloading.sk index 2e47b7d8621..38b344d8dc0 100644 --- a/src/test/skript/tests/misc/function overloading.sk +++ b/src/test/skript/tests/misc/function overloading.sk @@ -85,3 +85,19 @@ parse: test "function overloading with supertype": assert {FunctionOverloadingSupertype1::parse::*} is not set assert {FunctionOverloadingSupertype2::parse::*} is not set + +function overloading_listarg(x: int, y: int) :: int: + return 1 + +function overloading_listarg(x: int, y: int, z: int) :: int: + return 2 + +function overloading_listarg(x: ints) :: int: + return 3 + +test "function overloading with single param lists": + assert overloading_listarg(1) = 3 + assert overloading_listarg(1, 2) = 1 + assert overloading_listarg(1, 2, 3) = 2 + assert overloading_listarg(1, 2, 3, 4) = 3 + assert overloading_listarg(1, 2, 3, 4, 5) = 3 From 3b37ed9291891496011e92c2e2c6724e3f432e7e Mon Sep 17 00:00:00 2001 From: Patrick Miller Date: Fri, 1 Aug 2025 15:44:44 -0400 Subject: [PATCH 20/20] Prepare For Release (2.12.1) --- gradle.properties | 2 +- src/main/java/ch/njol/skript/effects/EffEquip.java | 2 +- src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 669a8aae315..55586f5bc20 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.12.0 +version=2.12.1 jarName=Skript.jar testEnv=java21/paper-1.21.8 testEnvJavaVersion=21 diff --git a/src/main/java/ch/njol/skript/effects/EffEquip.java b/src/main/java/ch/njol/skript/effects/EffEquip.java index 5ab958dbc58..06954159076 100644 --- a/src/main/java/ch/njol/skript/effects/EffEquip.java +++ b/src/main/java/ch/njol/skript/effects/EffEquip.java @@ -45,7 +45,7 @@ }) @Since({ "1.0, 2.7 (multiple entities, unequip), 2.10 (wolves)", - "INSERT VERSION (happy ghasts)" + "2.12.1 (happy ghasts)" }) public class EffEquip extends Effect { diff --git a/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java b/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java index 22be9847ad6..8b1a9eb7409 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java +++ b/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java @@ -46,7 +46,7 @@ @Keywords("armor") @Since({ "1.0, 2.8.0 (armor), 2.10 (body armor), 2.12 (saddle)", - "INSERT VERSION (happy ghast)" + "2.12.1 (happy ghast)" }) public class ExprArmorSlot extends PropertyExpression {