From 2af552e90b33d2c088b11828b2e4cebf3035b569 Mon Sep 17 00:00:00 2001 From: Fusezion Date: Mon, 6 May 2024 07:25:47 -0400 Subject: [PATCH 01/23] Fix EffHealth - slot durability not accounting correctly (#6644) * Fix slot stack trace * Add a regression test for if this breaks again --- src/main/java/ch/njol/skript/effects/EffHealth.java | 2 +- .../6643-effhealth slot not accounting existing damage | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/test/skript/tests/regressions/6643-effhealth slot not accounting existing damage diff --git a/src/main/java/ch/njol/skript/effects/EffHealth.java b/src/main/java/ch/njol/skript/effects/EffHealth.java index 25715349c19..68c0a6d1839 100644 --- a/src/main/java/ch/njol/skript/effects/EffHealth.java +++ b/src/main/java/ch/njol/skript/effects/EffHealth.java @@ -103,7 +103,7 @@ protected void execute(Event event) { if (this.amount == null) { ItemUtils.setDamage(itemStack, 0); } else { - int damageAmt = (int) Math2.fit(0, (isHealing ? -amount : amount), itemStack.getType().getMaxDurability()); + int damageAmt = (int) Math2.fit(0, (ItemUtils.getDamage(itemStack) + (isHealing ? -amount : amount)), itemStack.getType().getMaxDurability()); ItemUtils.setDamage(itemStack, damageAmt); } diff --git a/src/test/skript/tests/regressions/6643-effhealth slot not accounting existing damage b/src/test/skript/tests/regressions/6643-effhealth slot not accounting existing damage new file mode 100644 index 00000000000..701587e9e41 --- /dev/null +++ b/src/test/skript/tests/regressions/6643-effhealth slot not accounting existing damage @@ -0,0 +1,8 @@ +test "slot damage accountability": + set {_inv} to chest inventory named "slot accountability" + set slot 0 of {_inv} to diamond sword with damage 100 + + repair slot 0 of {_inv} by 10 + assert damage of slot 0 of {_inv} is 90 with "Durability of slot 0, was not updated correctly" + repair slot 0 of {_inv} + assert durability of slot 0 of {_inv} is maximum durability of slot 0 of {_inv} with "Durability of slot 0, was not fully repaired" From e0aed7259224b91d449da5d34f142f2cd156f9d1 Mon Sep 17 00:00:00 2001 From: Shane Bee Date: Wed, 8 May 2024 09:43:08 -0700 Subject: [PATCH 02/23] EffHealth - fix max damage on custom items (#6646) Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../ch/njol/skript/bukkitutil/ItemUtils.java | 26 +++++++++++++++++++ .../ch/njol/skript/effects/EffHealth.java | 4 +-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java index 3e50c7cca8b..31bf99c2d56 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java @@ -34,6 +34,8 @@ */ public class ItemUtils { + public static final boolean HAS_MAX_DAMAGE = Skript.methodExists(Damageable.class, "getMaxDamage"); + /** * Gets damage/durability of an item, or 0 if it does not have damage. * @param itemStack Item. @@ -46,6 +48,18 @@ public static int getDamage(ItemStack itemStack) { return 0; // Non damageable item } + /** Gets the max damage/durability of an item + *

NOTE: Will account for custom damageable items in MC 1.20.5+

+ * @param itemStack Item to grab durability from + * @return Max amount of damage this item can take + */ + public static int getMaxDamage(ItemStack itemStack) { + ItemMeta meta = itemStack.getItemMeta(); + if (HAS_MAX_DAMAGE && meta instanceof Damageable && ((Damageable) meta).hasMaxDamage()) + return ((Damageable) meta).getMaxDamage(); + return itemStack.getType().getMaxDurability(); + } + /** * Sets damage/durability of an item if possible. * @param itemStack Item to modify. @@ -72,6 +86,18 @@ public static int getDamage(ItemType itemType) { return 0; // Non damageable item } + /** Gets the max damage/durability of an item + *

NOTE: Will account for custom damageable items in MC 1.20.5+

+ * @param itemType Item to grab durability from + * @return Max amount of damage this item can take + */ + public static int getMaxDamage(ItemType itemType) { + ItemMeta meta = itemType.getItemMeta(); + if (HAS_MAX_DAMAGE && meta instanceof Damageable && ((Damageable) meta).hasMaxDamage()) + return ((Damageable) meta).getMaxDamage(); + return itemType.getMaterial().getMaxDurability(); + } + /** * Sets damage/durability of an item if possible. * @param itemType Item to modify. diff --git a/src/main/java/ch/njol/skript/effects/EffHealth.java b/src/main/java/ch/njol/skript/effects/EffHealth.java index 68c0a6d1839..2cd392e8810 100644 --- a/src/main/java/ch/njol/skript/effects/EffHealth.java +++ b/src/main/java/ch/njol/skript/effects/EffHealth.java @@ -90,7 +90,7 @@ protected void execute(Event event) { if (this.amount == null) { ItemUtils.setDamage(itemType, 0); } else { - ItemUtils.setDamage(itemType, (int) Math2.fit(0, (ItemUtils.getDamage(itemType) + (isHealing ? -amount : amount)), itemType.getMaterial().getMaxDurability())); + ItemUtils.setDamage(itemType, (int) Math2.fit(0, (ItemUtils.getDamage(itemType) + (isHealing ? -amount : amount)), ItemUtils.getMaxDamage(itemType))); } } else if (obj instanceof Slot) { @@ -103,7 +103,7 @@ protected void execute(Event event) { if (this.amount == null) { ItemUtils.setDamage(itemStack, 0); } else { - int damageAmt = (int) Math2.fit(0, (ItemUtils.getDamage(itemStack) + (isHealing ? -amount : amount)), itemStack.getType().getMaxDurability()); + int damageAmt = (int) Math2.fit(0, (ItemUtils.getDamage(itemStack) + (isHealing ? -amount : amount)), ItemUtils.getMaxDamage(itemStack)); ItemUtils.setDamage(itemStack, damageAmt); } From 524dc18790c8222c8441498c3885f9cbd4d47e1f Mon Sep 17 00:00:00 2001 From: cheeezburga <47320303+cheeezburga@users.noreply.github.com> Date: Thu, 9 May 2024 20:30:39 +1000 Subject: [PATCH 03/23] Adds some fire resistant syntax (#6639) * Adds some fire resistant syntax * Fix property name method * Adds expression and method checks - Added an expression to create a copy of an itemtype with or without fire resistance - Added method checks to only register the syntax if the appropriate method exists * Fixed method checks to provide the argument class * Added tests for each of the syntax * Added notes to description of expression * Apply suggestions from code review Co-authored-by: Shane Bee * Add version checks to tests and apply suggestions from code review * Removed note from syntax description - Happy to add back a different version of this like what Fuse suggested, but general consensus seemed to be to get rid of it * Apply suggestions from code review Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> * Apply suggestions from code review * Fixed tests * Fixed expression test --------- Co-authored-by: Shane Bee Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Moderocky --- .../conditions/CondIsFireResistant.java | 56 ++++++++++++ .../njol/skript/effects/EffFireResistant.java | 77 ++++++++++++++++ .../expressions/ExprWithFireResistance.java | 87 +++++++++++++++++++ .../conditions/CondIsFireResistant.sk | 41 +++++++++ .../syntaxes/effects/EffFireResistant.sk | 14 +++ .../expressions/ExprWithFireResistance.sk | 12 +++ 6 files changed, 287 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsFireResistant.java create mode 100644 src/main/java/ch/njol/skript/effects/EffFireResistant.java create mode 100644 src/main/java/ch/njol/skript/expressions/ExprWithFireResistance.java create mode 100644 src/test/skript/tests/syntaxes/conditions/CondIsFireResistant.sk create mode 100644 src/test/skript/tests/syntaxes/effects/EffFireResistant.sk create mode 100644 src/test/skript/tests/syntaxes/expressions/ExprWithFireResistance.sk diff --git a/src/main/java/ch/njol/skript/conditions/CondIsFireResistant.java b/src/main/java/ch/njol/skript/conditions/CondIsFireResistant.java new file mode 100644 index 00000000000..924fbd8a710 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsFireResistant.java @@ -0,0 +1,56 @@ +/** + * 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.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import org.bukkit.inventory.meta.ItemMeta; + +@Name("Is Fire Resistant") +@Description("Checks whether an item is fire resistant.") +@Examples({ + "if player's tool is fire resistant:", + "if {_items::*} aren't resistant to fire:" +}) +@RequiredPlugins("Spigot 1.20.5+") +@Since("INSERT VERSION") +public class CondIsFireResistant extends PropertyCondition { + + static { + if (Skript.methodExists(ItemMeta.class, "isFireResistant")) + PropertyCondition.register(CondIsFireResistant.class, "(fire resistant|resistant to fire)", "itemtypes"); + } + + @Override + public boolean check(ItemType item) { + return item.getItemMeta().isFireResistant(); + } + + @Override + public String getPropertyName() { + return "fire resistant"; + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffFireResistant.java b/src/main/java/ch/njol/skript/effects/EffFireResistant.java new file mode 100644 index 00000000000..6a7bfb79f89 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffFireResistant.java @@ -0,0 +1,77 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Nullable; + +@Name("Make Fire Resistant") +@Description("Makes items fire resistant.") +@Examples({ + "make player's tool fire resistant:", + "make {_items::*} not resistant to fire" +}) +@RequiredPlugins("Spigot 1.20.5+") +@Since("INSERT VERSION") +public class EffFireResistant extends Effect { + + static { + if (Skript.methodExists(ItemMeta.class, "setFireResistant", boolean.class)) + Skript.registerEffect(EffFireResistant.class, "make %itemtypes% [:not] (fire resistant|resistant to fire)"); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression items; + private boolean not; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + items = (Expression) exprs[0]; + not = parseResult.hasTag("not"); + return true; + } + + @Override + protected void execute(Event event) { + for (ItemType item : this.items.getArray(event)) { + ItemMeta meta = item.getItemMeta(); + meta.setFireResistant(!not); + item.setItemMeta(meta); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "make " + items.toString(event, debug) + (not ? " not" : "") + " fire resistant"; + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprWithFireResistance.java b/src/main/java/ch/njol/skript/expressions/ExprWithFireResistance.java new file mode 100644 index 00000000000..e471b4610ee --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprWithFireResistance.java @@ -0,0 +1,87 @@ +/** + * 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.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.bukkit.inventory.meta.ItemMeta; +import org.eclipse.jdt.annotation.Nullable; + +@Name("With Fire Resistance") +@Description({ + "Creates a copy of an item with (or without) fire resistance." +}) +@Examples({ + "set {_x} to diamond sword with fire resistance", + "equip player with netherite helmet without fire resistance", + "drop fire resistant stone at player" +}) +@RequiredPlugins("Spigot 1.20.5+") +@Since("INSERT VERSION") +public class ExprWithFireResistance extends PropertyExpression { + + static { + if (Skript.methodExists(ItemMeta.class, "setFireResistant", boolean.class)) + Skript.registerExpression(ExprWithFireResistance.class, ItemType.class, ExpressionType.PROPERTY, + "%itemtype% with[:out] fire[ ]resistance", + "fire resistant %itemtype%"); + } + + private boolean out; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr((Expression) exprs[0]); + out = parseResult.hasTag("out"); + return true; + } + + @Override + protected ItemType[] get(Event event, ItemType[] source) { + return get(source.clone(), item -> { + ItemMeta meta = item.getItemMeta(); + meta.setFireResistant(!out); + item.setItemMeta(meta); + return item; + }); + } + + @Override + public Class getReturnType() { + return ItemType.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return getExpr().toString(event, debug) + " with fire resistance"; + } + +} diff --git a/src/test/skript/tests/syntaxes/conditions/CondIsFireResistant.sk b/src/test/skript/tests/syntaxes/conditions/CondIsFireResistant.sk new file mode 100644 index 00000000000..61555e98fae --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondIsFireResistant.sk @@ -0,0 +1,41 @@ +test "is fire resistant" when running minecraft "1.20.5": + + # single item: naturally not fire resistant + set {_item} to diamond + assert {_item} is not fire resistant with "diamond is unexpectedly fire resistant" + + # TODO: enable in 1.21 (doesn't work in 1.20.5 or 1.20.6) + # single item: artificially not fire resistant + # set {_item} to netherite boots without fire resistance + # assert {_item} is not fire resistant with "netherite boots are unexpectedly fire resistant" + + # TODO: enable in 1.21 (doesn't work in 1.20.5 or 1.20.6) + # single item: naturally fire resistant + # set {_item} to netherite boots + # assert {_item} is fire resistant with "netherite boots are unexpectedly not fire resistant" + + # single item: artificially fire resistant + set {_item} to fire resistant diamond + assert {_item} is fire resistant with "fire resistant diamond is unexpectedly not fire resistant" + + # multiple items: naturally not fire resistant + set {_item} to diamond + set {_item2} to stone block + assert ({_item} and {_item2}) are not fire resistant with "{_item} and {_item2} are unexpectedly fire resistant" + + # TODO: enable in 1.21 (doesn't work in 1.20.5 or 1.20.6) + # multiple items: artificially not fire resistance + # set {_item} to netherite boots without fire resistance + # set {_item2} to netherite helmet without fire resistance + # assert ({_item} and {_item2}) are not fire resistant with "{_item} and {_item2} are unexpectedly fire resistant" + + # TODO: enable in 1.21 (doesn't work in 1.20.5 or 1.20.6) + # multiple items: naturally fire resistant + # set {_item} to netherite boots + # set {_item2} to netherite helmet + # assert ({_item} and {_item2}) are fire resistant with "{_item} and {_item2} are unexpectedly not fire resistant" + + # multiple items: artifically fire resistant + set {_item} to diamond with fire resistance + set {_item2} to fire resistant stone block + assert ({_item} and {_item2}) are fire resistant with "fire resistant {_item} and {_item2} are unexpectedly not fire resistant" diff --git a/src/test/skript/tests/syntaxes/effects/EffFireResistant.sk b/src/test/skript/tests/syntaxes/effects/EffFireResistant.sk new file mode 100644 index 00000000000..1005555ff01 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffFireResistant.sk @@ -0,0 +1,14 @@ +test "apply fire resistance" when running minecraft "1.20.5": + + # single item + set {_item} to diamond + make {_item} fire resistant + assert {_item} is fire resistant with "{_item} is unexpectedly not fire resistant" + + # multiple items + set {_item} to diamond + set {_item2} to paper + make ({_item} and {_item2}) resistant to fire + assert ({_item} and {_item2}) are resistant to fire with "{_item} and {_item2} are unexpectedly not fire resistant" + + # TODO: add tests for already fire resistant items (i.e. netherite) in 1.21 (doesn't work in 1.20.5 or 1.20.6) diff --git a/src/test/skript/tests/syntaxes/expressions/ExprWithFireResistance.sk b/src/test/skript/tests/syntaxes/expressions/ExprWithFireResistance.sk new file mode 100644 index 00000000000..e7c2754a3cb --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprWithFireResistance.sk @@ -0,0 +1,12 @@ +test "item with fire resistance" when running minecraft "1.20.5": + + # single item + set {_item} to diamond with fire resistance + assert {_item} is fire resistant with "{_item} was not fire resistant" + + # multiple items + set {_item} to fire resistant diamond + set {_item2} to paper with fire resistance + assert ({_item} and {_item2}) are fire resistant with "{_item} and {_item2} are unexpectedly not fire resistant" + + # TODO: add tests for already fire resistant items (i.e. netherite) in 1.21 (doesn't work in 1.20.5 or 1.20.6) From 389dbf9a69572fd74240e30fb680619c3cf33094 Mon Sep 17 00:00:00 2001 From: Moderocky Date: Thu, 9 May 2024 11:44:47 +0100 Subject: [PATCH 04/23] Variable-arity returns and function contracts. (#6568) * Add function contracts. * Use contract in place of reference. * Add permissible single return support. * Provide contract in signature during verification. * Allow contract in function creation. * No longer defer basic use to contract. * Add contract to clamp function. * Add tests for singular clamping. * Update src/test/skript/tests/syntaxes/functions/clamp.sk Co-authored-by: Patrick Miller * Sorry nice annotation, you're going to a "better" place :( * Move stuff around in circles. * Sovde made me change the assertions. :( * Move everything (again) * Add null annotation. --------- Co-authored-by: Patrick Miller Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../skript/classes/data/DefaultFunctions.java | 23 ++++++++-- .../ch/njol/skript/effects/EffChange.java | 2 +- .../java/ch/njol/skript/lang/Expression.java | 14 ++++++ .../lang/function/FunctionReference.java | 46 ++++++++++++++++--- .../skript/lang/function/JavaFunction.java | 7 ++- .../njol/skript/lang/function/Signature.java | 25 +++++++++- .../lang/function/SimpleJavaFunction.java | 5 ++ .../java/ch/njol/skript/util/Contract.java | 44 ++++++++++++++++++ .../skript/tests/syntaxes/functions/clamp.sk | 12 +++++ 9 files changed, 163 insertions(+), 15 deletions(-) create mode 100644 src/main/java/ch/njol/skript/util/Contract.java 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 d028845071b..afc46cba346 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -19,6 +19,7 @@ package ch.njol.skript.classes.data; import ch.njol.skript.expressions.base.EventValueExpression; +import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.function.FunctionEvent; import ch.njol.skript.lang.function.Functions; import ch.njol.skript.lang.function.JavaFunction; @@ -40,6 +41,7 @@ import org.bukkit.entity.Player; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.Nullable; +import ch.njol.skript.util.Contract; import java.math.BigDecimal; import java.math.RoundingMode; @@ -309,11 +311,22 @@ public Number[] executeSimple(Object[][] params) { .examples("min(1) = 1", "min(1, 2, 3, 4) = 1", "min({some list variable::*})") .since("2.2")); - Functions.registerFunction(new SimpleJavaFunction("clamp", new Parameter[]{ - new Parameter<>("values", DefaultClasses.NUMBER, false, null), - new Parameter<>("min", DefaultClasses.NUMBER, true, null), - new Parameter<>("max", DefaultClasses.NUMBER, true, null) - }, DefaultClasses.NUMBER, false) { + Functions.registerFunction(new SimpleJavaFunction("clamp", new Parameter[] { + new Parameter<>("values", DefaultClasses.NUMBER, false, null), + new Parameter<>("min", DefaultClasses.NUMBER, true, null), + new Parameter<>("max", DefaultClasses.NUMBER, true, null) + }, DefaultClasses.NUMBER, false, new Contract() { + + @Override + public boolean isSingle(Expression... arguments) { + return arguments[0].isSingle(); + } + + @Override + public Class getReturnType(Expression... arguments) { + return Number.class; + } + }) { @Override public @Nullable Number[] executeSimple(Object[][] params) { Number[] values = (Number[]) params[0]; diff --git a/src/main/java/ch/njol/skript/effects/EffChange.java b/src/main/java/ch/njol/skript/effects/EffChange.java index 9c355191ab2..584d6858a89 100644 --- a/src/main/java/ch/njol/skript/effects/EffChange.java +++ b/src/main/java/ch/njol/skript/effects/EffChange.java @@ -248,7 +248,7 @@ else if (mode == ChangeMode.SET) assert x != null; changer = ch = v; - if (!ch.isSingle() && single) { + if (!ch.canBeSingle() && single) { if (mode == ChangeMode.SET) Skript.error(changed + " can only be set to one " + Classes.getSuperClassInfo(x).getName() + ", not more", ErrorQuality.SEMANTIC_ERROR); else diff --git a/src/main/java/ch/njol/skript/lang/Expression.java b/src/main/java/ch/njol/skript/lang/Expression.java index a44283b1b0f..ebde53ee479 100644 --- a/src/main/java/ch/njol/skript/lang/Expression.java +++ b/src/main/java/ch/njol/skript/lang/Expression.java @@ -120,6 +120,20 @@ default Optional getOptionalSingle(Event event) { */ boolean isSingle(); + /** + * Whether there's a possibility this could return a single value. + * Designed for expressions that may return more than one value, but are equally appropriate to use where + * only singular values are accepted, in which case a single value (out of all available values) will be returned. + * An example would be functions that return results based on their inputs. + * Ideally, this will return {@link #isSingle()} based on its known inputs at initialisation, but for some syntax + * this may not be known (or a syntax may be intentionally vague in its permissible returns). + * @return Whether this can be used by single changers + * @see #getSingle(Event) + */ + default boolean canBeSingle() { + return this.isSingle(); + } + /** * Checks this expression against the given checker. This is the normal version of this method and the one which must be used for simple checks, * or as the innermost check of nested checks. 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 01feacaf2a5..409e4edd686 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java @@ -33,6 +33,7 @@ import ch.njol.util.StringUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import ch.njol.skript.util.Contract; import java.util.ArrayList; import java.util.Arrays; @@ -41,7 +42,7 @@ /** * Reference to a Skript function. */ -public class FunctionReference { +public class FunctionReference implements Contract { /** * Name of function that is called, for logging purposes. @@ -97,7 +98,13 @@ public class FunctionReference { */ @Nullable public final String script; - + + /** + * The contract for this function (typically the function reference itself). + * Used to determine input-based return types and simple behaviour. + */ + private Contract contract; + public FunctionReference( String functionName, @Nullable Node node, @Nullable String script, @Nullable Class[] returnTypes, Expression[] params @@ -106,7 +113,8 @@ public FunctionReference( this.node = node; this.script = script; this.returnTypes = returnTypes; - parameters = params; + this.parameters = params; + this.contract = this; } public boolean validateParameterArity(boolean first) { @@ -257,6 +265,10 @@ public boolean validateFunction(boolean first) { signature = (Signature) sign; sign.calls.add(this); + + Contract contract = sign.getContract(); + if (contract != null) + this.contract = contract; return true; } @@ -310,21 +322,41 @@ protected T[] execute(Event e) { // Execute the function return function.execute(params); } - + public boolean isSingle() { + return contract.isSingle(parameters); + } + + @Override + public boolean isSingle(Expression... arguments) { return single; } - + @Nullable public Class getReturnType() { + //noinspection unchecked + return (Class) contract.getReturnType(parameters); + } + + @Override + @Nullable + public Class getReturnType(Expression... arguments) { if (signature == null) throw new SkriptAPIException("Signature of function is null when return type is asked!"); - + @SuppressWarnings("ConstantConditions") ClassInfo ret = signature.returnType; return ret == null ? null : ret.getC(); } - + + /** + * The contract is used in preference to the function for determining return type, etc. + * @return The contract determining this function's parse-time hints, potentially this reference + */ + public Contract getContract() { + return contract; + } + public String toString(@Nullable Event e, boolean debug) { StringBuilder b = new StringBuilder(functionName + "("); for (int i = 0; i < parameters.length; i++) { diff --git a/src/main/java/ch/njol/skript/lang/function/JavaFunction.java b/src/main/java/ch/njol/skript/lang/function/JavaFunction.java index a46bdc17b02..5b23f304d77 100644 --- a/src/main/java/ch/njol/skript/lang/function/JavaFunction.java +++ b/src/main/java/ch/njol/skript/lang/function/JavaFunction.java @@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.util.Contract; /** * @author Peter Güttinger @@ -32,7 +33,11 @@ public JavaFunction(Signature sign) { } public JavaFunction(String name, Parameter[] parameters, ClassInfo returnType, boolean single) { - this(new Signature<>("none", name, parameters, false, returnType, single, Thread.currentThread().getStackTrace()[3].getClassName())); + this(name, parameters, returnType, single, null); + } + + public JavaFunction(String name, Parameter[] parameters, ClassInfo returnType, boolean single, @Nullable Contract contract) { + this(new Signature<>("none", name, parameters, false, returnType, single, Thread.currentThread().getStackTrace()[3].getClassName(), contract)); } @Override diff --git a/src/main/java/ch/njol/skript/lang/function/Signature.java b/src/main/java/ch/njol/skript/lang/function/Signature.java index a07825fd292..3b30e2273de 100644 --- a/src/main/java/ch/njol/skript/lang/function/Signature.java +++ b/src/main/java/ch/njol/skript/lang/function/Signature.java @@ -20,6 +20,7 @@ import ch.njol.skript.classes.ClassInfo; import org.eclipse.jdt.annotation.Nullable; +import ch.njol.skript.util.Contract; import java.util.Collection; import java.util.Collections; @@ -75,12 +76,19 @@ public class Signature { @Nullable final String originClassPath; + /** + * An overriding contract for this function (e.g. to base its return on its arguments). + */ + @Nullable + final Contract contract; + public Signature(String script, String name, Parameter[] parameters, boolean local, @Nullable ClassInfo returnType, boolean single, - @Nullable String originClassPath) { + @Nullable String originClassPath, + @Nullable Contract contract) { this.script = script; this.name = name; this.parameters = parameters; @@ -88,10 +96,20 @@ public Signature(String script, this.returnType = returnType; this.single = single; this.originClassPath = originClassPath; + this.contract = contract; calls = Collections.newSetFromMap(new WeakHashMap<>()); } + public Signature(String script, + String name, + Parameter[] parameters, boolean local, + @Nullable ClassInfo returnType, + boolean single, + @Nullable String originClassPath) { + this(script, name, parameters, local, returnType, single, originClassPath, null); + } + public Signature(String script, String name, Parameter[] parameters, boolean local, @Nullable ClassInfo returnType, boolean single) { this(script, name, parameters, local, returnType, single, null); } @@ -126,6 +144,11 @@ public String getOriginClassPath() { return originClassPath; } + @Nullable + public Contract getContract() { + return contract; + } + /** * Gets maximum number of parameters that the function described by this * signature is able to take. diff --git a/src/main/java/ch/njol/skript/lang/function/SimpleJavaFunction.java b/src/main/java/ch/njol/skript/lang/function/SimpleJavaFunction.java index bed025fbb7e..87e3bd4f26a 100644 --- a/src/main/java/ch/njol/skript/lang/function/SimpleJavaFunction.java +++ b/src/main/java/ch/njol/skript/lang/function/SimpleJavaFunction.java @@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.util.Contract; /** * A {@link JavaFunction} which doesn't make use of @@ -36,6 +37,10 @@ public SimpleJavaFunction(Signature sign) { public SimpleJavaFunction(String name, Parameter[] parameters, ClassInfo returnType, boolean single) { super(name, parameters, returnType, single); } + + public SimpleJavaFunction(String name, Parameter[] parameters, ClassInfo returnType, boolean single, Contract contract) { + super(name, parameters, returnType, single, contract); + } @SuppressWarnings("ConstantConditions") @Nullable diff --git a/src/main/java/ch/njol/skript/util/Contract.java b/src/main/java/ch/njol/skript/util/Contract.java new file mode 100644 index 00000000000..49e5fd40c79 --- /dev/null +++ b/src/main/java/ch/njol/skript/util/Contract.java @@ -0,0 +1,44 @@ +/** + * 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.lang.Expression; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The 'contract' of a function or another callable. + * This is a non-exhaustive helper for type hints, singularity, etc. that may change based on the arguments + * passed to a callable, in order for it to make better judgements on correct use at parse time. + */ +public interface Contract { + + /** + * @return Whether, given these parameters, this will return a single value + * @see Expression#isSingle() + */ + boolean isSingle(Expression... arguments); + + /** + * @return What this will return, given these parameters + * @see Expression#getReturnType() + */ + @Nullable + Class getReturnType(Expression... arguments); + +} diff --git a/src/test/skript/tests/syntaxes/functions/clamp.sk b/src/test/skript/tests/syntaxes/functions/clamp.sk index 98b9ad85dc5..2c3be6329ba 100644 --- a/src/test/skript/tests/syntaxes/functions/clamp.sk +++ b/src/test/skript/tests/syntaxes/functions/clamp.sk @@ -41,3 +41,15 @@ test "clamp numbers": assert number within {_got::1} is not number within {_got::1} with "(edge cases list) NaN" # need within because the variables weren't cooperating assert {_got::2} is {_expected::2} with "(edge cases list) -infinity" assert {_got::3} is {_expected::3} with "(edge cases list) infinity" + +test "clamp numbers (single)": + set {_expected::*} to (1, 0.0, and 2.0) + set {_got::*} to clamp((1, -infinity value, infinity value), 0.0, 2.0) + assert size of {_got::*} is 3 with "(multiple) expected" + loop 3 times: + assert {_got::%loop-number%} is {_expected::%loop-number%} with "(plural) expected %{_expected::%loop-number%}% found %{_got::%loop-number%}%" + + # single store + set {_expected} to 2 + set {_got} to clamp(0, 2, 5) + assert {_got} is {_expected} with "(single) expected" From d649c836da6a8d4ce6bfb45e623edb748788d437 Mon Sep 17 00:00:00 2001 From: Moderocky Date: Fri, 10 May 2024 16:01:04 +0100 Subject: [PATCH 05/23] Copy the SimpleLiteral backing data instead of exposing it. (#6683) Use copy array whenever data could be modified. --- .../ch/njol/skript/lang/util/SimpleLiteral.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) 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 3cab2276046..030db308993 100644 --- a/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java +++ b/src/main/java/ch/njol/skript/lang/util/SimpleLiteral.java @@ -38,6 +38,7 @@ import org.skriptlang.skript.lang.converter.Converters; import java.lang.reflect.Array; +import java.util.Arrays; /** * Represents a literal, i.e. a static value like a number or a string. @@ -95,24 +96,28 @@ public boolean init() { return true; } + private T[] data() { + return Arrays.copyOf(data, data.length); + } + @Override public T[] getArray() { - return data; + return this.data(); } @Override public T[] getArray(Event event) { - return data; + return this.data(); } @Override public T[] getAll() { - return data; + return this.data(); } @Override public T[] getAll(Event event) { - return data; + return this.data(); } @Override @@ -136,7 +141,7 @@ public Class getReturnType() { public Literal getConvertedExpression(Class... to) { if (CollectionUtils.containsSuperclass(to, type)) return (Literal) this; - R[] parsedData = Converters.convert(data, to, (Class) Utils.getSuperType(to)); + R[] parsedData = Converters.convert(this.data(), to, (Class) Utils.getSuperType(to)); if (parsedData.length != data.length) return null; return new ConvertedLiteral<>(this, parsedData, (Class) Utils.getSuperType(to)); From c8cf21b81cf199e479b4318c736e82089ab52911 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Mon, 13 May 2024 18:45:56 +0200 Subject: [PATCH 06/23] Fix compilation error due to Spigot changes to EntityDeathEvent (#6692) * switch events * unimportant Co-authored-by: Moderocky --- .../skript/test/tests/lang/CancelledEventsTest.java | 8 ++------ src/test/skript/junit/CancelledEventTest.sk | 6 +++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/skriptlang/skript/test/tests/lang/CancelledEventsTest.java b/src/test/java/org/skriptlang/skript/test/tests/lang/CancelledEventsTest.java index 17f7606fa6a..75bddb12e21 100644 --- a/src/test/java/org/skriptlang/skript/test/tests/lang/CancelledEventsTest.java +++ b/src/test/java/org/skriptlang/skript/test/tests/lang/CancelledEventsTest.java @@ -20,12 +20,9 @@ import ch.njol.skript.test.runner.SkriptJUnitTest; import org.bukkit.Bukkit; -import org.bukkit.entity.Pig; -import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.block.BlockFormEvent; import org.junit.Test; -import java.util.ArrayList; - public class CancelledEventsTest extends SkriptJUnitTest { @@ -35,8 +32,7 @@ public class CancelledEventsTest extends SkriptJUnitTest { @Test public void callCancelledEvent() { - Pig pig = spawnTestPig(); - EntityDeathEvent event = new EntityDeathEvent(pig, new ArrayList<>()); + BlockFormEvent event = new BlockFormEvent(getBlock(), getBlock().getState()); // call cancelled event event.setCancelled(true); diff --git a/src/test/skript/junit/CancelledEventTest.sk b/src/test/skript/junit/CancelledEventTest.sk index f7abc9d99bc..ae8b2563367 100644 --- a/src/test/skript/junit/CancelledEventTest.sk +++ b/src/test/skript/junit/CancelledEventTest.sk @@ -13,17 +13,17 @@ test "ExprDropsJUnit" when running JUnit: on load: set {-cancelled-event-test::call-count} to 0 -on death of pig: +on form: junit test is {@test} complete objective "listen for uncancelled event" for {@test} -on cancelled death of pig: +on cancelled form: junit test is {@test} complete objective "listen for cancelled event" for {@test} -on any death of pig: +on any form: junit test is {@test} add 1 to {-cancelled-event-test::call-count} From ef3d3603c4aaedf519f2c1322c3a0f09f4b53a67 Mon Sep 17 00:00:00 2001 From: Moderocky Date: Sat, 18 May 2024 10:16:41 +0100 Subject: [PATCH 07/23] Experimental features & the `using` structure. (#6552) * First pass at supporting SimpleNode for Structures * Experimental features - the 'using' structure. * Oops :grimacing: * Improve documentation and test coverage. * Add shane's extra special one-line creation & registration (50% off today only!) method. * Update src/main/java/ch/njol/skript/conditions/CondIsUsing.java Version thingy. Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * Add Ayham's changes (bye bye nice final modifiers :cry:) * Add a test-only syntax contingent on a feature flag. * Update src/main/java/org/skriptlang/skript/lang/experiment/Feature.java Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> * Add todo notes and deprecate internal members. * I made it string sovde :( * Update src/main/java/org/skriptlang/skript/lang/structure/Structure.java Co-authored-by: Patrick Miller * :( * Allow unregistering experiments. * No brackets :( * Update src/main/java/ch/njol/skript/conditions/CondIsUsingFeature.java Co-authored-by: Patrick Miller * Update src/main/java/ch/njol/skript/lang/SyntaxElement.java Co-authored-by: Patrick Miller * Move experiments to snapshot model. * Add docs for needy walrus. * Update src/main/java/org/skriptlang/skript/lang/script/Script.java Co-authored-by: Patrick Miller * Apply suggestions from code review Co-authored-by: Patrick Miller * Compromising my values for walrus * Apply changes from code review. * Change pattern method. * Apply suggestions from code review Co-authored-by: Patrick Miller * Move features to registrations. --------- Co-authored-by: APickledWalrus Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- src/main/java/ch/njol/skript/Skript.java | 346 +++++++++--------- .../skript/conditions/CondIsUsingFeature.java | 81 ++++ .../skript/lang/parser/ParserInstance.java | 83 ++++- .../ch/njol/skript/registrations/Feature.java | 79 ++++ .../njol/skript/structures/StructUsing.java | 93 +++++ .../test/runner/ExprExperimentalOnly.java | 68 ++++ .../njol/skript/test/runner/TestFeatures.java | 87 +++++ .../skript/lang/experiment/Experiment.java | 172 +++++++++ .../lang/experiment/ExperimentRegistry.java | 162 ++++++++ .../skript/lang/experiment/ExperimentSet.java | 54 +++ .../skript/lang/experiment/Experimented.java | 45 +++ .../skript/lang/experiment/LifeCycle.java | 69 ++++ .../tests/syntaxes/structures/StructUsing.sk | 19 + 13 files changed, 1179 insertions(+), 179 deletions(-) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsUsingFeature.java create mode 100644 src/main/java/ch/njol/skript/registrations/Feature.java create mode 100644 src/main/java/ch/njol/skript/structures/StructUsing.java create mode 100644 src/main/java/ch/njol/skript/test/runner/ExprExperimentalOnly.java create mode 100644 src/main/java/ch/njol/skript/test/runner/TestFeatures.java create mode 100644 src/main/java/org/skriptlang/skript/lang/experiment/Experiment.java create mode 100644 src/main/java/org/skriptlang/skript/lang/experiment/ExperimentRegistry.java create mode 100644 src/main/java/org/skriptlang/skript/lang/experiment/ExperimentSet.java create mode 100644 src/main/java/org/skriptlang/skript/lang/experiment/Experimented.java create mode 100644 src/main/java/org/skriptlang/skript/lang/experiment/LifeCycle.java create mode 100644 src/test/skript/tests/syntaxes/structures/StructUsing.sk diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 83fa4a93ccb..d98a51a7763 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -83,7 +83,6 @@ import ch.njol.util.Kleenean; import ch.njol.util.NullableChecker; import ch.njol.util.StringUtils; -import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.CheckedIterator; import ch.njol.util.coll.iterator.EnumerationIterable; import com.google.common.collect.Lists; @@ -108,6 +107,7 @@ import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPlugin; import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.UnknownNullability; import org.junit.After; import org.junit.runner.JUnitCore; import org.junit.runner.Result; @@ -116,6 +116,8 @@ import org.skriptlang.skript.lang.converter.Converter; import org.skriptlang.skript.lang.converter.Converters; import org.skriptlang.skript.lang.entry.EntryValidator; +import org.skriptlang.skript.lang.experiment.ExperimentRegistry; +import ch.njol.skript.registrations.Feature; import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.structure.Structure; import org.skriptlang.skript.lang.structure.StructureInfo; @@ -169,7 +171,7 @@ *

* Once you made sure that Skript is loaded you can use Skript.getInstance() whenever you need a reference to the plugin, but you likely won't need it since all API * methods are static. - * + * * @author Peter Güttinger * @see #registerAddon(JavaPlugin) * @see #registerCondition(Class, String...) @@ -182,34 +184,34 @@ * @see Converters#registerConverter(Class, Class, Converter) */ public final class Skript extends JavaPlugin implements Listener { - + // ================ PLUGIN ================ - + @Nullable private static Skript instance = null; - + private static boolean disabled = false; private static boolean partDisabled = false; - + public static Skript getInstance() { final Skript i = instance; if (i == null) throw new IllegalStateException(); return i; } - + /** * Current updater instance used by Skript. */ @Nullable private SkriptUpdater updater; - + public Skript() throws IllegalStateException { if (instance != null) throw new IllegalStateException("Cannot create multiple instances of Skript!"); instance = this; } - + private static Version minecraftVersion = new Version(666), UNKNOWN_VERSION = new Version(666); private static ServerPlatform serverPlatform = ServerPlatform.BUKKIT_UNKNOWN; // Start with unknown... onLoad changes this @@ -227,24 +229,26 @@ public static void updateMinecraftVersion() { minecraftVersion = new Version("" + m.group()); } } - + @Nullable private static Version version = null; - + @Deprecated(forRemoval = true) // TODO this field will be replaced by a proper registry later + private static @UnknownNullability ExperimentRegistry experimentRegistry; + public static Version getVersion() { final Version v = version; if (v == null) throw new IllegalStateException(); return v; } - + public static final Message m_invalid_reload = new Message("skript.invalid reload"), m_finished_loading = new Message("skript.finished loading"), m_no_errors = new Message("skript.no errors"), m_no_scripts = new Message("skript.no scripts"); private static final PluralizingArgsMessage m_scripts_loaded = new PluralizingArgsMessage("skript.scripts loaded"); - + public static ServerPlatform getServerPlatform() { if (classExists("net.glowstone.GlowServer")) { return ServerPlatform.BUKKIT_GLOWSTONE; // Glowstone has timings too, so must check for it first @@ -270,7 +274,7 @@ private static boolean using32BitJava() { // Property returned should either be "Java HotSpot(TM) 32-Bit Server VM" or "OpenJDK 32-Bit Server VM" if 32-bit and using OracleJDK/OpenJDK return System.getProperty("java.vm.name").contains("32"); } - + /** * Checks if server software and Minecraft version are supported. * Prints errors or warnings to console if something is wrong. @@ -287,7 +291,7 @@ private static boolean checkServerPlatform() { minecraftVersion = new Version("" + m.group()); } Skript.debug("Loading for Minecraft " + minecraftVersion); - + // Check that MC version is supported if (!isRunningMinecraft(1, 9)) { // Prevent loading when not running at least Minecraft 1.9 @@ -296,7 +300,7 @@ private static boolean checkServerPlatform() { Skript.error("Note that those versions are, of course, completely unsupported!"); return false; } - + // Check that current server platform is somewhat supported serverPlatform = getServerPlatform(); Skript.debug("Server platform: " + serverPlatform); @@ -316,7 +320,7 @@ private static boolean checkServerPlatform() { Skript.warning("It will still probably work, but if it does not, you are on your own."); Skript.warning("Skript officially supports Paper and Spigot."); } - + // If nothing got triggered, everything is probably ok return true; } @@ -328,7 +332,7 @@ private static boolean checkServerPlatform() { * Checks whether a hook has been enabled. * @param hook The hook to check. * @return Whether the hook is enabled. - * @see #disableHookRegistration(Class[]) + * @see #disableHookRegistration(Class[]) */ public static boolean isHookEnabled(Class> hook) { return !disabledHookRegistrations.contains(hook); @@ -346,7 +350,7 @@ public static boolean isFinishedLoadingHooks() { * Disables the registration for the given hook classes. If Skript has been enabled, this method * will throw an API exception. It should be used in something like {@link JavaPlugin#onLoad()}. * @param hooks The hooks to disable the registration of. - * @see #isHookEnabled(Class) + * @see #isHookEnabled(Class) */ @SafeVarargs public static void disableHookRegistration(Class>... hooks) { @@ -362,6 +366,13 @@ public static void disableHookRegistration(Class>... hooks) { */ private File scriptsFolder; + /** + * @return The manager for experimental, optional features. + */ + public static ExperimentRegistry experiments() { + return experimentRegistry; + } + /** * @return The folder containing all Scripts. */ @@ -371,7 +382,7 @@ public File getScriptsFolder() { scriptsFolder.mkdirs(); return scriptsFolder; } - + @Override public void onEnable() { Bukkit.getPluginManager().registerEvents(this, this); @@ -380,11 +391,11 @@ public void onEnable() { setEnabled(false); return; } - + handleJvmArguments(); // JVM arguments - + version = new Version("" + getDescription().getVersion()); // Skript version - + // Start the updater // Note: if config prohibits update checks, it will NOT do network connections try { @@ -392,7 +403,9 @@ public void onEnable() { } catch (Exception e) { Skript.exception(e, "Update checker could not be initialized."); } - + experimentRegistry = new ExperimentRegistry(this); + Feature.registerAll(getAddonInstance(), experimentRegistry); + if (!getDataFolder().isDirectory()) getDataFolder().mkdirs(); @@ -468,7 +481,7 @@ public void onEnable() { // initialize the Skript addon instance getAddonInstance(); - + // Load classes which are always safe to use new JavaClasses(); // These may be needed in configuration @@ -487,15 +500,15 @@ public void onEnable() { } catch (Throwable e) { classLoadError = e; } - + // Config must be loaded after Java and Skript classes are parseable // ... but also before platform check, because there is a config option to ignore some errors SkriptConfig.load(); - + // Now override the verbosity if test mode is enabled if (TestMode.VERBOSITY != null) SkriptLogger.setVerbosity(Verbosity.valueOf(TestMode.VERBOSITY)); - + // Use the updater, now that it has been configured to (not) do stuff if (updater != null) { CommandSender console = Bukkit.getConsoleSender(); @@ -517,29 +530,29 @@ public void onEnable() { throw e; // Uh oh, this shouldn't happen. Re-throw the error. } } - + // If loading can continue (platform ok), check for potentially thrown error if (classLoadError != null) { exception(classLoadError); setEnabled(false); return; } - + PluginCommand skriptCommand = getCommand("skript"); assert skriptCommand != null; // It is defined, unless build is corrupted or something like that skriptCommand.setExecutor(new SkriptCommand()); skriptCommand.setTabCompleter(new SkriptCommandTabCompleter()); - + // Load Bukkit stuff. It is done after platform check, because something might be missing! new BukkitEventValues(); - + new DefaultComparators(); new DefaultConverters(); new DefaultFunctions(); new DefaultOperations(); - + ChatMessages.registerListeners(); - + try { getAddonInstance().loadClasses("ch.njol.skript", "conditions", "effects", "events", "expressions", "entity", "sections", "structures"); @@ -550,17 +563,17 @@ public void onEnable() { } Commands.registerListeners(); - + if (logNormal()) info(" " + Language.get("skript.copyright")); - + final long tick = testing() ? Bukkit.getWorlds().get(0).getFullTime() : 0; Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable() { @SuppressWarnings("synthetic-access") @Override public void run() { assert Bukkit.getWorlds().get(0).getFullTime() == tick; - + // Load hooks from Skript jar try { try (JarFile jar = new JarFile(getFile())) { @@ -588,7 +601,7 @@ public void run() { Skript.exception(e); } finishedLoadingHooks = true; - + if (TestMode.ENABLED) { info("Preparing Skript for testing..."); tainted = true; @@ -601,10 +614,10 @@ public void run() { Bukkit.getServer().shutdown(); } } - + stopAcceptingRegistrations(); - - + + Documentation.generate(); // TODO move to test classes? // Variable loading @@ -749,7 +762,7 @@ protected void afterErrors() { } if (ignored > 0) Skript.warning("There were " + ignored + " ignored test cases! This can mean they are not properly setup in order in that class!"); - + info("Completed " + tests + " JUnit tests in " + size + " classes with " + fails + " failures in " + milliseconds + " milliseconds."); } } @@ -824,7 +837,7 @@ protected void afterErrors() { )); metrics.addCustomChart(new SimplePie("releaseChannel", SkriptConfig.releaseChannel::value)); Skript.metrics = metrics; - + /* * Start loading scripts */ @@ -875,10 +888,10 @@ protected void afterErrors() { throw Skript.exception(e); } }); - + } }); - + Bukkit.getPluginManager().registerEvents(new Listener() { @EventHandler public void onJoin(final PlayerJoinEvent e) { @@ -890,7 +903,7 @@ public void run() { SkriptUpdater updater = getUpdater(); if (updater == null) return; - + // Don't actually check for updates to avoid breaking Github rate limit if (updater.getReleaseStatus() == ReleaseStatus.OUTDATED) { // Last check indicated that an update is available @@ -905,17 +918,17 @@ public void run() { } } }, this); - + // Tell Timings that we are here! SkriptTimings.setSkript(this); } - + /** * Handles -Dskript.stuff command line arguments. */ private void handleJvmArguments() { Path folder = getDataFolder().toPath(); - + /* * Burger is a Python application that extracts data from Minecraft. * Datasets for most common versions are available for download. @@ -954,13 +967,13 @@ private void handleJvmArguments() { return; } } - + // Use BurgerHelper to create some mappings, then dump them as JSON try { BurgerHelper burger = new BurgerHelper(burgerInput); Map materials = burger.mapMaterials(); Map ids = BurgerHelper.mapIds(); - + Gson gson = new Gson(); Files.write(folder.resolve("materials_mappings.json"), gson.toJson(materials) .getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); @@ -971,18 +984,18 @@ private void handleJvmArguments() { } } } - + public static Version getMinecraftVersion() { return minecraftVersion; } - + /** * @return Whether this server is running CraftBukkit */ public static boolean isRunningCraftBukkit() { return serverPlatform == ServerPlatform.BUKKIT_CRAFTBUKKIT; } - + /** * @return Whether this server is running Minecraft major.minor or higher */ @@ -992,24 +1005,24 @@ public static boolean isRunningMinecraft(final int major, final int minor) { } return minecraftVersion.compareTo(major, minor) >= 0; } - + public static boolean isRunningMinecraft(final int major, final int minor, final int revision) { if (minecraftVersion.compareTo(UNKNOWN_VERSION) == 0) { updateMinecraftVersion(); } return minecraftVersion.compareTo(major, minor, revision) >= 0; } - + public static boolean isRunningMinecraft(final Version v) { if (minecraftVersion.compareTo(UNKNOWN_VERSION) == 0) { updateMinecraftVersion(); } return minecraftVersion.compareTo(v) >= 0; } - + /** * Used to test whether certain Bukkit features are supported. - * + * * @param className * @return Whether the given class exists. * @deprecated use {@link #classExists(String)} @@ -1018,10 +1031,10 @@ public static boolean isRunningMinecraft(final Version v) { public static boolean supports(final String className) { return classExists(className); } - + /** * Tests whether a given class exists in the classpath. - * + * * @param className The {@link Class#getCanonicalName() canonical name} of the class * @return Whether the given class exists. */ @@ -1033,10 +1046,10 @@ public static boolean classExists(final String className) { return false; } } - + /** * Tests whether a method exists in the given class. - * + * * @param c The class * @param methodName The name of the method * @param parameterTypes The parameter types of the method @@ -1052,12 +1065,12 @@ public static boolean methodExists(final Class c, final String methodName, fi return false; } } - + /** * Tests whether a method exists in the given class, and whether the return type matches the expected one. *

* Note that this method doesn't work properly if multiple methods with the same name and parameters exist but have different return types. - * + * * @param c The class * @param methodName The name of the method * @param parameterTypes The parameter types of the method @@ -1074,10 +1087,10 @@ public static boolean methodExists(final Class c, final String methodName, fi return false; } } - + /** * Tests whether a field exists in the given class. - * + * * @param c The class * @param fieldName The name of the field * @return Whether the given field exists. @@ -1092,23 +1105,23 @@ public static boolean fieldExists(final Class c, final String fieldName) { return false; } } - + @Nullable static Metrics metrics; - + @Nullable public static Metrics getMetrics() { return metrics; } - + @SuppressWarnings("null") private final static Collection closeOnDisable = Collections.synchronizedCollection(new ArrayList()); - + /** * Registers a Closeable that should be closed when this plugin is disabled. *

* All registered Closeables will be closed after all scripts have been stopped. - * + * * @param closeable */ public static void closeOnDisable(final Closeable closeable) { @@ -1199,13 +1212,14 @@ public void onDisable() { if (disabled) return; disabled = true; + this.experimentRegistry = null; if (!partDisabled) { beforeDisable(); } - + Bukkit.getScheduler().cancelTasks(this); - + for (Closeable c : closeOnDisable) { try { c.close(); @@ -1214,46 +1228,46 @@ public void onDisable() { } } } - + // ================ CONSTANTS, OPTIONS & OTHER ================ - + public final static String SCRIPTSFOLDER = "scripts"; - + public static void outdatedError() { error("Skript v" + getInstance().getDescription().getVersion() + " is not fully compatible with Bukkit " + Bukkit.getVersion() + ". Some feature(s) will be broken until you update Skript."); } - + public static void outdatedError(final Exception e) { outdatedError(); if (testing()) e.printStackTrace(); } - + /** * A small value, useful for comparing doubles or floats. *

* E.g. to test whether two floating-point numbers are equal: - * + * *

 	 * Math.abs(a - b) < Skript.EPSILON
 	 * 
- * + * * or whether a location is within a specific radius of another location: - * + * *
 	 * location.distanceSquared(center) - radius * radius < Skript.EPSILON
 	 * 
- * + * * @see #EPSILON_MULT */ public final static double EPSILON = 1e-10; /** * A value a bit larger than 1 - * + * * @see #EPSILON */ public final static double EPSILON_MULT = 1.00001; - + /** * The maximum ID a block can have in Minecraft. */ @@ -1262,19 +1276,19 @@ public static void outdatedError(final Exception e) { * The maximum data value of Minecraft, i.e. Short.MAX_VALUE - Short.MIN_VALUE. */ public final static int MAXDATAVALUE = Short.MAX_VALUE - Short.MIN_VALUE; - + // TODO localise Infinity, -Infinity, NaN (and decimal point?) public static String toString(final double n) { return StringUtils.toString(n, SkriptConfig.numberAccuracy.value()); } - + public final static UncaughtExceptionHandler UEH = new UncaughtExceptionHandler() { @Override public void uncaughtException(final @Nullable Thread t, final @Nullable Throwable e) { Skript.exception(e, "Exception in thread " + (t == null ? null : t.getName())); } }; - + /** * Creates a new Thread and sets its UncaughtExceptionHandler. The Thread is not started automatically. */ @@ -1283,38 +1297,38 @@ public static Thread newThread(final Runnable r, final String name) { t.setUncaughtExceptionHandler(UEH); return t; } - + // ================ REGISTRATIONS ================ - + private static boolean acceptRegistrations = true; - + public static boolean isAcceptRegistrations() { if (instance == null) throw new IllegalStateException("Skript was never loaded"); return acceptRegistrations && instance.isEnabled(); } - + public static void checkAcceptRegistrations() { if (!isAcceptRegistrations() && !Skript.testing()) throw new SkriptAPIException("Registration can only be done during plugin initialization"); } - + private static void stopAcceptingRegistrations() { Converters.createChainedConverters(); acceptRegistrations = false; - + Classes.onRegistrationsStop(); } - + // ================ ADDONS ================ - + private final static HashMap addons = new HashMap<>(); - + /** * Registers an addon to Skript. This is currently not required for addons to work, but the returned {@link SkriptAddon} provides useful methods for registering syntax elements * and adding new strings to Skript's localization system (e.g. the required "types.[type]" strings for registered classes). - * + * * @param p The plugin */ public static SkriptAddon registerAddon(final JavaPlugin p) { @@ -1325,25 +1339,25 @@ public static SkriptAddon registerAddon(final JavaPlugin p) { addons.put(p.getName(), addon); return addon; } - + @Nullable public static SkriptAddon getAddon(final JavaPlugin p) { return addons.get(p.getName()); } - + @Nullable public static SkriptAddon getAddon(final String name) { return addons.get(name); } - + @SuppressWarnings("null") public static Collection getAddons() { return Collections.unmodifiableCollection(addons.values()); } - + @Nullable private static SkriptAddon addon; - + /** * @return A {@link SkriptAddon} representing Skript. */ @@ -1364,7 +1378,7 @@ public static SkriptAddon getAddonInstance() { /** * registers a {@link Condition}. - * + * * @param condition The condition's class * @param patterns Skript patterns to match this condition */ @@ -1375,10 +1389,10 @@ public static void registerCondition(final Class condit conditions.add(info); statements.add(info); } - + /** * Registers an {@link Effect}. - * + * * @param effect The effect's class * @param patterns Skript patterns to match this effect */ @@ -1407,11 +1421,11 @@ public static void registerSection(Class section, String. public static Collection> getStatements() { return statements; } - + public static Collection> getConditions() { return conditions; } - + public static Collection> getEffects() { return effects; } @@ -1421,14 +1435,14 @@ public static Collection> getSections() { } // ================ EXPRESSIONS ================ - + private final static List> expressions = new ArrayList<>(100); - + private final static int[] expressionTypesStartIndices = new int[ExpressionType.values().length]; - + /** * Registers an expression. - * + * * @param c The expression's class * @param returnType The superclass of all values returned by the expression * @param type The expression's {@link ExpressionType type}. This is used to determine in which order to try to parse expressions. @@ -1446,12 +1460,12 @@ public static , T> void registerExpression(final Class> getExpressions() { return expressions.iterator(); } - + public static Iterator> getExpressions(final Class... returnTypes) { return new CheckedIterator<>(getExpressions(), new NullableChecker>() { @Override @@ -1467,7 +1481,7 @@ public boolean check(final @Nullable ExpressionInfo i) { } }); } - + // ================ EVENTS ================ private static final List> events = new ArrayList<>(50); @@ -1475,7 +1489,7 @@ public boolean check(final @Nullable ExpressionInfo i) { /** * Registers an event. - * + * * @param name Capitalised name of the event without leading "On" which is added automatically (Start the name with an asterisk to prevent this). Used for error messages and * the documentation. * @param c The event's class @@ -1487,10 +1501,10 @@ public boolean check(final @Nullable ExpressionInfo i) { public static SkriptEventInfo registerEvent(String name, Class c, Class event, String... patterns) { return registerEvent(name, c, new Class[] {event}, patterns); } - + /** * Registers an event. - * + * * @param name The name of the event, used for error messages * @param c The event's class * @param events The Bukkit events this event applies to @@ -1540,10 +1554,10 @@ public static List> getStructures() { } // ================ COMMANDS ================ - + /** * Dispatches a command with calling command events - * + * * @param sender * @param command * @return Whether the command was run @@ -1568,39 +1582,39 @@ public static boolean dispatchCommand(final CommandSender sender, final String c return false; } } - + // ================ LOGGING ================ - + public static boolean logNormal() { return SkriptLogger.log(Verbosity.NORMAL); } - + public static boolean logHigh() { return SkriptLogger.log(Verbosity.HIGH); } - + public static boolean logVeryHigh() { return SkriptLogger.log(Verbosity.VERY_HIGH); } - + public static boolean debug() { return SkriptLogger.debug(); } - + public static boolean testing() { return debug() || Skript.class.desiredAssertionStatus(); } - + public static boolean log(final Verbosity minVerb) { return SkriptLogger.log(minVerb); } - + public static void debug(final String info) { if (!debug()) return; SkriptLogger.log(SkriptLogger.DEBUG, info); } - + /** * @see SkriptLogger#log(Level, String) */ @@ -1608,7 +1622,7 @@ public static void debug(final String info) { public static void info(final String info) { SkriptLogger.log(Level.INFO, info); } - + /** * @see SkriptLogger#log(Level, String) */ @@ -1616,7 +1630,7 @@ public static void info(final String info) { public static void warning(final String warning) { SkriptLogger.log(Level.WARNING, warning); } - + /** * @see SkriptLogger#log(Level, String) */ @@ -1625,54 +1639,54 @@ public static void error(final @Nullable String error) { if (error != null) SkriptLogger.log(Level.SEVERE, error); } - + /** * Use this in {@link Expression#init(Expression[], int, Kleenean, ch.njol.skript.lang.SkriptParser.ParseResult)} (and other methods that are called during the parsing) to log * errors with a specific {@link ErrorQuality}. - * + * * @param error * @param quality */ public static void error(final String error, final ErrorQuality quality) { SkriptLogger.log(new LogEntry(SkriptLogger.SEVERE, quality, error)); } - + private final static String EXCEPTION_PREFIX = "#!#! "; - + /** * Used if something happens that shouldn't happen - * + * * @param info Description of the error and additional information * @return an EmptyStacktraceException to throw if code execution should terminate. */ public static EmptyStacktraceException exception(final String... info) { return exception(null, info); } - + public static EmptyStacktraceException exception(final @Nullable Throwable cause, final String... info) { return exception(cause, null, null, info); } - + public static EmptyStacktraceException exception(final @Nullable Throwable cause, final @Nullable Thread thread, final String... info) { return exception(cause, thread, null, info); } - + public static EmptyStacktraceException exception(final @Nullable Throwable cause, final @Nullable TriggerItem item, final String... info) { return exception(cause, null, item, info); } - + /** * Maps Java packages of plugins to descriptions of said plugins. * This is only done for plugins that depend or soft-depend on Skript. */ private static Map pluginPackages = new HashMap<>(); private static boolean checkedPlugins = false; - + /** * Set by Skript when doing something that users shouldn't do. */ private static boolean tainted = false; - + /** * Set to true when an exception is thrown. */ @@ -1688,7 +1702,7 @@ public static void markErrored() { /** * Used if something happens that shouldn't happen - * + * * @param cause exception that shouldn't occur * @param info Description of the error and additional information * @return an EmptyStacktraceException to throw if code execution should terminate. @@ -1702,11 +1716,11 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina } // First error: gather plugin package information - if (!checkedPlugins) { + if (!checkedPlugins) { for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { if (plugin.getName().equals("Skript")) // Don't track myself! continue; - + PluginDescriptionFile desc = plugin.getDescription(); if (desc.getDepend().contains("Skript") || desc.getSoftDepend().contains("Skript")) { // Take actual main class out from the qualified name @@ -1715,24 +1729,24 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina for (int i = 0; i < parts.length - 1; i++) { name.append(parts[i]).append('.'); } - + // Put this to map pluginPackages.put(name.toString(), desc); if (Skript.debug()) Skript.info("Identified potential addon: " + desc.getFullName() + " (" + name.toString() + ")"); } } - + checkedPlugins = true; // No need to do this next time } - + String issuesUrl = "https://github.com/SkriptLang/Skript/issues"; - + logEx(); logEx("[Skript] Severe Error:"); logEx(info); logEx(); - + // Parse something useful out of the stack trace StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); Set stackPlugins = new HashSet<>(); @@ -1742,9 +1756,9 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina stackPlugins.add(e.getValue()); // Yes? Add it to list } } - + SkriptUpdater updater = Skript.getInstance().getUpdater(); - + // Check if server platform is supported if (tainted) { logEx("Skript is running with developer command-line options."); @@ -1786,7 +1800,7 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina String website = desc.getWebsite(); if (website != null && !website.isEmpty()) // Add website if found pluginsMessage.append(" (").append(desc.getWebsite()).append(")"); - + pluginsMessage.append(" "); } logEx(pluginsMessage.toString()); @@ -1799,12 +1813,12 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina String website = desc.getWebsite(); if (website != null && !website.isEmpty()) // Add website if found pluginsMessage.append(" (").append(desc.getWebsite()).append(")"); - + pluginsMessage.append(" "); } logEx(pluginsMessage.toString()); } - + logEx("You should try disabling those plugins one by one, trying to find which one causes it."); logEx("If the error doesn't disappear even after disabling all listed plugins, it is probably Skript issue."); logEx("In that case, you will be given instruction on how should you report it."); @@ -1812,7 +1826,7 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina logEx("Only if the author tells you to do so, report it to Skript's issue tracker."); } } - + logEx(); logEx("Stack trace:"); if (cause == null || cause.getStackTrace().length == 0) { @@ -1827,7 +1841,7 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina cause = cause.getCause(); first = false; } - + logEx(); logEx("Version Information:"); if (updater != null) { @@ -1863,14 +1877,14 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina logEx(); logEx("End of Error."); logEx(); - + return new EmptyStacktraceException(); } - + static void logEx() { SkriptLogger.LOGGER.severe(EXCEPTION_PREFIX); } - + static void logEx(final String... lines) { for (final String line : lines) SkriptLogger.LOGGER.severe(EXCEPTION_PREFIX + line); @@ -1885,7 +1899,7 @@ public static String getSkriptPrefix() { public static void info(final CommandSender sender, final String info) { sender.sendMessage(Utils.replaceEnglishChatStyles(getSkriptPrefix() + info)); } - + /** * @param message * @param permission @@ -1894,25 +1908,25 @@ public static void info(final CommandSender sender, final String info) { public static void broadcast(final String message, final String permission) { Bukkit.broadcast(Utils.replaceEnglishChatStyles(getSkriptPrefix() + message), permission); } - + public static void adminBroadcast(final String message) { broadcast(message, "skript.admin"); } - + /** * Similar to {@link #info(CommandSender, String)} but no [Skript] prefix is added. - * + * * @param sender * @param info */ public static void message(final CommandSender sender, final String info) { sender.sendMessage(Utils.replaceEnglishChatStyles(info)); } - + public static void error(final CommandSender sender, final String error) { sender.sendMessage(Utils.replaceEnglishChatStyles(getSkriptPrefix() + ChatColor.DARK_RED + error)); } - + /** * Gets the updater instance currently used by Skript. * @return SkriptUpdater instance. @@ -1921,5 +1935,5 @@ public static void error(final CommandSender sender, final String error) { public SkriptUpdater getUpdater() { return updater; } - + } diff --git a/src/main/java/ch/njol/skript/conditions/CondIsUsingFeature.java b/src/main/java/ch/njol/skript/conditions/CondIsUsingFeature.java new file mode 100644 index 00000000000..4ce1c0ef0bd --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsUsingFeature.java @@ -0,0 +1,81 @@ +/** + * 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.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.experiment.Experimented; + +@SuppressWarnings("NotNullFieldNotInitialized") +@Name("Is Using Experimental Feature") +@Description("Checks whether a script is using an experimental feature by name.") +@Examples({"the script is using \"example feature\"", + "on load:", + "\tif the script is using \"example feature\":", + "\t\tbroadcast \"You're using an experimental feature!\""}) +@Since("INSERT VERSION") +public class CondIsUsingFeature extends Condition { + + static { + Skript.registerCondition(CondIsUsingFeature.class, + "[the] [current] script is using %strings%", + "[the] [current] script is(n't| not) using %strings%"); + } + + private Expression names; + private Experimented snapshot; + + @SuppressWarnings("null") + @Override + public boolean init(Expression[] expressions, int pattern, Kleenean delayed, ParseResult result) { + //noinspection unchecked + this.names = (Expression) expressions[0]; + this.setNegated(pattern == 1); + this.snapshot = this.getParser().experimentSnapshot(); + return true; + } + + @Override + public boolean check(Event event) { + String[] array = names.getArray(event); + if (array.length == 0) + return true; + boolean isUsing = true; + for (@NotNull String object : array) { + isUsing &= snapshot.hasExperiment(object); + } + return isUsing ^ this.isNegated(); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "the current script " + (isNegated() ? "isn't" : "is") + " using " + names.toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java index 86d6a209d08..84ea8ceb50c 100644 --- a/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java +++ b/src/main/java/ch/njol/skript/lang/parser/ParserInstance.java @@ -19,6 +19,7 @@ package ch.njol.skript.lang.parser; import ch.njol.skript.ScriptLoader; +import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; import ch.njol.skript.config.Config; import ch.njol.skript.config.Node; @@ -32,7 +33,11 @@ import ch.njol.util.coll.CollectionUtils; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.lang.experiment.Experiment; +import org.skriptlang.skript.lang.experiment.ExperimentSet; +import org.skriptlang.skript.lang.experiment.Experimented; import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.script.ScriptEvent; import org.skriptlang.skript.lang.structure.Structure; @@ -44,8 +49,8 @@ import java.util.Map; import java.util.function.Function; -public final class ParserInstance { - +public final class ParserInstance implements Experimented { + private static final ThreadLocal PARSER_INSTANCES = ThreadLocal.withInitial(ParserInstance::new); /** @@ -394,11 +399,63 @@ public Node getNode() { public void setIndentation(String indentation) { this.indentation = indentation; } - + public String getIndentation() { return indentation; } + // Experiments API + + @Override + public boolean hasExperiment(String featureName) { + return Skript.experiments().isUsing(this.getCurrentScript(), featureName); + } + + + @Override + public boolean hasExperiment(Experiment experiment) { + return Skript.experiments().isUsing(this.getCurrentScript(), experiment); + } + + /** + * Marks this as using an experimental feature. + * @param experiment The feature to register. + */ + @ApiStatus.Internal + public void addExperiment(Experiment experiment) { + Script script = this.getCurrentScript(); + ExperimentSet set = script.getData(ExperimentSet.class, () -> new ExperimentSet()); + set.add(experiment); + } + + /** + * Marks this as no longer using an experimental feature (e.g. during de-registration or reload). + * @param experiment The feature to unregister. + */ + @ApiStatus.Internal + public void removeExperiment(Experiment experiment) { + Script script = this.getCurrentScript(); + @Nullable ExperimentSet set = script.getData(ExperimentSet.class); + if (set == null) + return; + set.remove(experiment); + } + + /** + * A snapshot of the experiments this script is currently known to be using. + * This is safe to retain during runtime (e.g. to defer a check) but will + * not see changes, such as if a script subsequently 'uses' another experiment. + * + * @return A snapshot of the current experiment flags in use + */ + public Experimented experimentSnapshot() { + Script script = this.getCurrentScript(); + @Nullable ExperimentSet set = script.getData(ExperimentSet.class); + if (set == null) + return new ExperimentSet(); + return new ExperimentSet(set); + } + // ParserInstance Data API /** @@ -409,13 +466,13 @@ public String getIndentation() { * {@code ParserInstance.registerData(MyData.class, MyData::new)} */ public static abstract class Data { - + private final ParserInstance parserInstance; - + public Data(ParserInstance parserInstance) { this.parserInstance = parserInstance; } - + protected final ParserInstance getParser() { return parserInstance; } @@ -427,13 +484,13 @@ protected final ParserInstance getParser() { public void onCurrentScriptChange(@Nullable Config currentScript) { } public void onCurrentEventsChange(Class @Nullable [] currentEvents) { } - + } - + private static final Map, Function> dataRegister = new HashMap<>(); // Should be Map, ? extends Data>, but that caused issues (with generics) in #getData(Class) private final Map, Data> dataMap = new HashMap<>(); - + /** * Registers a data class to all {@link ParserInstance}s. * @@ -444,11 +501,11 @@ public static void registerData(Class dataClass, Function dataFunction) { dataRegister.put(dataClass, dataFunction); } - + public static boolean isRegistered(Class dataClass) { return dataRegister.containsKey(dataClass); } - + /** * @return the data object for the given class from this {@link ParserInstance}, * or null (after {@code false} has been asserted) if the given data class isn't registered. @@ -465,7 +522,7 @@ public T getData(Class dataClass) { assert false; return null; } - + private List getDataInstances() { // List gave errors, so using this instead List dataList = new ArrayList<>(); @@ -536,5 +593,5 @@ public void setCurrentScript(@Nullable Config currentScript) { if (script != null) setActive(script); } - + } diff --git a/src/main/java/ch/njol/skript/registrations/Feature.java b/src/main/java/ch/njol/skript/registrations/Feature.java new file mode 100644 index 00000000000..7b9f19b1d7f --- /dev/null +++ b/src/main/java/ch/njol/skript/registrations/Feature.java @@ -0,0 +1,79 @@ +/** + * 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.registrations; + +import ch.njol.skript.SkriptAddon; +import ch.njol.skript.patterns.PatternCompiler; +import ch.njol.skript.patterns.SkriptPattern; +import org.skriptlang.skript.lang.experiment.Experiment; +import org.skriptlang.skript.lang.experiment.ExperimentRegistry; +import org.skriptlang.skript.lang.experiment.LifeCycle; + +/** + * Experimental feature toggles as provided by Skript itself. + */ +public enum Feature implements Experiment { + ; + + private final String codeName; + private final LifeCycle phase; + private final SkriptPattern compiledPattern; + + Feature(String codeName, LifeCycle phase, String... patterns) { + this.codeName = codeName; + this.phase = phase; + switch (patterns.length) { + case 0: + this.compiledPattern = PatternCompiler.compile(codeName); + break; + case 1: + this.compiledPattern = PatternCompiler.compile(patterns[0]); + break; + default: + this.compiledPattern = PatternCompiler.compile('(' + String.join("|", patterns) + ')'); + break; + } + } + + Feature(String codeName, LifeCycle phase) { + this(codeName, phase, codeName); + } + + public static void registerAll(SkriptAddon addon, ExperimentRegistry manager) { + for (Feature value : values()) { + manager.register(addon, value); + } + } + + @Override + public String codeName() { + return codeName; + } + + @Override + public LifeCycle phase() { + return phase; + } + + @Override + public SkriptPattern pattern() { + return compiledPattern; + } + +} diff --git a/src/main/java/ch/njol/skript/structures/StructUsing.java b/src/main/java/ch/njol/skript/structures/StructUsing.java new file mode 100644 index 00000000000..9e4d473969a --- /dev/null +++ b/src/main/java/ch/njol/skript/structures/StructUsing.java @@ -0,0 +1,93 @@ +/** + * 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.structures; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; +import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.experiment.Experiment; +import org.skriptlang.skript.lang.structure.Structure; + +@Name("Using Experimental Feature") +@Description({ + "Place at the top of a script file to enable an optional experimental feature.", + "For example, this might include " +}) +@Examples({ + "using 1.21", + "using my-cool-addon-feature" +}) +@Since("INSERT VERSION") +public class StructUsing extends Structure { + + public static final Priority PRIORITY = new Priority(15); + + static { + Skript.registerSimpleStructure(StructUsing.class, "using <.+>"); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private Experiment experiment; + + @Override + public boolean init(Literal @NotNull [] arguments, int pattern, ParseResult result, @Nullable EntryContainer container) { + this.enableExperiment(result.regexes.get(0).group()); + return true; + } + + private void enableExperiment(String name) { + this.experiment = Skript.experiments().find(name.trim()); + switch (experiment.phase()) { + case MAINSTREAM: + Skript.warning("The experimental feature '" + name + "' is now included by default and is no longer required."); + break; + case DEPRECATED: + Skript.warning("The experimental feature '" + name + "' is deprecated and may be removed in future versions."); + break; + case UNKNOWN: + Skript.warning("The experimental feature '" + name + "' was not found."); + } + this.getParser().addExperiment(experiment); + } + + @Override + public boolean load() { + return true; + } + + @Override + public Priority getPriority() { + return PRIORITY; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "using " + experiment.codeName(); + } + +} diff --git a/src/main/java/ch/njol/skript/test/runner/ExprExperimentalOnly.java b/src/main/java/ch/njol/skript/test/runner/ExprExperimentalOnly.java new file mode 100644 index 00000000000..b9fd408b0f5 --- /dev/null +++ b/src/main/java/ch/njol/skript/test/runner/ExprExperimentalOnly.java @@ -0,0 +1,68 @@ +/** + * 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.test.runner; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.NoDoc; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Experimental Only") +@Description("A do-nothing syntax that only parses when `example feature` is enabled.") +@NoDoc +public class ExprExperimentalOnly extends SimpleExpression { + + static { + if (TestMode.ENABLED) + Skript.registerExpression(ExprExperimentalOnly.class, Boolean.class, ExpressionType.SIMPLE, "experimental only"); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + return this.getParser().hasExperiment(TestFeatures.EXAMPLE_FEATURE); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "experimental only"; + } + + @Override + protected @Nullable Boolean[] get(Event event) { + return new Boolean[]{true}; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return Boolean.class; + } + +} diff --git a/src/main/java/ch/njol/skript/test/runner/TestFeatures.java b/src/main/java/ch/njol/skript/test/runner/TestFeatures.java new file mode 100644 index 00000000000..b975ff219f9 --- /dev/null +++ b/src/main/java/ch/njol/skript/test/runner/TestFeatures.java @@ -0,0 +1,87 @@ +/** + * 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.test.runner; + +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptAddon; +import ch.njol.skript.patterns.PatternCompiler; +import ch.njol.skript.patterns.SkriptPattern; +import org.skriptlang.skript.lang.experiment.Experiment; +import org.skriptlang.skript.lang.experiment.ExperimentRegistry; +import org.skriptlang.skript.lang.experiment.LifeCycle; + +/** + * Features available only in test scripts. + */ +public enum TestFeatures implements Experiment { + EXAMPLE_FEATURE("example feature", LifeCycle.STABLE), + DEPRECATED_FEATURE("deprecated feature", LifeCycle.DEPRECATED), + TEST_FEATURE("test", LifeCycle.EXPERIMENTAL, "test[ing]", "fizz[ ]buzz") + ; + + private final String codeName; + private final LifeCycle phase; + private final SkriptPattern compiledPattern; + + TestFeatures(String codeName, LifeCycle phase, String... patterns) { + this.codeName = codeName; + this.phase = phase; + switch (patterns.length) { + case 0: + this.compiledPattern = PatternCompiler.compile(codeName); + break; + case 1: + this.compiledPattern = PatternCompiler.compile(patterns[0]); + break; + default: + this.compiledPattern = PatternCompiler.compile('(' + String.join("|", patterns) + ')'); + break; + } + } + + TestFeatures(String codeName, LifeCycle phase) { + this(codeName, phase, codeName); + } + + public static void registerAll(SkriptAddon addon, ExperimentRegistry manager) { + for (TestFeatures value : values()) { + manager.register(addon, value); + } + } + + @Override + public String codeName() { + return codeName; + } + + @Override + public LifeCycle phase() { + return phase; + } + + @Override + public SkriptPattern pattern() { + return compiledPattern; + } + + static { + registerAll(Skript.getAddonInstance(), Skript.experiments()); + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/experiment/Experiment.java b/src/main/java/org/skriptlang/skript/lang/experiment/Experiment.java new file mode 100644 index 00000000000..d4067869507 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/experiment/Experiment.java @@ -0,0 +1,172 @@ +/** + * 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 org.skriptlang.skript.lang.experiment; + +import ch.njol.skript.patterns.PatternCompiler; +import ch.njol.skript.patterns.SkriptPattern; +import ch.njol.skript.registrations.Feature; +import org.jetbrains.annotations.ApiStatus; + +import java.util.Objects; + +/** + * An optional, potentially-experimental feature enabled per-script with the {@code using X} syntax. + * Experiments provided by Skript itself are found in {@link Feature}. + * This can also represent an unknown experiment 'used' by a script that was not declared or registered + * by Skript or any of its addons. + */ +public interface Experiment { + + @ApiStatus.Internal + static Experiment unknown(String text) { + return new UnmatchedExperiment(text); + } + + /** + * A constant experiment provider (designed for the use of addons). + * @param codeName The debug 'code name' of this feature. + * @param phase The stability of this feature. + * @param patterns What the user may write to match the feature. Defaults to the codename if not set. + * @return An experiment flag. + */ + static Experiment constant(String codeName, LifeCycle phase, String... patterns) { + return new ConstantExperiment(codeName, phase, patterns); + } + + /** + * A simple, printable code-name for this pattern for warnings and debugging. + * Ideally, this should be matched by one of the {@link #pattern()} entries. + * + * @return The code name of this experiment. + */ + String codeName(); + + /** + * @return The safety phase of this feature. + */ + LifeCycle phase(); + + /** + * @return Whether this feature was declared by Skript or a real extension. + */ + default boolean isKnown() { + return this.phase() != LifeCycle.UNKNOWN; + } + + /** + * @return The compiled matching pattern for this experiment + */ + SkriptPattern pattern(); + + /** + * @return Whether the usage pattern of this experiment matches the input text + */ + default boolean matches(String text) { + return this.pattern().match(text) != null; + } + +} + +/** + * A class for constant experiments. + */ +class ConstantExperiment implements Experiment { + + private final String codeName; + private final SkriptPattern compiledPattern; + private final LifeCycle phase; + + ConstantExperiment(String codeName, LifeCycle phase) { + this(codeName, phase, new String[0]); + } + + ConstantExperiment(String codeName, LifeCycle phase, String... patterns) { + this.codeName = codeName; + this.phase = phase; + switch (patterns.length) { + case 0: + this.compiledPattern = PatternCompiler.compile(codeName); + break; + case 1: + this.compiledPattern = PatternCompiler.compile(patterns[0]); + break; + default: + this.compiledPattern = PatternCompiler.compile(String.join("|", patterns)); + break; + } + } + + @Override + public String codeName() { + return codeName; + } + + @Override + public LifeCycle phase() { + return phase; + } + + @Override + public SkriptPattern pattern() { + return compiledPattern; + } + + @Override + public boolean matches(String text) { + return codeName.equals(text); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Experiment that = (Experiment) o; + return Objects.equals(this.codeName(), that.codeName()); + } + + @Override + public int hashCode() { + return codeName.hashCode(); + } + +} + +/** + * The dummy class for an unmatched experiment. + * This is something that was 'used' by a file but was never registered with Skript. + * These are kept so that they *can* be tested for (e.g. by a third-party extension that uses a post-registration + * experiment system). + */ +class UnmatchedExperiment extends ConstantExperiment { + + UnmatchedExperiment(String codeName) { + super(codeName, LifeCycle.UNKNOWN); + } + + @Override + public LifeCycle phase() { + return LifeCycle.UNKNOWN; + } + + @Override + public boolean isKnown() { + return false; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/experiment/ExperimentRegistry.java b/src/main/java/org/skriptlang/skript/lang/experiment/ExperimentRegistry.java new file mode 100644 index 00000000000..6c05e763ee1 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/experiment/ExperimentRegistry.java @@ -0,0 +1,162 @@ +/** + * 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 org.skriptlang.skript.lang.experiment; + +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptAddon; +import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.lang.script.Script; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * A manager for registering (and identifying) experimental feature flags. + */ +/* +* TODO +* This is designed to be (replaced by|refactored into) a proper registry when the registries rework PR +* is completed. The overall skeleton is designed to remain, so that there should be no breaking changes +* for anything using it. I.e. you will still be able to use Skript#experiments() and obtain 'this' class +* although these will just become helper methods for the proper registry behaviour. +* */ +public class ExperimentRegistry implements Experimented { + + private final Skript skript; + private final Set experiments; + + public ExperimentRegistry(Skript skript) { + this.skript = skript; + this.experiments = new LinkedHashSet<>(); + } + + /** + * Finds an experiment matching this name. If none exist, an 'unknown' one will be created. + * + * @param text The text provided by the user. + * @return An experiment. + */ + public @NotNull Experiment find(String text) { + if (experiments.isEmpty()) + return Experiment.unknown(text); + for (Experiment experiment : experiments) { + if (experiment.matches(text)) + return experiment; + } + return Experiment.unknown(text); + } + + /** + * @return All currently-registered experiments. + */ + public Experiment[] registered() { + return experiments.toArray(new Experiment[0]); + } + + /** + * Registers a new experimental feature flag, which will be available to scripts + * with the {@code using %name%} structure. + * + * @param addon The source of this feature. + * @param experiment The experimental feature flag. + */ + public void register(SkriptAddon addon, Experiment experiment) { + // the addon instance is requested for now in case we need it in future (for error triage) + this.experiments.add(experiment); + } + + /** + * @see #register(SkriptAddon, Experiment) + */ + public void registerAll(SkriptAddon addon, Experiment... experiments) { + for (Experiment experiment : experiments) { + this.register(addon, experiment); + } + } + + /** + * Unregisters an experimental feature flag. + * Loaded scripts currently using the flag will not have it disabled. + * + * @param addon The source of this feature. + * @param experiment The experimental feature flag. + */ + public void unregister(SkriptAddon addon, Experiment experiment) { + // the addon instance is requested for now in case we need it in future (for error triage) + this.experiments.remove(experiment); + } + + /** + * Creates (and registers) a new experimental feature flag, which will be available to scripts + * with the {@code using %name%} structure. + * + * @param addon The source of this feature. + * @param codeName The debug 'code name' of this feature. + * @param phase The stability of this feature. + * @param patterns What the user may write to match the feature. Defaults to the codename if not set. + * @return An experiment flag. + */ + public Experiment register(SkriptAddon addon, String codeName, LifeCycle phase, String... patterns) { + Experiment experiment = Experiment.constant(codeName, phase, patterns); + this.register(addon, experiment); + return experiment; + } + + @Override + public boolean hasExperiment(Experiment experiment) { + return experiments.contains(experiment); + } + + @Override + public boolean hasExperiment(String featureName) { + return this.find(featureName).isKnown(); + } + + /** + * Whether a script is using an experiment. + * @param script The script to test + * @param experiment The experimental flag + * @return Whether the script declared itself as `using X` + */ + public boolean isUsing(Script script, Experiment experiment) { + if (script == null) + return false; + @Nullable ExperimentSet set = script.getData(ExperimentSet.class); + if (set == null) + return false; + return set.hasExperiment(experiment); + } + + /** + * Whether a script is using an experiment. + * @param script The script to test + * @param featureName The experimental flag's name + * @return Whether the script declared itself as `using X` + */ + public boolean isUsing(Script script, String featureName) { + if (script == null) + return false; + @Nullable ExperimentSet set = script.getData(ExperimentSet.class); + if (set == null) + return false; + return set.hasExperiment(featureName); + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/experiment/ExperimentSet.java b/src/main/java/org/skriptlang/skript/lang/experiment/ExperimentSet.java new file mode 100644 index 00000000000..bce336ffb55 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/experiment/ExperimentSet.java @@ -0,0 +1,54 @@ +/** + * 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 org.skriptlang.skript.lang.experiment; + +import org.jetbrains.annotations.NotNull; +import org.skriptlang.skript.lang.script.ScriptData; + +import java.util.Collection; +import java.util.LinkedHashSet; + +/** + * A container for storing and testing experiments. + */ +public class ExperimentSet extends LinkedHashSet implements ScriptData, Experimented { + + public ExperimentSet(@NotNull Collection collection) { + super(collection); + } + + public ExperimentSet() { + super(); + } + + @Override + public boolean hasExperiment(Experiment experiment) { + return this.contains(experiment); + } + + @Override + public boolean hasExperiment(String featureName) { + for (Experiment experiment : this) { + if (experiment.matches(featureName)) + return true; + } + return false; + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/experiment/Experimented.java b/src/main/java/org/skriptlang/skript/lang/experiment/Experimented.java new file mode 100644 index 00000000000..77c68b5e4bc --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/experiment/Experimented.java @@ -0,0 +1,45 @@ +/** + * 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 org.skriptlang.skript.lang.experiment; + +import ch.njol.skript.Skript; + +/** + * Something that can have experimental features enabled for. + * The only intended implementation of this is the {@link org.skriptlang.skript.lang.script.Script}, + * however it is left open for configuration files, etc. that may use this functionality in the future. + */ +@FunctionalInterface +public interface Experimented { + + /** + * @param experiment The experimental feature to test. + * @return Whether this uses the given feature. + */ + boolean hasExperiment(Experiment experiment); + + /** + * @param featureName The name of the experimental feature to test. + * @return Whether this has a feature with the given name. + */ + default boolean hasExperiment(String featureName) { + return Skript.experiments().find(featureName).isKnown(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/experiment/LifeCycle.java b/src/main/java/org/skriptlang/skript/lang/experiment/LifeCycle.java new file mode 100644 index 00000000000..ff1479464af --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/experiment/LifeCycle.java @@ -0,0 +1,69 @@ +/** + * 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 org.skriptlang.skript.lang.experiment; + +/** + * The life cycle phase of an {@link Experiment}. + */ +public enum LifeCycle { + /** + * A feature that is expected to be safe and (at least) semi-permanent. + * This can be used for long-term features that are kept behind toggles to prevent breaking changes. + */ + STABLE(false), + /** + * An experimental, preview feature designed to be used with caution. + * Features in the experimental phase may be subject to changes or removal at short notice. + */ + EXPERIMENTAL(false), + /** + * A feature at the end of its life cycle, being prepared for removal. + * Scripts will report a deprecation warning on load if a deprecated feature is used. + */ + DEPRECATED(true), + /** + * Represents a feature that was previously opt-in (or experimental) but is now a part of the default set. + * I.e. it no longer needs to be enabled using a feature flag. + * This will provide a little note to the user on load informing them they no longer need to + * use this feature flag. + */ + MAINSTREAM(true), + /** + * Represents an unregistered, unknown feature. + * This occurs when a user tags a script as {@code using X}, where {@code X} is not a registered + * feature provided by any addon or extension. + * Scripts will report a warning on load if an unknown feature is used, but this will not prevent + * the loading cycle. + */ + UNKNOWN(true); + + private final boolean warn; + + LifeCycle(boolean warn) { + this.warn = warn; + } + + /** + * @return Whether using a feature of this type will produce a warning on load. + */ + public boolean warn() { + return warn; + } + +} diff --git a/src/test/skript/tests/syntaxes/structures/StructUsing.sk b/src/test/skript/tests/syntaxes/structures/StructUsing.sk new file mode 100644 index 00000000000..61bc948a284 --- /dev/null +++ b/src/test/skript/tests/syntaxes/structures/StructUsing.sk @@ -0,0 +1,19 @@ +test "using before declaration": + assert the current script is using "example feature" with "using feature failed" + assert experimental only is true with "feature-conditional syntax failed" + +using example feature +using fizz buzz +using deprecated feature + +test "using": + assert the current script is using "example feature" with "using feature failed" + assert the current script is using "fizz buzz" with "using exact failed" + assert the current script is using "fizzbuzz" with "using other pattern failed" + assert the current script is using "test" with "using codename failed" + assert the current script is using "testing" with "using alt pattern failed" + assert the current script is not using "foo bar" with "using not present failed" + assert experimental only is true with "feature-conditional syntax failed" + + parse if the current script is using "foo bar fake experiment": + foo bar fake syntax! From e7b17b016d7643077c23fc203a1dd64052d441d0 Mon Sep 17 00:00:00 2001 From: Patrick Miller Date: Thu, 30 May 2024 03:56:43 -0400 Subject: [PATCH 08/23] Particle Compatibility Improvements (#6716) * first pass at updating lang entries * Rewrite Particle lang entries A lot of particles probably don't work though * Add a test * Fix block marker particles * Add support for multiple expressions in a particle pattern * first pass at new data suppliers * Fix a few issues related to new suppliers * Data supplier touchups * Fix broken particles --------- Co-authored-by: Moderocky --- .../njol/skript/util/visual/VisualEffect.java | 28 +- .../skript/util/visual/VisualEffects.java | 200 +++-- src/main/resources/lang/default.lang | 795 +++++++++++------- .../regressions/3284-itemcrack particle.sk | 2 +- .../tests/syntaxes/effects/EffVisualEffect.sk | 112 +++ 5 files changed, 760 insertions(+), 377 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/effects/EffVisualEffect.sk diff --git a/src/main/java/ch/njol/skript/util/visual/VisualEffect.java b/src/main/java/ch/njol/skript/util/visual/VisualEffect.java index 194fd833bbe..7965ba5c722 100644 --- a/src/main/java/ch/njol/skript/util/visual/VisualEffect.java +++ b/src/main/java/ch/njol/skript/util/visual/VisualEffect.java @@ -23,10 +23,13 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.SyntaxElement; +import ch.njol.skript.lang.util.ContextlessEvent; import ch.njol.util.Kleenean; import ch.njol.yggdrasil.YggdrasilSerializable; +import org.bukkit.Bukkit; import org.bukkit.Effect; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.Particle; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; @@ -36,8 +39,6 @@ public class VisualEffect implements SyntaxElement, YggdrasilSerializable { - private static final boolean HAS_REDSTONE_DATA = Skript.classExists("org.bukkit.Particle$DustOptions"); - private VisualEffectType type; @Nullable @@ -46,14 +47,29 @@ public class VisualEffect implements SyntaxElement, YggdrasilSerializable { private float dX, dY, dZ = 0f; public VisualEffect() {} - + @SuppressWarnings({"null", "ConstantConditions"}) @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { type = VisualEffects.get(matchedPattern); - if (exprs.length > 4 && exprs[0] != null) { - data = exprs[0].getSingle(null); + if (exprs.length > 4) { + int exprCount = exprs.length - 4; // some effects might have multiple expressions + ContextlessEvent event = ContextlessEvent.get(); + if (exprCount == 1) { + data = exprs[0] != null ? exprs[0].getSingle(event) : null; + } else { // provide an array of expression values + Object[] dataArray = new Object[exprCount]; + for (int i = 0; i < exprCount; i++) + dataArray[i] = exprs[i] != null ? exprs[i].getSingle(event) : null; + data = dataArray; + } + } + + if (parseResult.hasTag("barrierbm")) { // barrier compatibility + data = Bukkit.createBlockData(Material.BARRIER); + } else if (parseResult.hasTag("lightbm")) { // light compatibility + data = Bukkit.createBlockData(Material.LIGHT); } if ((parseResult.mark & 1) != 0) { @@ -100,7 +116,7 @@ public void play(@Nullable Player[] ps, Location l, @Nullable Entity e, int coun } // Some particles use offset as RGB color codes - if (type.isColorable() && (!HAS_REDSTONE_DATA || particle != (VisualEffects.OLD_REDSTONE_PARTICLE != null ? VisualEffects.OLD_REDSTONE_PARTICLE : Particle.DUST)) && data instanceof ParticleOption) { + if (type.isColorable() && data instanceof ParticleOption) { ParticleOption option = ((ParticleOption) data); dX = option.getRed(); dY = option.getGreen(); diff --git a/src/main/java/ch/njol/skript/util/visual/VisualEffects.java b/src/main/java/ch/njol/skript/util/visual/VisualEffects.java index 60014886646..dd9d31456c9 100644 --- a/src/main/java/ch/njol/skript/util/visual/VisualEffects.java +++ b/src/main/java/ch/njol/skript/util/visual/VisualEffects.java @@ -19,8 +19,8 @@ package ch.njol.skript.util.visual; import ch.njol.skript.Skript; -import ch.njol.skript.aliases.Aliases; import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.SyntaxElementInfo; import ch.njol.skript.localization.Language; @@ -29,13 +29,14 @@ import ch.njol.skript.util.ColorRGB; import ch.njol.skript.util.Direction; import ch.njol.skript.util.SkriptColor; +import ch.njol.skript.util.Timespan; import ch.njol.skript.variables.Variables; import ch.njol.util.StringUtils; import ch.njol.util.coll.iterator.SingleItemIterator; import org.bukkit.*; import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; import org.bukkit.inventory.ItemStack; -import org.bukkit.material.MaterialData; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.eclipse.jdt.annotation.Nullable; @@ -53,7 +54,6 @@ public class VisualEffects { private static final boolean NEW_EFFECT_DATA = Skript.classExists("org.bukkit.block.data.BlockData"); - private static final boolean HAS_REDSTONE_DATA = Skript.classExists("org.bukkit.Particle$DustOptions"); private static final Map> effectTypeModifiers = new HashMap<>(); private static SyntaxElementInfo elementInfo; @@ -120,37 +120,15 @@ private static void registerDataSupplier(String id, BiFunction { if (visualEffectTypes != null) // Already registered return; - // Colorables - registerColorable("Particle.SPELL_MOB"); - registerColorable("Particle.SPELL_MOB_AMBIENT"); - registerColorable("Particle.REDSTONE"); - registerColorable("Particle.NOTE"); // Data suppliers registerDataSupplier("Effect.POTION_BREAK", (raw, location) -> @@ -161,72 +139,132 @@ private static void registerDataSupplier(String id, BiFunction { - Color color = raw == null ? defaultColor : (Color) raw; - return new ParticleOption(color, 1); + // Useful: https://minecraft.wiki/w/Particle_format + + /* + * Particles with BlockData DataType + */ + final BiFunction blockDataSupplier = (raw, location) -> { + if (raw instanceof Object[]) { // workaround for modern pattern since it contains a choice + Object[] data = (Object[]) raw; + raw = data[0] != null ? data[0] : data[1]; + } + if (raw == null) + return Bukkit.createBlockData(Material.AIR); + if (raw instanceof ItemType) + return Bukkit.createBlockData(((ItemType) raw).getRandom().getType()); + return raw; + }; + registerDataSupplier("Particle.BLOCK", blockDataSupplier); + registerDataSupplier("Particle.BLOCK_CRACK", blockDataSupplier); + registerDataSupplier("Particle.BLOCK_DUST", blockDataSupplier); + + registerDataSupplier("Particle.BLOCK_MARKER", blockDataSupplier); + + registerDataSupplier("Particle.DUST_PILLAR", blockDataSupplier); + + registerDataSupplier("Particle.FALLING_DUST", blockDataSupplier); + + /* + * Particles with DustOptions DataType + */ + final Color defaultColor = SkriptColor.LIGHT_RED; + final BiFunction dustOptionsSupplier = (raw, location) -> { + Object[] data = (Object[]) raw; + Color color = data[0] != null ? (Color) data[0] : defaultColor; + float size = data[1] != null ? (Float) data[1] : 1; + return new Particle.DustOptions(color.asBukkitColor(), size); + }; + registerDataSupplier("Particle.DUST", dustOptionsSupplier); + registerDataSupplier("Particle.REDSTONE", dustOptionsSupplier); + + /* + * Particles with Color DataType + */ + registerDataSupplier("Particle.ENTITY_EFFECT", (raw, location) -> { + if (raw == null) + return defaultColor.asBukkitColor(); + return ((Color) raw).asBukkitColor(); }); - registerDataSupplier("Particle.SPELL_MOB_AMBIENT", (raw, location) -> { - Color color = raw == null ? defaultColor : (Color) raw; + final BiFunction oldColorSupplier = (raw, location) -> { + Color color = raw != null ? (Color) raw : defaultColor; return new ParticleOption(color, 1); + }; + registerColorable("Particle.SPELL_MOB"); + registerDataSupplier("Particle.SPELL_MOB", oldColorSupplier); + registerColorable("Particle.SPELL_MOB_AMBIENT"); + registerDataSupplier("Particle.SPELL_MOB_AMBIENT", oldColorSupplier); + + final BiFunction itemStackSupplier = (raw, location) -> { + ItemStack itemStack = null; + if (raw instanceof ItemType) + itemStack = ((ItemType) raw).getRandom(); + if (itemStack == null || ItemUtils.isAir(itemStack.getType())) // item crack air is not allowed + itemStack = new ItemStack(Material.IRON_SWORD); + if (IS_ITEM_CRACK_MATERIAL) + return itemStack.getType(); + return itemStack; + }; + registerDataSupplier("Particle.ITEM", itemStackSupplier); + registerDataSupplier("Particle.ITEM_CRACK", itemStackSupplier); + + /* + * Particles with other DataTypes + */ + registerDataSupplier("Particle.DUST_COLOR_TRANSITION", (raw, location) -> { + Object[] data = (Object[]) raw; + Color fromColor = data[0] != null ? (Color) data[0] : defaultColor; + Color toColor = data[1] != null ? (Color) data[1] : defaultColor; + float size = data[2] != null ? (Float) data[2] : 1; + return new Particle.DustTransition(fromColor.asBukkitColor(), toColor.asBukkitColor(), size); }); - registerDataSupplier("Particle.REDSTONE", (raw, location) -> { - Color color = raw == null ? defaultColor : (Color) raw; - ParticleOption particleOption = new ParticleOption(color, 1); - - if (HAS_REDSTONE_DATA && (OLD_REDSTONE_PARTICLE == null || OLD_REDSTONE_PARTICLE.getDataType() == Particle.DustOptions.class)) { - return new Particle.DustOptions(particleOption.getBukkitColor(), particleOption.size); - } else { - return particleOption; - } - }); + + // uses color differently + registerColorable("Particle.NOTE"); + // TODO test how this works registerDataSupplier("Particle.NOTE", (raw, location) -> { int colorValue = (int) (((Number) raw).floatValue() * 255); ColorRGB color = new ColorRGB(colorValue, 0, 0); return new ParticleOption(color, 1); }); - registerDataSupplier("Particle.ITEM_CRACK", (raw, location) -> { - ItemStack itemStack = Aliases.javaItemType("iron sword").getRandom(); - if (raw instanceof ItemType) { - ItemStack rand = ((ItemType) raw).getRandom(); - if (rand != null) - itemStack = rand; - } else if (raw != null) { - return raw; - } - assert itemStack != null; - if (OLD_ITEM_CRACK_PARTICLE == null || OLD_ITEM_CRACK_PARTICLE.getDataType() == Material.class) - return itemStack.getType(); - return itemStack; + // Float DataType, represents "the angle the particle displays at in radians" + registerDataSupplier("Particle.SCULK_CHARGE", (raw, location) -> raw != null ? raw : 0); + + // Integer DataType, represents "the delay in ticks" + registerDataSupplier("Particle.SHRIEK", (raw, location) -> { + int delay = 0; + if (raw instanceof Timespan) + delay = (int) Math.min(Math.max(((Timespan) raw).getTicks(), 0), Integer.MAX_VALUE); + return delay; }); - BiFunction crackDustBiFunction = (raw, location) -> { - if (raw == null) { - return Material.STONE.getData(); - } else if (raw instanceof ItemType) { - ItemStack rand = ((ItemType) raw).getRandom(); - if (NEW_EFFECT_DATA) { - return Bukkit.createBlockData(rand != null ? rand.getType() : Material.STONE); - } else { - if (rand == null) - return Material.STONE.getData(); - - @SuppressWarnings("deprecation") - MaterialData type = rand.getData(); - assert type != null; - return type; - } - } else { - return raw; - } - }; - registerDataSupplier("Particle.BLOCK_CRACK", crackDustBiFunction); - registerDataSupplier("Particle.BLOCK_DUST", crackDustBiFunction); - registerDataSupplier("Particle.FALLING_DUST", crackDustBiFunction); + registerDataSupplier("Particle.VIBRATION", (raw, location) -> VibrationUtils.buildVibration((Object[]) raw, location)); generateTypes(); }); } + // exists to avoid NoClassDefFoundError from Vibration + private static final class VibrationUtils { + private static Vibration buildVibration(Object[] data, Location location) { + int arrivalTime = -1; + if (data[1] != null) + arrivalTime = (int) Math.min(Math.max(((Timespan) data[1]).getTicks(), 0), Integer.MAX_VALUE); + if (data[0] instanceof Entity) { + Entity entity = (Entity) data[0]; + if (arrivalTime == -1) + arrivalTime = (int) (location.distance(entity.getLocation()) / 20); + //noinspection removal - new constructor only exists on newer versions + return new Vibration(location, new Vibration.Destination.EntityDestination(entity), arrivalTime); + } + // assume it's a location + Location destination = data[0] != null ? (Location) data[0] : location; + if (arrivalTime == -1) + arrivalTime = (int) (location.distance(destination) / 20); + //noinspection removal - new constructor only exists on newer versions + return new Vibration(location, new Vibration.Destination.BlockDestination(destination), arrivalTime); + } + } + } diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index 21bfe5f6092..9f7df2c9e4c 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1397,15 +1397,6 @@ visual effects: iron_golem_rose: name: iron golem offering rose @an pattern: (iron golem [offering] rose|rose in hand) - villager_heart: - name: villager hearts @x - pattern: villager hearts - villager_angry: - name: angry villager entity @an - pattern: angry villager entity - villager_happy: - name: happy villager entity @a - pattern: happy villager entity witch_magic: name: witch magic @- pattern: witch magic @@ -1458,324 +1449,550 @@ visual effects: name: hurt by explosion @- pattern: (hurt|damage) by explosion particle: - fireworks_spark: - name: firework's spark @- - pattern: firework['s] spark - crit: - name: critical hit @a - pattern: (critical hit|crit) [spark] - crit_magic: - name: magical critical hit @a - pattern: (magic[al]|blue) (critical hit|crit) [spark] - spell_mob: - name: potion swirl @a - pattern: [%-color%] potion swirl - spell_mob_ambient: - name: transparent potion swirl @a - pattern: [%-color%] transparent potion swirl - spell: - name: spell @a - pattern: (spell|thrown potion|lingering potion) - spell_instant: - name: spell @a - pattern: instant (spell|thrown potion) - spell_witch: - name: witch spell @- - pattern: (witch (magic|spell)|purple spark) - note: - name: note @a - pattern: note [(of|with) [colo[u]r] %number%] - portal: - name: portal @a - pattern: [nether] portal - enchantment_table: - name: flying glyph @a - pattern: (flying (glyph|letter)|enchantment table) - flame: - name: flame @- - pattern: (flame|fire) - lava: - name: lava pop @a - pattern: lava pop - footstep: - name: footstep @x - pattern: footsteps - water_splash: - name: water splash @a - pattern: [water] splash - smoke_normal: - name: smoke particle @a - pattern: smoke particle - explosion_huge: - name: huge explosion @a - pattern: huge explosion - explosion_large: - name: large explosion @a - pattern: large explosion - explosion_normal: - name: explosion @an - pattern: explosion - suspended_depth: - name: void fog @- - pattern: void fog - town_aura: - name: small smoke @- - pattern: (small smoke|town aura) - cloud: - name: cloud @a - pattern: cloud - redstone: - name: coloured dust @- - pattern: [%-color%] [colo[u]red] dust - snowball: - name: snowball break @a - pattern: snowball break - drip_water: - name: water drip @a - pattern: water drip - drip_lava: - name: lava drip @a - pattern: lava drip - snow_shovel: - name: snow shovel @- - pattern: (snow shovel|snow(man| golem) spawn) - slime: - name: slime @- - pattern: slime - heart: - name: heart @a - pattern: heart - villager_angry: + + angry_villager: # added in 1.20.5 name: angry villager @a pattern: (angry villager|[villager] thundercloud) - villager_happy: - name: happy villager @- - pattern: (happy villager|green spark) - smoke_large: - name: large smoke @- - pattern: large smoke - item_crack: - name: item crack @- - pattern: %itemtype% item (break|crack)[ing] - block_crack: + villager_angry: # for versions below 1.20.5 + name: angry villager @a + pattern: (angry villager|[villager] thundercloud) + + ash: # added in 1.16 + name: ash @- + pattern: ash + + block: # added in 1.20.5 + name: block @- + pattern: (%-blockdata/itemtype% break[ing]|[sprinting] dust of %-blockdata/itemtype%) + block_crack: # for versions below 1.20.5 name: block break @- - pattern: %itemtype% break[ing] - block_dust: + pattern: %blockdata/itemtype% break[ing] + block_dust: # for versions below 1.20.5 name: block dust @- - pattern: [sprinting] dust of [%itemtype%] - end_rod: - name: end rod @- - pattern: end rod - falling_dust: - name: falling dust @- - pattern: falling dust of [%itemtype%] + pattern: [sprinting] dust of %blockdata/itemtype% - # 1.11 particles - totem: - name: totem @- - pattern: totem - spit: - name: spit @- - pattern: spit + block_marker: # added in 1.18 + name: block marker @a + pattern: (barrierbm:barrier|lightbm:light|%-blockdata/itemtype% block marker) + barrier: + name: barrier @a + pattern: barrier + light: # added in 1.17 + name: light @- + pattern: light - # 1.13 particles - squid_ink: - name: squid ink @- - pattern: squid ink - bubble_pop: - name: bubble pop @- - pattern: bubble pop - current_down: - name: current down @- - pattern: (current|bubble column) down - bubble_column_up: + bubble: # added in 1.20.5 + name: bubble @- + pattern: [water] bubble + water_bubble: # for versions below 1.20.5 + name: water bubble @- + pattern: [water] bubble + + bubble_column_up: # added in 1.13 name: bubble column up @- pattern: (current|bubble column) up - nautilus: - name: nautilus @- - pattern: nautilus - dolphin: - name: dolphin @- - pattern: dolphin - # 1.14 particles - sneeze: - name: sneeze @- - pattern: sneeze - campfire_cosy_smoke: + bubble_pop: # added in 1.13 + name: bubble pop @- + pattern: bubble pop + + campfire_cosy_smoke: # added in 1.14 name: campfire cosy smoke @- pattern: campfire co(s|z)y smoke - campfire_signal_smoke: + + campfire_signal_smoke: # added in 1.14 name: campfire signal smoke @- pattern: campfire signal smoke - composter: + + cherry_leaves: # added in 1.20 + name: cherry leaves @- + pattern: cherry leaves + + cloud: + name: cloud @a + pattern: cloud + + composter: # added in 1.14 name: composter @- - pattern: composter particle - flash: - name: flash @- - pattern: flash - falling_lava: - name: falling lava @- - pattern: falling lava - landing_lava: - name: landing lava @- - pattern: landing lava - falling_water: - name: falling water @- - pattern: falling water - barrier: - name: barrier @a - pattern: barrier - damage_indicator: + pattern: composter [particle] + + crimson_spore: # added in 1.16 + name: crimson spore @- + pattern: crimson spore + + crit: + name: critical hit @a + pattern: (critical hit|crit) [spark] + + current_down: # added in 1.13 + name: current down @- + pattern: (current|bubble column) down + + damage_indicator: # added in 1.14 name: damage indicator @- pattern: damage indicator - dragon_breath: + + dolphin: # added in 1.13 + name: dolphin @- + pattern: dolphin + + dragon_breath: # added in 1.14 name: dragon breath @- - pattern: dragon[s] breath - mob_appearance: - name: mob appearance @- - pattern: (mob appearance|guardian ghost) - suspended: - name: suspended @- - pattern: (suspended|underwater) - sweep_attack: - name: sweep attack @- - pattern: sweep attack - water_bubble: - name: water bubble @- - pattern: water bubble - water_wake: - name: water wake @- - pattern: water wake - water_drop: - name: water drop @- - pattern: water drop + pattern: dragon[[']s] breath + + dripping_dripstone_lava: # added in 1.17 + name: dripping dripstone lava @- + pattern: dripping dripstone lava + + dripping_dripstone_water: # added in 1.17 + name: dripping dripstone water @- + pattern: dripping dripstone water - # 1.15 particles - dripping_honey: + dripping_honey: # added in 1.15 name: dripping honey @- pattern: dripping honey - falling_honey: + + dripping_lava: # added in 1.20.5 + name: dripping lava @a + pattern: (dripping lava|lava drip) + drip_lava: # for versions below 1.20.5 + name: lava drip @a + pattern: (dripping lava|lava drip) + + dripping_obsidian_tear: # added in 1.16 + name: dripping obsidian tear @- + pattern: dripping obsidian tear + + dripping_water: # added in 1.20.5 + name: dripping water + pattern: (dripping water|water drip) + drip_water: # for versions below 1.20.5 + name: water drip @a + pattern: (dripping water|water drip) + + dust: # added in 1.20.5 + name: dust @- + pattern: [%-color%] [colo[u]red] dust [with size %-float%] + redstone: # for versions below 1.20.5 + name: coloured dust @- + pattern: [%-color%] [colo[u]red] dust [with size %-float%] + + dust_color_transition: # added in 1.17 + name: dust color transition @a + pattern: dust colo[u]r transition [from %-color%] [to %-color%] [with size %-float%] + + dust_pillar: # added in 1.20.5 (for 1.21) + name: dust pillar @a + pattern: %blockdata/itemtype% dust pillar + + dust_plume: # added in 1.20.3 + name: dust plume @a + pattern: dust plume + + effect: # added in 1.20.5 + name: effect @an + pattern: (effect|spell|thrown potion) # lingering is part of entity_effect + spell: # for versions below 1.20.5 + name: spell @a + pattern: (effect|spell|thrown potion|lingering potion) + + egg_crack: # added in 1.20 + name: egg crack @an + pattern: [sniffer] egg crack + + elder_guardian: # added in 1.20.5 + name: elder guardian @- + pattern: (elder guardian|mob appearance|guardian ghost) + mob_appearance: # for versions below 1.20.5 + name: mob appearance @- + pattern: (elder guardian|mob appearance|guardian ghost) + + electric_spark: + name: electric spark @- + pattern: electric spark + + enchant: # added in 1.20.5 + name: enchant + pattern: (enchant|flying (glyph|letter)|enchantment table) + enchantment_table: # for versions below 1.20.5 + name: flying glyph @a + pattern: (enchant|flying (glyph|letter)|enchantment table) + + enchanted_hit: # added in 1.20.5 + name: enchanted hit @an + pattern: (enchanted hit|(magic[al]|blue) (critical hit|crit) [spark]) + crit_magic: # for versions below 1.20.5 + name: magical critical hit @a + pattern: (enchanted hit|(magic[al]|blue) (critical hit|crit) [spark]) + + end_rod: + name: end rod @- + pattern: end rod + + entity_effect: # added in 1.20.5 + name: entity effect @an + pattern: (lingering potion|[%-color%] [transparent] potion swirl) + # TODO ambient_entity_effect seems to exist, but is not supported by spigot particle enum? + spell_mob: # for versions below 1.20.5 + name: potion swirl @a + pattern: [%-color%] potion swirl + spell_mob_ambient: # for versions below 1.20.5 + name: transparent potion swirl @a + pattern: [%-color%] transparent potion swirl + + # TODO explosions are a mess (see explosion_normal, explosion_large, explosion_huge) + explosion: # added in 1.20.5 + name: large explosion @a + pattern: large explosion + explosion_large: # for versions below 1.20.5 + name: large explosion @a + pattern: large explosion + + explosion_emitter: # added in 1.20.5 + name: explosion emitter @an + pattern: (explosion emitter|huge explosion) + explosion_huge: # for versions below 1.20.5 + name: huge explosion @a + pattern: (explosion emitter|huge explosion) + + falling_dripstone_lava: # added in 1.17 + name: falling dripstone lava @- + pattern: falling dripstone lava + + falling_dripstone_water: # added in 1.17 + name: falling dripstone water @- + pattern: falling dripstone water + + falling_dust: + name: falling dust @- + pattern: falling dust of %blockdata/itemtype% + + falling_honey: # added in 1.15 name: falling honey @- pattern: falling honey - landing_honey: - name: landing honey @- - pattern: landing honey - falling_nectar: + + falling_lava: # added in 1.14 + name: falling lava @- + pattern: falling lava + + falling_nectar: # added in 1.15 name: falling nectar @- pattern: falling nectar - # 1.16 particles - ash: - name: ash @- - pattern: ash - crimson_spore: - name: crimson spore @- - pattern: crimson spore - soul_fire_flame: - name: soul fire flame @- - pattern: soul fire flame - warped_spore: - name: warped spore @- - pattern: warped spore - dripping_obsidian_tear: - name: dripping obsidian tear @- - pattern: dripping obsidian tear - falling_obsidian_tear: + falling_obsidian_tear: # added in 1.16 name: falling obsidian tear @- pattern: falling obsidian tear - landing_obsidian_tear: - name: landing obsidian tear @- - pattern: landing obsidian tear - soul: - name: soul @- - pattern: soul - reverse_portal: - name: reverse portal @- - pattern: reverse portal - white_ash: - name: white ash @- - pattern: white ash - # 1.17 particles - light: - name: light @- - pattern: light - #dust_color_transition: - #name: dust color transition @a - #pattern: dust colo[u]r transition [from %-color%] [to %-color%] [with size %-number%] - #vibration: - #name: vibration @a - #pattern: vibration - falling_spore_blossom: + + falling_spore_blossom: # added in 1.17 name: falling spore blossom @- pattern: falling spore blossom - spore_blossom_air: - name: spore blossom air @- - pattern: spore blossom air - small_flame: - name: small flame @- - pattern: small flame - snowflake: - name: snowflake @- - pattern: snowflake - dripping_dripstone_lava: - name: dripping dripstone lava @- - pattern: dripping dripstone lava - falling_dripstone_lava: - name: falling dripstone lava @- - pattern: falling dripstone lava - dripping_dripstone_water: - name: dripping dripstone water @- - pattern: dripping dripstone water - falling_dripstone_water: - name: falling dripstone water @- - pattern: falling dripstone water - glow_squid_ink: - name: glow squid ink @- - pattern: glow squid ink - glow: + + falling_water: # added in 1.14 + name: falling water @- + pattern: falling water + + firework: # added in 1.20.5 + name: firework @- + pattern: (firework|firework['s] spark) + fireworks_spark: # for versions below 1.20.5 + name: firework's spark @- + pattern: (firework|firework['s] spark) + + fishing: # added in 1.20.5 + name: water wake @- + pattern: (fishing|water wake) + water_wake: # for versions below 1.20.5 + name: water wake @- + pattern: (fishing|water wake) + + flame: + name: flame @- + pattern: (flame|fire) + + flash: # added in 1.14 + name: flash @- + pattern: flash + + glow: # added in 1.17 name: glow @- pattern: glow - wax_on: - name: wax on @- - pattern: wax on - wax_off: - name: wax off @- - pattern: wax off - electric_spark: - name: electric spark @- - pattern: electric spark - scrape: + + glow_squid_ink: # added in 1.17 + name: glow squid ink @- + pattern: glow squid ink + + gust: # added in 1.20.5 (for 1.21) + name: gust @a + pattern: gust + + gust_emitter_large: # added in 1.20.5 (for 1.21) + name: large gust emitter @a + pattern: large gust emitter + + gust_emitter_small: # added in 1.20.5 (for 1.21) + name: small gust emitter @a + pattern: small gust emitter + + happy_villager: # added in 1.20.5 + name: happy villager @- + pattern: (happy villager|green spark) + villager_happy: + name: happy villager @- + pattern: (happy villager|green spark) + + heart: + name: heart @a + pattern: heart + + infested: # added in 1.20.5 (for 1.21) + name: infested @- + pattern: infested + + instant_effect: # added in 1.20.5 + name: instant effect @an + pattern: instant (effect|spell|thrown potion) + spell_instant: # for versions below 1.20.5 + name: spell @a + pattern: instant (effect|spell|thrown potion) + + item: # added in 1.20.5 + name: item @- + pattern: %itemtype% item (break|crack)[ing] + item_crack: # for versions below 1.20.5 + name: item crack @- + pattern: %itemtype% item (break|crack)[ing] + + item_cobweb: # added in 1.20.5 (for 1.21) + name: cobweb @- + pattern: cobweb + + item_slime: # added in 1.20.5 + name: slime @- + pattern: slime + slime: # for versions below 1.20.5 + name: slime @- + pattern: slime + + item_snowball: # added in 1.20.5 + name: snowball @- + pattern: (snowball [break]|snow shovel|snow(man| golem) spawn) + snowball: # for versions below 1.20.5 + name: snowball break @- + pattern: snowball break + snow_shovel: # for versions below 1.20.5 + name: snow shovel @- + pattern: (snowball|snow shovel|snow(man| golem) spawn) + + landing_honey: # added in 1.15 + name: landing honey @- + pattern: landing honey + + landing_lava: # added in 1.14 + name: landing lava @- + pattern: landing lava + + landing_obsidian_tear: # added in 1.16 + name: landing obsidian tear @- + pattern: landing obsidian tear + + large_smoke: # added in 1.20.5 + name: large smoke @- + pattern: large smoke + smoke_large: # for versions below 1.20.5 + name: large smoke @- + pattern: large smoke + + lava: + name: lava pop @a + pattern: lava pop + + mycelium: # previously town_aura, changed in 1.20.5 + name: mycelium @- + pattern: (mycelium|small smoke|town aura) + town_aura: + name: small smoke @- + pattern: (mycelium|small smoke|town aura) + + nautilus: # added in 1.13 + name: nautilus @- + pattern: nautilus + + note: + name: note @a + pattern: note [(of|with) [colo[u]r] %number%] + + ominous_spawning: # added in 1.20.5 (for 1.21) + name: ominous spawning @- + pattern: ominous spawning + + poof: # added in 1.20.5 + name: poof @a + pattern: (poof|explosion) + explosion_normal: # for versions below 1.20.5 + name: explosion @an + pattern: (poof|explosion) + + portal: + name: portal @a + pattern: [nether] portal + + raid_omen: # added in 1.20.5 (1.21) + name: raid omen @a + pattern: raid omen + + rain: # added in 1.20.5 + name: rain @- + pattern: (rain|water drop) + water_drop: # for versions below 1.20.5 + name: water drop @a + pattern: (rain|water drop) + + reverse_portal: # added in 1.16 + name: reverse portal @- + pattern: reverse portal + + scrape: # added in 1.17 name: scrape @- pattern: scrape - # 1.19 particles - sonic_boom: - name: sonic boom @- - pattern: sonic boom - sculk_soul: - name: sculk soul @- - pattern: sculk soul - sculk_charge: + sculk_charge: # added in 1.19 name: sculk charge @- - pattern: sculk charge - sculk_charge_pop: + pattern: sculk charge [with ([a] roll|[an] angle) [of] %-float%] + + sculk_charge_pop: # added in 1.19 name: sculk charge pop @- pattern: sculk charge pop - shriek: + + sculk_soul: # added in 1.19 + name: sculk soul @- + pattern: sculk soul + + shriek: # added in 1.19 name: shriek @- - pattern: shriek - - # 1.19.4 particles - dripping_cherry_leaves: - name: dripping cherry leaves @- - pattern: dripping cherry leaves - falling_cherry_leaves: - name: falling cherry leaves @- - pattern: falling cherry leaves - landing_cherry_leaves: - name: landing cherry leaves @- - pattern: landing cherry leaves + pattern: shriek [with [a] delay [of] %-timespan%] + + small_flame: # added in 1.17 + name: small flame @a + pattern: small flame + + small_gust: # added in 1.20.5 (for 1.21) + name: small gust @a + pattern: small gust + + smoke: # added in 1.20.5 + name: smoke @- + pattern: smoke [particle] + smoke_normal: # for versions below 1.20.5 + name: smoke @- + pattern: smoke [particle] + + sneeze: # added in 1.14 + name: sneeze @a + pattern: sneeze + + snowflake: # added in 1.17 + name: snowflake @a + pattern: snowflake + + sonic_boom: # added in 1.19 + name: sonic boom @a + pattern: sonic boom + + soul: # added in 1.16 + name: soul @- + pattern: soul + + soul_fire_flame: # added in 1.16 + name: soul fire flame @a + pattern: soul fire flame + + spit: # added in 1.11 + name: spit @- + pattern: spit + + splash: # added in 1.20.5 + name: splash @a + pattern: [water] splash + water_splash: # for versions below 1.20.5 + name: water splash @a + pattern: [water] splash + + spore_blossom_air: # added in 1.17 + name: spore blossom air @- + pattern: spore blossom air + + squid_ink: # added in 1.13 + name: squid ink @- + pattern: squid ink + + sweep_attack: # added in 1.14 + name: sweep attack @a + pattern: sweep attack + + totem_of_undying: # added in 1.20.5 + name: totem of undying @a + pattern: totem [of undying] + totem: # for versions below 1.20.5 + name: totem @a + pattern: totem [of undying] + + trial omen: # added in 1.20.5 (for 1.21) + name: trial omen @a + pattern: trial omen + + trial_spawner_detection: # added in 1.20.5 (for 1.21) + name: trial spawner detection @- + pattern: trial spawner detection + + trial_spawner_detection_ominous: # added in 1.20.5 (for 1.21) + name: ominous trial spawner detection @- + pattern: ominous trial spawner detection + + underwater: # added in 1.20.5 + name: underwater @- + pattern: (underwater|suspended|void fog) + suspended: # for versions below 1.20.5 + name: suspended @- + pattern: (underwater|suspended) + suspended_depth: # for versions below 1.20.5 + name: void fog @- + pattern: void fog + + vault_connection: # added in 1.20.5 (for 1.21) + name: vault connection @- + pattern: vault connection + + vibration: # added in 1.17 + name: vibration @a + # must be a literal, so you can't actually use this properly yet + pattern: vibration [to %-entity/location%] [over %-timespan%] + + warped_spore: # added in 1.16 + name: warped spore @a + pattern: warped spore + + wax_off: # added in 1.17 + name: wax off @- + pattern: wax off + + wax_on: # added in 1.17 + name: wax on @- + pattern: wax on + + white_ash: # added in 1.16 + name: white ash @- + pattern: white ash + + white_smoke: # added in 1.20.3 + name: white smoke @- + pattern: white smoke + + witch: # added in 1.20.5 + name: witch @a + pattern: (witch [magic|spell]|purple spark) + spell_witch: # for versions below 1.20.5 + name: witch spell @a + pattern: (witch [magic|spell]|purple spark) # -- Inventory Actions -- inventory actions: diff --git a/src/test/skript/tests/regressions/3284-itemcrack particle.sk b/src/test/skript/tests/regressions/3284-itemcrack particle.sk index 0390fb158bd..470e61948c4 100644 --- a/src/test/skript/tests/regressions/3284-itemcrack particle.sk +++ b/src/test/skript/tests/regressions/3284-itemcrack particle.sk @@ -1,3 +1,3 @@ -test "item crack particles" when running below minecraft "1.20.5": # item crack does not exist on 1.20.5 +test "item crack particles": set {_loc} to location of spawn of world "world" play diamond sword item crack at {_loc} diff --git a/src/test/skript/tests/syntaxes/effects/EffVisualEffect.sk b/src/test/skript/tests/syntaxes/effects/EffVisualEffect.sk new file mode 100644 index 00000000000..b8631e79cf2 --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffVisualEffect.sk @@ -0,0 +1,112 @@ +# we want to ensure particles parse and play on all versions +test "visual effects": + set {_} to location of spawn of world "world" + play angry villager at {_} + play air breaking at {_} + play sprinting dust of air at {_} + play barrier at {_} + play bubble at {_} + play bubble column up at {_} + play bubble pop at {_} + play cloud at {_} + play crit at {_} + play current down at {_} + play dolphin at {_} + play dripping lava at {_} + play dripping water at {_} + play white dust with size 2 at {_} + play effect at {_} + play elder guardian at {_} + play enchant at {_} + play enchanted hit at {_} + play end rod at {_} + play lingering potion at {_} + play white potion swirl at {_} + play white transparent potion swirl at {_} + play large explosion at {_} + play explosion emitter at {_} + play falling dust of air at {_} + play firework at {_} + play fishing at {_} + play flame at {_} + play happy villager at {_} + play instant effect at {_} + play iron sword item break at {_} + play slime at {_} + play snowball at {_} + play snowball break at {_} + play large smoke at {_} + play lava pop at {_} + play mycelium at {_} + play nautilus at {_} + play note at {_} + play note of 5 at {_} + play poof at {_} + play portal at {_} + play rain at {_} + play smoke at {_} + play spit at {_} + play splash at {_} + play squid ink at {_} + play totem of undying at {_} + play suspended at {_} + play void fog at {_} + play witch at {_} + parse if running minecraft "1.14.4": + play campfire cosy smoke at {_} + play campfire signal smoke at {_} + play composter at {_} + play damage indicator at {_} + play dragon's breath at {_} + play falling lava at {_} + play falling water at {_} + play flash at {_} + play landing lava at {_} + play sneeze at {_} + play sweep attack at {_} + parse if running minecraft "1.15.2": + play dripping honey at {_} + play falling honey at {_} + play falling nectar at {_} + play landing honey at {_} + parse if running minecraft "1.16.5": + play ash at {_} + play crimson spore at {_} + play dripping obsidian tear at {_} + play falling obsidian tear at {_} + play landing obsidian tear at {_} + play reverse portal at {_} + play soul at {_} + play soul fire flame at {_} + play warped spore at {_} + play white ash at {_} + parse if running minecraft "1.17.1": + play light at {_} + play dripping dripstone lava at {_} + play dripping dripstone water at {_} + play dust color transition from white to white with size 1 at {_} + play falling dripstone lava at {_} + play falling dripstone water at {_} + play falling spore blossom at {_} + play glow at {_} + play glow squid ink at {_} + play scrape at {_} + play small flame at {_} + play snowflake at {_} + play spore blossom air at {_} + play vibration over 1 second at {_} + play wax off at {_} + play wax on at {_} + parse if running minecraft "1.18.2": + play air block marker at {_} + parse if running minecraft "1.19.4": + play sculk charge with a roll of 1 at {_} + play sculk charge pop at {_} + play sculk soul at {_} + play shriek with a delay of 0 seconds at {_} + play sonic boom at {_} + parse if running minecraft "1.20.6": + play cherry leaves at {_} + play dust plume at {_} + play egg crack at {_} + play white smoke at {_} From 338c47e378353f39293d637b6f86e95c32644c6f Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Thu, 30 May 2024 16:25:32 +0200 Subject: [PATCH 09/23] Fix bug with target distance and clean up method (#6742) Co-authored-by: Moderocky --- .../njol/skript/expressions/ExprTarget.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprTarget.java b/src/main/java/ch/njol/skript/expressions/ExprTarget.java index dc4146bbd8e..26e4a38596a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTarget.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTarget.java @@ -197,31 +197,31 @@ public static T getTarget(LivingEntity origin, @Nullable Enti public static T getTarget(LivingEntity origin, @Nullable EntityData type, double raysize) { if (origin instanceof Mob) return ((Mob) origin).getTarget() == null || type != null && !type.isInstance(((Mob) origin).getTarget()) ? null : (T) ((Mob) origin).getTarget(); - Location location = origin.getLocation(); - RayTraceResult result = null; - // TODO when DisplayData is added. -// if (type.getClass().equals(DisplayData.class)) -// raysize = 1.0D; + Predicate predicate = entity -> { if (entity.equals(origin)) return false; if (type != null && !type.isInstance(entity)) return false; + //noinspection RedundantIfStatement if (entity instanceof Player && ((Player) entity).getGameMode() == GameMode.SPECTATOR) return false; return true; }; + + Location eyes = origin.getEyeLocation(); + Vector direction = origin.getLocation().getDirection(); + + double distance = targetBlockDistance; if (!ignoreBlocks) { - RayTraceResult blockResult = origin.getWorld().rayTraceBlocks(origin.getEyeLocation(), location.getDirection(), targetBlockDistance); + RayTraceResult blockResult = origin.getWorld().rayTraceBlocks(eyes, direction, targetBlockDistance); if (blockResult != null) { Vector hit = blockResult.getHitPosition(); - Location eyes = origin.getEyeLocation(); - if (hit != null) - result = origin.getWorld().rayTraceEntities(eyes, location.getDirection(), eyes.toVector().distance(hit), raysize, predicate); + distance = eyes.toVector().distance(hit); } - } else { - result = origin.getWorld().rayTraceEntities(origin.getEyeLocation(), location.getDirection(), targetBlockDistance, raysize, predicate); } + + RayTraceResult result = origin.getWorld().rayTraceEntities(eyes, direction, distance, raysize, predicate); if (result == null) return null; Entity hitEntity = result.getHitEntity(); From f43bc75ed0a07a0073a524d511f18af6eba21230 Mon Sep 17 00:00:00 2001 From: Phill310 Date: Fri, 31 May 2024 03:13:31 -0700 Subject: [PATCH 10/23] Fix make look at vector (#6724) * Make entity look in direction of vector rather than using vector as a location * Use eyes as the origin --------- Co-authored-by: Moderocky --- .../java/ch/njol/skript/bukkitutil/PaperEntityUtils.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/bukkitutil/PaperEntityUtils.java b/src/main/java/ch/njol/skript/bukkitutil/PaperEntityUtils.java index f7812d52f32..7dc7149ce4f 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/PaperEntityUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/PaperEntityUtils.java @@ -114,8 +114,8 @@ public static void lookAt(LookAnchor entityAnchor, Object target, @Nullable Floa Player player = (Player) entity; if (target instanceof Vector) { Vector vector = (Vector) target; - player.lookAt(vector.getX(), vector.getY(), vector.getZ(), LookAnchor.EYES); - player.lookAt(vector.getX(), vector.getY(), vector.getZ(), LookAnchor.FEET); + player.lookAt(player.getEyeLocation().add(vector), LookAnchor.EYES); + player.lookAt(player.getEyeLocation().add(vector), LookAnchor.FEET); } else if (target instanceof Location) { player.lookAt((Location) target, LookAnchor.EYES); player.lookAt((Location) target, LookAnchor.FEET); @@ -159,7 +159,7 @@ public void tick() { switch (type) { case VECTOR: Vector vector = ((Vector)target); - mob.lookAt(vector.getX(), vector.getY(), vector.getZ(), speed, maxPitch); + mob.lookAt(mob.getEyeLocation().add(vector), speed, maxPitch); break; case LOCATION: mob.lookAt((Location) target, speed, maxPitch); From 4983d9c010682d7b2844f094cd857b0e644c0706 Mon Sep 17 00:00:00 2001 From: Ilari Suhonen Date: Fri, 31 May 2024 14:19:51 +0300 Subject: [PATCH 11/23] Improve /skript subcommand help colours (#6654) fix(skript-command): improve help colours --- src/main/java/ch/njol/skript/SkriptCommand.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptCommand.java b/src/main/java/ch/njol/skript/SkriptCommand.java index 62261d0e426..0bc25391267 100644 --- a/src/main/java/ch/njol/skript/SkriptCommand.java +++ b/src/main/java/ch/njol/skript/SkriptCommand.java @@ -62,19 +62,19 @@ public class SkriptCommand implements CommandExecutor { // TODO /skript scripts show/list - lists all enabled and/or disabled scripts in the scripts folder and/or subfolders (maybe add a pattern [using * and **]) // TODO document this command on the website private static final CommandHelp SKRIPT_COMMAND_HELP = new CommandHelp("/skript", SkriptColor.LIGHT_CYAN, CONFIG_NODE + ".help") - .add(new CommandHelp("reload", SkriptColor.DARK_RED) + .add(new CommandHelp("reload", SkriptColor.DARK_CYAN) .add("all") .add("config") .add("aliases") .add("scripts") .add("