diff --git a/gradle.properties b/gradle.properties index f6647e60f22..8008d0748ff 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.9.4 +version=2.9.5 jarName=Skript.jar testEnv=java21/paper-1.21.3 testEnvJavaVersion=21 diff --git a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java index a74a604664d..2af432ce6d3 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java @@ -21,6 +21,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.util.slot.Slot; +import com.destroystokyo.paper.profile.PlayerProfile; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.OfflinePlayer; @@ -49,6 +50,8 @@ public class ItemUtils { // Introduced in Paper 1.21 public static final boolean HAS_RESET = Skript.methodExists(Damageable.class, "resetDamage"); public static final boolean CAN_CREATE_PLAYER_PROFILE = Skript.methodExists(Bukkit.class, "createPlayerProfile", UUID.class, String.class); + // paper does not do texture lookups by default + public static final boolean REQUIRES_TEXTURE_LOOKUP = Skript.classExists("com.destroystokyo.paper.profile.PlayerProfile") && Skript.isRunningMinecraft(1, 19, 4); /** * Gets damage/durability of an item, or 0 if it does not have damage. @@ -165,7 +168,12 @@ public static void setHeadOwner(ItemType skull, OfflinePlayer player) { SkullMeta skullMeta = (SkullMeta) meta; - if (player.getName() != null) { + if (REQUIRES_TEXTURE_LOOKUP) { + PlayerProfile profile = player.getPlayerProfile(); + if (!profile.hasTextures()) + profile.complete(true); // BLOCKING MOJANG API CALL + skullMeta.setPlayerProfile(profile); + } else if (player.getName() != null) { skullMeta.setOwningPlayer(player); } else if (CAN_CREATE_PLAYER_PROFILE) { //noinspection deprecation @@ -304,6 +312,16 @@ public static boolean isAir(Material type) { // cherry if (Skript.isRunningMinecraft(1, 19, 4)) TREE_TO_SAPLING_MAP.put(TreeType.CHERRY, Material.CHERRY_SAPLING); + + // mega pine (2x2 spruce tree with minimal leaves at top) + if (Skript.isRunningMinecraft(1, 20, 5)) + TREE_TO_SAPLING_MAP.put(TreeType.MEGA_PINE, Material.SPRUCE_SAPLING); + + // pale oak + if (Skript.isRunningMinecraft(1, 21, 3)) { + TREE_TO_SAPLING_MAP.put(TreeType.PALE_OAK, Material.PALE_OAK_SAPLING); + TREE_TO_SAPLING_MAP.put(TreeType.PALE_OAK_CREAKING, Material.PALE_OAK_SAPLING); + } } public static Material getTreeSapling(TreeType treeType) { diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java index 1588d31ece8..48bf82939fd 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java @@ -32,26 +32,53 @@ public class DefaultOperations { static { // Number - Number Arithmetics.registerOperation(Operator.ADDITION, Number.class, (left, right) -> { - if (Utils.isInteger(left, right)) - return left.longValue() + right.longValue(); + if (Utils.isInteger(left, right)) { + long result = left.longValue() + right.longValue(); + // catches overflow, from Math.addExact(long, long) + if (((left.longValue() ^ result) & (right.longValue() ^ result)) >= 0) + return result; + } return left.doubleValue() + right.doubleValue(); }); Arithmetics.registerOperation(Operator.SUBTRACTION, Number.class, (left, right) -> { - if (Utils.isInteger(left, right)) - return left.longValue() - right.longValue(); + if (Utils.isInteger(left, right)) { + long result = left.longValue() - right.longValue(); + // catches overflow, from Math.addExact(long, long) + if (((left.longValue() ^ result) & (right.longValue() ^ result)) >= 0) + return result; + } return left.doubleValue() - right.doubleValue(); }); Arithmetics.registerOperation(Operator.MULTIPLICATION, Number.class, (left, right) -> { - if (Utils.isInteger(left, right)) - return left.longValue() * right.longValue(); - return left.doubleValue() * right.doubleValue(); + if (!Utils.isInteger(left, right)) + return left.doubleValue() * right.doubleValue(); + + // catch overflow, from Math.multiplyExact(long, long) + long longLeft = left.longValue(); + long longRight = right.longValue(); + long ax = Math.abs(longLeft); + long ay = Math.abs(longRight); + + long result = left.longValue() * right.longValue(); + + if (((ax | ay) >>> 31 != 0)) { + // Some bits greater than 2^31 that might cause overflow + // Check the result using the divide operator + // and check for the special case of Long.MIN_VALUE * -1 + if (((longRight != 0) && (result / longRight != longLeft)) || + (longLeft == Long.MIN_VALUE && longRight == -1)) { + return left.doubleValue() * right.doubleValue(); + } + } + return result; }); Arithmetics.registerOperation(Operator.DIVISION, Number.class, (left, right) -> left.doubleValue() / right.doubleValue()); Arithmetics.registerOperation(Operator.EXPONENTIATION, Number.class, (left, right) -> Math.pow(left.doubleValue(), right.doubleValue())); Arithmetics.registerDifference(Number.class, (left, right) -> { - if (Utils.isInteger(left, right)) - return Math.abs(left.longValue() - right.longValue()); - return Math.abs(left.doubleValue() - right.doubleValue()); + double result = Math.abs(left.doubleValue() - right.doubleValue()); + if (Utils.isInteger(left, right) && result < Long.MAX_VALUE && result > Long.MIN_VALUE) + return (long) result; + return result; }); Arithmetics.registerDefaultValue(Number.class, () -> 0L); diff --git a/src/main/java/ch/njol/skript/effects/EffEnchant.java b/src/main/java/ch/njol/skript/effects/EffEnchant.java index 016b8c66be8..0e83dfcfefb 100644 --- a/src/main/java/ch/njol/skript/effects/EffEnchant.java +++ b/src/main/java/ch/njol/skript/effects/EffEnchant.java @@ -18,11 +18,6 @@ */ package ch.njol.skript.effects; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.event.Event; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Nullable; - import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.classes.Changer.ChangeMode; @@ -36,6 +31,11 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.EnchantmentType; import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Function; /** * @author Peter Güttinger @@ -72,29 +72,26 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override protected void execute(Event event) { - ItemType[] items = this.items.getArray(event); - if (items.length == 0) // short circuit - return; + Function changeFunction; if (enchantments != null) { EnchantmentType[] types = enchantments.getArray(event); if (types.length == 0) return; - for (ItemType item : items) { - for (EnchantmentType type : types) { - Enchantment enchantment = type.getType(); - assert enchantment != null; - item.addEnchantments(new EnchantmentType(enchantment, type.getLevel())); - } - } + changeFunction = item -> { + item.addEnchantments(types); + return item; + }; } else { - for (ItemType item : items) { + changeFunction = item -> { item.clearEnchantments(); - } + return item; + }; } - this.items.change(event, items.clone(), ChangeMode.SET); + + this.items.changeInPlace(event, changeFunction); } - + @Override public String toString(@Nullable Event event, boolean debug) { return enchantments == null ? "disenchant " + items.toString(event, debug) : "enchant " + items.toString(event, debug) + " with " + enchantments; diff --git a/src/main/java/ch/njol/skript/effects/EffReplace.java b/src/main/java/ch/njol/skript/effects/EffReplace.java index ef2faf44282..84d6dd47bb3 100644 --- a/src/main/java/ch/njol/skript/effects/EffReplace.java +++ b/src/main/java/ch/njol/skript/effects/EffReplace.java @@ -37,9 +37,11 @@ import org.bukkit.event.Event; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Map; +import java.util.function.Function; import java.util.regex.Matcher; @Name("Replace") @@ -107,20 +109,9 @@ private void replace(Event event, Object[] needles, Expression haystackExpr) if (replacement == null || haystack == null || haystack.length == 0 || needles == null || needles.length == 0) return; if (replaceString) { - if (replaceFirst) { - for (int x = 0; x < haystack.length; x++) - for (Object n : needles) { - assert n != null; - haystack[x] = StringUtils.replaceFirst((String)haystack[x], (String)n, Matcher.quoteReplacement((String)replacement), caseSensitive); - } - } else { - for (int x = 0; x < haystack.length; x++) - for (Object n : needles) { - assert n != null; - haystack[x] = StringUtils.replace((String) haystack[x], (String) n, (String) replacement, caseSensitive); - } - } - haystackExpr.change(event, haystack, ChangeMode.SET); + Function replaceFunction = getReplaceFunction(needles, (String) replacement); + //noinspection unchecked + ((Expression) haystackExpr).changeInPlace(event, replaceFunction); } else { for (Inventory inv : (Inventory[]) haystack) for (ItemType needle : (ItemType[]) needles) @@ -138,14 +129,36 @@ private void replace(Event event, Object[] needles, Expression haystackExpr) } } } - + + private @NotNull Function getReplaceFunction(Object[] needles, String replacement) { + Function replaceFunction; + if (replaceFirst) { + replaceFunction = haystackString -> { + for (Object needle : needles) { + assert needle != null; + haystackString = StringUtils.replaceFirst(haystackString, (String) needle, Matcher.quoteReplacement(replacement), caseSensitive); + } + return haystackString; + }; + } else { + replaceFunction = haystackString -> { + for (Object needle : needles) { + assert needle != null; + haystackString = StringUtils.replace(haystackString, (String) needle, replacement, caseSensitive); + } + return haystackString; + }; + } + return replaceFunction; + } + @Override public String toString(@Nullable Event event, boolean debug) { SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); builder.append("replace"); if (replaceFirst) - builder.append("the first"); + builder.append("first"); builder.append(needles, "in", haystack, "with", replacement); if (caseSensitive) builder.append("with case sensitivity"); diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index 65d2c4eb4d4..5bf1ad37ad3 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -343,13 +343,10 @@ public class SimpleEvents { .requiredPlugins("Minecraft 1.14+ (event-entity support)") .since("1.0, 2.5.3 (event-entity support)"); Skript.registerEvent("Projectile Hit", SimpleEvent.class, ProjectileHitEvent.class, "projectile hit") - .description("Called when a projectile hits an entity or a block.", - "Use the damage event with a check for a projectile " + - "to be able to use the entity that got hit in the case when the projectile hit a living entity.", - "A damage event will even be fired if the damage is 0, e.g. when throwing snowballs at non-nether mobs.") + .description("Called when a projectile hits an entity or a block.") .examples("on projectile hit:", - "\tevent-projectile is arrow", - "\tdelete event-projectile") + "\tif victim's health <= 3:", + "\t\tdelete event-projectile") .since("1.0"); if(Skript.classExists("com.destroystokyo.paper.event.entity.ProjectileCollideEvent")) diff --git a/src/main/java/ch/njol/skript/expressions/ExprBlocks.java b/src/main/java/ch/njol/skript/expressions/ExprBlocks.java index a6cae9350a3..fbc9683db1e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBlocks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBlocks.java @@ -56,9 +56,11 @@ @Since("1.0, 2.5.1 (within/cuboid/chunk)") public class ExprBlocks extends SimpleExpression { + private static final boolean SUPPORTS_WORLD_LOADED = Skript.methodExists(Location.class, "isWorldLoaded"); + static { Skript.registerExpression(ExprBlocks.class, Block.class, ExpressionType.COMBINED, - "[(all [[of] the]|the)] blocks %direction% [%locations%]", // TODO doesn't loop all blocks? + "[(all [[of] the]|the)] blocks %direction% [%locations%]", "[(all [[of] the]|the)] blocks from %location% [on] %direction%", "[(all [[of] the]|the)] blocks from %location% to %location%", "[(all [[of] the]|the)] blocks between %location% and %location%", @@ -116,7 +118,11 @@ protected Block[] get(Event event) { return from.stream(event) .filter(Location.class::isInstance) .map(Location.class::cast) - .filter(Location::isWorldLoaded) + .filter(location -> { + if (SUPPORTS_WORLD_LOADED) + return location.isWorldLoaded(); + return location.getChunk().isLoaded(); + }) .map(direction::getRelative) .map(Location::getBlock) .toArray(Block[]::new); diff --git a/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java b/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java index 8b9274c70cd..75ad70a0ddd 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java +++ b/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java @@ -1,13 +1,15 @@ package ch.njol.skript.expressions; import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; import ch.njol.skript.SkriptConfig; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SyntaxStringBuilder; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; import ch.njol.skript.Skript; -import ch.njol.skript.SkriptConfig; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -18,11 +20,6 @@ import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; import ch.njol.util.StringUtils; -import org.bukkit.event.Event; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnknownNullability; - -import java.util.regex.Pattern; @Name("Join & Split") @Description("Joins several texts with a common delimiter (e.g. \", \"), or splits a text into multiple texts at a given delimiter.") @@ -47,41 +44,51 @@ public class ExprJoinSplit extends SimpleExpression { private boolean caseSensitivity; private boolean removeTrailing; - @UnknownNullability private Expression strings; - @UnknownNullability - private Expression delimiter; + private @Nullable Expression delimiter; + + private @Nullable Pattern pattern; @Override - @SuppressWarnings("unchecked") public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { join = matchedPattern == 0; regex = matchedPattern >= 3; caseSensitivity = SkriptConfig.caseSensitive.value() || parseResult.hasTag("case"); removeTrailing = parseResult.hasTag("trailing"); - + //noinspection unchecked strings = (Expression) exprs[0]; + //noinspection unchecked delimiter = (Expression) exprs[1]; + if (!join && delimiter instanceof Literal literal) { + String stringPattern = literal.getSingle(); + try { + this.pattern = compilePattern(stringPattern); + } catch (PatternSyntaxException e) { + Skript.error("'" + stringPattern + "' is not a valid regular expression"); + return false; + } + } return true; } @Override - @Nullable - protected String[] get(Event event) { + protected String @Nullable [] get(Event event) { String[] strings = this.strings.getArray(event); String delimiter = this.delimiter != null ? this.delimiter.getSingle(event) : ""; if (strings.length == 0 || delimiter == null) return new String[0]; - if (join) { - return new String[]{StringUtils.join(strings, delimiter)}; - } else { - if (!regex) { - delimiter = (caseSensitivity ? "" : "(?i)") + Pattern.quote(delimiter); - } - - return strings[0].split(delimiter, removeTrailing ? 0 : -1); + if (join) + return new String[] {StringUtils.join(strings, delimiter)}; + + try { + Pattern pattern = this.pattern; + if (pattern == null) + pattern = compilePattern(delimiter); + return pattern.split(strings[0], removeTrailing ? 0 : -1); + } catch (PatternSyntaxException e) { + return new String[0]; } } @@ -97,14 +104,27 @@ public Class getReturnType() { @Override public String toString(@Nullable Event event, boolean debug) { - if (join) - return "join " + strings.toString(event, debug) + - (delimiter != null ? " with " + delimiter.toString(event, debug) : ""); - return (regex ? "regex " : "") + - "split " + strings.toString(event, debug) + - (delimiter != null ? " at " + delimiter.toString(event, debug) : "") + - (regex ? "" : "(case sensitive: " + caseSensitivity + ")") + - (removeTrailing ? " without trailing string" : ""); + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); + if (join) { + builder.append("join", strings); + if (delimiter != null) + builder.append("with", delimiter); + return builder.toString(); + } + + assert delimiter != null; + if (regex) + builder.append("regex"); + builder.append("split", strings, "at", delimiter); + if (removeTrailing) + builder.append("without trailing text"); + if (!regex) + builder.append("(case sensitive:", caseSensitivity + ")"); + return builder.toString(); + } + + private Pattern compilePattern(String delimiter) { + return Pattern.compile(regex ? delimiter : (caseSensitivity ? "" : "(?i)") + Pattern.quote(delimiter)); } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java b/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java index 6e0af7d63cc..62e5696e098 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorLength.java @@ -18,18 +18,20 @@ */ package ch.njol.skript.expressions; -import ch.njol.util.VectorMath; -import org.bukkit.event.Event; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Nullable; - import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.util.VectorMath; import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Function; @Name("Vectors - Length") @Description("Gets or sets the length of a vector.") @@ -61,39 +63,49 @@ public Class[] acceptChange(ChangeMode mode) { } @Override - public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { assert delta != null; - final Vector[] vectors = getExpr().getArray(event); double deltaLength = ((Number) delta[0]).doubleValue(); + + Function changeFunction; switch (mode) { case REMOVE: deltaLength = -deltaLength; //$FALL-THROUGH$ case ADD: - for (Vector vector : vectors) { - if (VectorMath.isZero(vector) || (deltaLength < 0 && vector.lengthSquared() < deltaLength * deltaLength)) { + final double finalDeltaLength = deltaLength; + final double finalDeltaLengthSquared = deltaLength * deltaLength; + changeFunction = vector -> { + if (VectorMath.isZero(vector) || (finalDeltaLength < 0 && vector.lengthSquared() < finalDeltaLengthSquared)) { vector.zero(); } else { - double newLength = deltaLength + vector.length(); + double newLength = finalDeltaLength + vector.length(); if (!vector.isNormalized()) vector.normalize(); vector.multiply(newLength); } - } + return vector; + }; break; case SET: - for (Vector vector : vectors) { - if (deltaLength < 0 || VectorMath.isZero(vector)) { + final double finalDeltaLength1 = deltaLength; + changeFunction = vector -> { + if (finalDeltaLength1 < 0 || VectorMath.isZero(vector)) { vector.zero(); } else { if (!vector.isNormalized()) vector.normalize(); - vector.multiply(deltaLength); + vector.multiply(finalDeltaLength1); } - } + return vector; + }; break; + default: + return; } - getExpr().change(event, vectors, ChangeMode.SET); + + //noinspection unchecked,DataFlowIssue + ((Expression) getExpr()).changeInPlace(event, changeFunction); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprXYZComponent.java b/src/main/java/ch/njol/skript/expressions/ExprXYZComponent.java index a6db3c7e92e..7ef773bada0 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprXYZComponent.java +++ b/src/main/java/ch/njol/skript/expressions/ExprXYZComponent.java @@ -3,6 +3,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.classes.Changer.ChangerUtils; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; @@ -89,9 +90,9 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye if ((mode == ChangeMode.ADD || mode == ChangeMode.REMOVE || mode == ChangeMode.SET)) { boolean acceptsChange; if (IS_RUNNING_1194) { - acceptsChange = Changer.ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, Vector.class, Quaternionf.class); + acceptsChange = ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, Vector.class, Quaternionf.class); } else { - acceptsChange = Changer.ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, Vector.class); + acceptsChange = ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, Vector.class); } if (acceptsChange) return CollectionUtils.array(Number.class); @@ -102,35 +103,20 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { assert delta != null; // reset/delete not supported - Object[] objects = getExpr().getArray(event); - double value = ((Number) delta[0]).doubleValue(); + final double value = ((Number) delta[0]).doubleValue(); - boolean hasVectors = false; - boolean hasQuaternions = false; - boolean hasInvalidInput = false; + // for covering the edge cases such as an expression that returns a Vector but can only be set to a Quaternions + boolean acceptsVectors = ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, Vector.class); + boolean acceptsQuaternions = ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, Quaternionf.class); - for (Object object : objects) { - if (object instanceof Vector vector) { + getExpr().changeInPlace(event, object -> { + if (acceptsVectors && object instanceof Vector vector) { changeVector(vector, axis, value, mode); - hasVectors = true; - } else if (object instanceof Quaternionf quaternion) { + } else if (acceptsQuaternions && object instanceof Quaternionf quaternion) { changeQuaternion(quaternion, axis, (float) value, mode); - hasQuaternions = true; - } else { - hasInvalidInput = true; } - } - - // don't SET the expression if there were invalid inputs - if (hasInvalidInput) - return; - - // covers the edge case where an expression can be set to Vector but returns Quaternions, or similar. - if (hasVectors && !Changer.ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, Vector.class)) - return; - if (hasQuaternions && !Changer.ChangerUtils.acceptsChange(getExpr(), ChangeMode.SET, Quaternionf.class)) - return; - getExpr().change(event, objects, ChangeMode.SET); + return object; + }); } /** diff --git a/src/main/java/ch/njol/skript/lang/Expression.java b/src/main/java/ch/njol/skript/lang/Expression.java index 2c1e78d6b87..8dea5655be5 100644 --- a/src/main/java/ch/njol/skript/lang/Expression.java +++ b/src/main/java/ch/njol/skript/lang/Expression.java @@ -35,11 +35,14 @@ import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.converter.Converter; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Spliterators; +import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -341,6 +344,54 @@ default Map[]> getAcceptedChangeModes() { */ void change(Event event, Object @Nullable [] delta, ChangeMode mode); + /** + * Changes the contents of an expression using the given {@link Function}. + * Intended for changes that change properties of the values of the expression, rather than completely + * changing the expression. For example, {@code set vector length of {_v} to 1}, rather than + * {@code set {_v} to vector(0,1,0)}. + *
+ * This is a 1 to 1 transformation and should not add or remove elements. + * For {@link Variable}s, this will retain indices. For non-{@link Variable}s, it will + * evaluate {@link #getArray(Event)}, apply the change function on each, and call + * {@link #change(Event, Object[], ChangeMode)} with the modified values and {@link ChangeMode#SET}. + * + * @param event The event to use for local variables and evaluation + * @param changeFunction A 1-to-1 function that transforms a single input to a single output. + * @param The output type of the change function. Must be a type returned + * by {{@link #acceptChange(ChangeMode)}} for {@link ChangeMode#SET}. + */ + default void changeInPlace(Event event, Function changeFunction) { + changeInPlace(event, changeFunction, false); + } + + /** + * Changes the contents of an expression using the given {@link Function}. + * Intended for changes that change properties of the values of the expression, rather than completely + * changing the expression. For example, {@code set vector length of {_v} to 1}, rather than + * {@code set {_v} to vector(0,1,0)}. + *
+ * This is a 1 to 1 transformation and should not add or remove elements. + * For {@link Variable}s, this will retain indices. For non-{@link Variable}s, it will + * evaluate the expression, apply the change function on each value, and call + * {@link #change(Event, Object[], ChangeMode)} with the modified values and {@link ChangeMode#SET}. + * + * @param event The event to use for local variables and evaluation + * @param changeFunction A 1-to-1 function that transforms a single input to a single output. + * @param getAll Whether to evaluate with {@link #getAll(Event)} or {@link #getArray(Event)}. + * @param The output type of the change function. Must be a type returned + * by {{@link #acceptChange(ChangeMode)}} for {@link ChangeMode#SET}. + */ + default void changeInPlace(Event event, Function changeFunction, boolean getAll) { + T[] values = getAll ? getAll(event) : getArray(event); + if (values.length == 0) + return; + List newValues = new ArrayList<>(); + for (T value : values) { + newValues.add(changeFunction.apply(value)); + } + change(event, newValues.toArray(), ChangeMode.SET); + } + /** * This method is called before this expression is set to another one. * The return value is what will be used for change. You can use modified diff --git a/src/main/java/ch/njol/skript/lang/Variable.java b/src/main/java/ch/njol/skript/lang/Variable.java index 5ce5cc4a1c0..4d499b814c1 100644 --- a/src/main/java/ch/njol/skript/lang/Variable.java +++ b/src/main/java/ch/njol/skript/lang/Variable.java @@ -64,6 +64,7 @@ import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.TreeMap; +import java.util.function.Function; public class Variable implements Expression { @@ -614,6 +615,25 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) thro } } + @Override + public void changeInPlace(Event event, Function changeFunction) { + if (!list) { + T value = getSingle(event); + if (value == null) + return; + set(event, changeFunction.apply(value)); + return; + } + variablesIterator(event).forEachRemaining(pair -> { + String index = pair.getKey(); + T value = Converters.convert(pair.getValue(), types); + if (value == null) + return; + Object newValue = changeFunction.apply(value); + setIndex(event, index, newValue); + }); + } + @Override public @Nullable T getSingle(Event event) { if (list) diff --git a/src/main/java/ch/njol/skript/util/BlockLineIterator.java b/src/main/java/ch/njol/skript/util/BlockLineIterator.java index e67fbd02b0f..201f031cc4d 100644 --- a/src/main/java/ch/njol/skript/util/BlockLineIterator.java +++ b/src/main/java/ch/njol/skript/util/BlockLineIterator.java @@ -1,31 +1,13 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.util; +import ch.njol.skript.bukkitutil.WorldUtils; +import ch.njol.util.Math2; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.util.BlockIterator; import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; -import ch.njol.skript.bukkitutil.WorldUtils; -import ch.njol.util.Math2; import ch.njol.util.NullableChecker; import ch.njol.util.coll.iterator.StoppableIterator; @@ -37,8 +19,10 @@ public class BlockLineIterator extends StoppableIterator { * @throws IllegalStateException randomly (Bukkit bug) */ public BlockLineIterator(Block start, Block end) throws IllegalStateException { - super(new BlockIterator(start.getWorld(), fitInWorld(start.getLocation().add(0.5, 0.5, 0.5), end.getLocation().subtract(start.getLocation()).toVector()), - end.equals(start) ? new Vector(1, 0, 0) : end.getLocation().subtract(start.getLocation()).toVector(), 0, 0), // should prevent an error if start = end + super(new BlockIterator(start.getWorld(), start.getLocation().toVector(), + end.equals(start) ? new Vector(1, 0, 0) : end.getLocation().subtract(start.getLocation()).toVector(), // should prevent an error if start = end + 0, 0 + ), new NullableChecker() { private final double overshotSq = Math.pow(start.getLocation().distance(end.getLocation()) + 2, 2); @@ -59,7 +43,7 @@ public boolean check(@Nullable Block block) { * @throws IllegalStateException randomly (Bukkit bug) */ public BlockLineIterator(Location start, Vector direction, double distance) throws IllegalStateException { - super(new BlockIterator(start.getWorld(), fitInWorld(start, direction), direction, 0, 0), new NullableChecker() { + super(new BlockIterator(start.getWorld(), start.toVector(), direction, 0, 0), new NullableChecker() { private final double distSq = distance * distance; @Override diff --git a/src/main/java/org/skriptlang/skript/lang/arithmetic/Arithmetics.java b/src/main/java/org/skriptlang/skript/lang/arithmetic/Arithmetics.java index 27a8065e122..10563db0b68 100644 --- a/src/main/java/org/skriptlang/skript/lang/arithmetic/Arithmetics.java +++ b/src/main/java/org/skriptlang/skript/lang/arithmetic/Arithmetics.java @@ -72,7 +72,7 @@ public static void registerOperation(Operator operator, Class leftC getOperations_i(operator).add(new OperationInfo<>(leftClass, rightClass, returnType, operation)); } - private static boolean exactOperationExists(Operator operator, Class leftClass, Class rightClass) { + public static boolean exactOperationExists(Operator operator, Class leftClass, Class rightClass) { for (OperationInfo info : getOperations_i(operator)) { if (info.getLeft() == leftClass && info.getRight() == rightClass) return true; @@ -188,7 +188,7 @@ public static void registerDifference(Class type, Class returnType, differences.put(type, new DifferenceInfo<>(type, returnType, operation)); } - private static boolean exactDifferenceExists(Class type) { + public static boolean exactDifferenceExists(Class type) { return differences.containsKey(type); } diff --git a/src/main/resources/lang/turkish.lang b/src/main/resources/lang/turkish.lang index fc2ffdcaaca..4946c6f5d9e 100644 --- a/src/main/resources/lang/turkish.lang +++ b/src/main/resources/lang/turkish.lang @@ -47,7 +47,7 @@ skript command: test: Skript'in dahili testleri için kullanılır. invalid script: '%s' adlı script scripts klasöründe bulunamadı! - invalid folder: '%s' adlı klasör scripts klasöründe bulunamadı! + invalid folder: '%s' adlı klasör scripts klasöründe bulunamadı! reload: warning line info: Satır %s: (%s)\n error line info: Satır %s: (%s)\n @@ -165,7 +165,7 @@ aliases: unknown variation: %s varyasyonu daha önce tanımlanmadı. missing aliases: Bu Minecraft ID'lerinin herhangi bir alias'ı yoktur: empty alias: Alias'ın tanımlı bir Minecraft ID'si veya tag'i yok. - invalid minecraft id: Minecraft ID %s geçerli değil + invalid minecraft id: Minecraft ID %s geçerli değil useless variation: Varyasyonun Minecraft ID'si veya tag'i yok, bu yüzden gereksiz. invalid tags: Belirtilen tagler geçerli JSON'da tanımlı değil. unexpected section: Section'lara burada izin verilmiyor. diff --git a/src/test/java/org/skriptlang/skript/test/tests/regression/MissingTreeSaplingMapEntriesTest.java b/src/test/java/org/skriptlang/skript/test/tests/regression/MissingTreeSaplingMapEntriesTest.java new file mode 100644 index 00000000000..371c03a48ba --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/regression/MissingTreeSaplingMapEntriesTest.java @@ -0,0 +1,17 @@ +package org.skriptlang.skript.test.tests.regression; + +import ch.njol.skript.bukkitutil.ItemUtils; +import org.bukkit.TreeType; +import org.junit.Assert; +import org.junit.Test; + +public class MissingTreeSaplingMapEntriesTest { + + @Test + public void test() { + for (TreeType type : TreeType.values()) { + Assert.assertNotNull("Tree type " + type + " has no mapped sapling in ItemUtils#getTreeSapling().", ItemUtils.getTreeSapling(type)); + } + } + +} diff --git a/src/test/skript/tests/regressions/7159-regex exceptions not handled.sk b/src/test/skript/tests/regressions/7159-regex exceptions not handled.sk new file mode 100644 index 00000000000..691fe8e0da8 --- /dev/null +++ b/src/test/skript/tests/regressions/7159-regex exceptions not handled.sk @@ -0,0 +1,12 @@ +test "regex exceptions not handled": + parse: + set {_split::*} to regex split "test" at "\b{_name}\b" + assert last parse logs is "'\b{_name}\b' is not a valid regular expression" with "regex split did not error with invalid regex literal" + + set {_pattern} to "\b{_name}\b" + set {_split::*} to regex split "test" at {_pattern} + assert {_split::*} is not set with "regex split returned a value with invalid regex expression" + + assert regex split "apple,banana;cherry" at "[,;]" is "apple", "banana" and "cherry" with "regex split did not split correctly with literal" + set {_pattern} to "[,;]" + assert regex split "apple,banana;cherry" at {_pattern} is "apple", "banana" and "cherry" with "regex split did not split correctly with expression" diff --git a/src/test/skript/tests/regressions/7209-long overflow.sk b/src/test/skript/tests/regressions/7209-long overflow.sk new file mode 100644 index 00000000000..bff56f0f57f --- /dev/null +++ b/src/test/skript/tests/regressions/7209-long overflow.sk @@ -0,0 +1,34 @@ +test "long overflow, addition": + set {_double-const} to 9000000000000000000.0 + set {_long-const} to 9000000000000000000 + + loop 100 times: + set {_double} to {_double} + {_double-const} + set {_long} to {_long} + {_long-const} + assert {_double} is {_long} with "long value did not overflow to a double" + + assert {_long} is 9000000000000000000 * 100.0 with "wrong final value" + +test "long underflow, subtraction": + set {_double-const} to 9000000000000000000.0 + set {_long-const} to 9000000000000000000 + + loop 100 times: + set {_double} to {_double} - {_double-const} + set {_long} to {_long} - {_long-const} + assert {_double} is {_long} with "long value did not overflow to a double" + + assert {_long} is 9000000000000000000 * -100.0 with "wrong final value" + +test "long overflow, multiplication": + set {_double} to 10.0 + set {_long} to 10 + + loop 100 times: + set {_double} to {_double} * 10 + set {_long} to {_long} * 10 + assert {_double} is {_long} with "long value did not overflow to a double" + + # the 6 is due to floating point error + assert {_long} is 100000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000 with "wrong final value" + diff --git a/src/test/skript/tests/regressions/pull-7120-changes overwriting indices.sk b/src/test/skript/tests/regressions/pull-7120-changes overwriting indices.sk new file mode 100644 index 00000000000..47b02990e7a --- /dev/null +++ b/src/test/skript/tests/regressions/pull-7120-changes overwriting indices.sk @@ -0,0 +1,31 @@ +test "EffEnchant overwriting var indices": + set {_test::1} to diamond sword + set {_test::a} to gold sword + set {_test::hello world} to block at spawn of world "world" + enchant {_test::*} with sharpness 1 + assert {_test::*} is diamond sword of sharpness 1, gold sword of sharpness 1, and block at spawn of world "world" with "failed to enchant items" + assert indices of {_test::*} is "1", "a", and "hello world" with "enchanting modified indices" + +test "EffReplace overwriting var indices": + set {_test::1} to "abc" + set {_test::a} to "cde" + set {_test::hello world} to block at spawn of world "world" + replace all "c" in {_test::*} with "z" + assert {_test::*} is "abz", "zde", and block at spawn of world "world" with "failed to replace strings" + assert indices of {_test::*} is "1", "a", and "hello world" with "replacing modified indices" + +test "ExprVectorLength overwriting var indices": + set {_test::1} to vector(0,3,0) + set {_test::a} to vector(0,0,4) + set {_test::hello world} to "abc" + set vector length of {_test::*} to 1 + assert {_test::*} is vector(0,1,0), vector(0,0,1), and "abc" with "failed to change vector length" + assert indices of {_test::*} is "1", "a", and "hello world" with "changing vector length modified indices" + +test "ExprVectorXYZ overwriting var indices": + set {_test::1} to vector(0,3,0) + set {_test::a} to vector(0,0,4) + set {_test::hello world} to "abc" + set x component of {_test::*} to 1 + assert {_test::*} is vector(1,3,0), vector(1,0,4), and "abc" with "failed to change vector x" + assert indices of {_test::*} is "1", "a", and "hello world" with "changing vector x modified indices" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprBlocks.sk b/src/test/skript/tests/syntaxes/expressions/ExprBlocks.sk new file mode 100644 index 00000000000..0b954dc9a0b --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprBlocks.sk @@ -0,0 +1,18 @@ +test "blocks void": + set {_loc} to location(0.5, 320.5, 0.5) + set {_blocks::*} to blocks between {_loc} and ({_loc} ~ vector(10,0,0)) + assert size of {_blocks::*} is 11 with "Blocks between loc and (loc~vector(10,0,0)) is not 11" + assert blocks at {_blocks::*} is void air with "Blocks can be set in the void?" + set blocks at {_blocks::*} to stone + assert blocks at {_blocks::*} is void air with "Blocks can be set in the void?" + +test "blocks vector direction": + set {_loc} to location(0.5, 20.5, 0.5) + set {_blocks::*} to blocks vector(1,0,0) {_loc} + assert size of {_blocks::*} is 100 with "Blocks vector(1,0,0) loc is not 100" + set blocks at {_blocks::*} to stone + assert blocks at {_blocks::*} is stone with "1 or more blocks were not set to stone" + set blocks at {_blocks::*} to air + assert blocks at {_blocks::*} is air with "1 or more blocks were not set to air" + loop {_blocks::*}: + set block at loop-value to loop-value