diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index 04de09b2b86..47923956ca6 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -969,7 +969,7 @@ public static ArrayList loadItems(SectionNode node) { item = Statement.parse(expr, items, "Can't understand this condition/effect: " + expr); if (item == null) continue; - long requiredTime = SkriptConfig.longParseTimeWarningThreshold.value().getMilliSeconds(); + long requiredTime = SkriptConfig.longParseTimeWarningThreshold.value().getAs(Timespan.TimePeriod.MILLISECOND); if (requiredTime > 0) { long timeTaken = System.currentTimeMillis() - start; if (timeTaken > requiredTime) diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 4b9418c5636..bcfdbc56b1d 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -489,6 +489,16 @@ public void onEnable() { classLoadError = e; } + // Warn about pausing + if (Skript.methodExists(Server.class, "getPauseWhenEmptyTime")) { + int pauseThreshold = getServer().getPauseWhenEmptyTime(); + if (pauseThreshold > -1) { + Skript.warning("Minecraft server pausing is enabled!"); + Skript.warning("Scripts that interact with the world or entities may not work as intended when the server is paused and may crash your server."); + Skript.warning("Consider setting 'pause-when-empty-seconds' to -1 in server.properties to make sure you don't encounter any issues."); + } + } + // 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(); diff --git a/src/main/java/ch/njol/skript/SkriptConfig.java b/src/main/java/ch/njol/skript/SkriptConfig.java index 0c6e0f347e2..6cb2ab99ede 100644 --- a/src/main/java/ch/njol/skript/SkriptConfig.java +++ b/src/main/java/ch/njol/skript/SkriptConfig.java @@ -26,6 +26,7 @@ import co.aikar.timings.Timings; import org.bukkit.event.EventPriority; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.util.event.EventRegistry; import java.io.File; import java.io.IOException; @@ -46,6 +47,37 @@ @SuppressWarnings("unused") public class SkriptConfig { + // + /** + * Used for listening to events involving Skript's configuration. + * @see #eventRegistry() + */ + public interface Event extends org.skriptlang.skript.util.event.Event { } + + /** + * Called when Skript's configuration is successfully reloaded. + * This occurs when the reload process has finished, meaning the config is safe to reference. + */ + @FunctionalInterface + public interface ReloadEvent extends Event { + + /** + * The method that is called when this event triggers. + */ + void onReload(); + + } + + private static final EventRegistry eventRegistry = new EventRegistry<>(); + + /** + * @return An event registry for the configuration's events. + */ + public static EventRegistry eventRegistry() { + return eventRegistry; + } + // + @Nullable static Config mainConfig; static Collection configs = new ArrayList<>(); @@ -71,7 +103,7 @@ public class SkriptConfig { .setter(t -> { SkriptUpdater updater = Skript.getInstance().getUpdater(); if (updater != null) - updater.setCheckFrequency(t.getTicks()); + updater.setCheckFrequency(t.getAs(Timespan.TimePeriod.TICK)); }); static final Option updaterDownloadTries = new Option<>("updater download tries", 7) .optional(true); @@ -436,6 +468,10 @@ static boolean load() { Skript.exception(e, "An error occurred while loading the config"); return false; } + + // trigger reload event handlers + eventRegistry().events(ReloadEvent.class).forEach(ReloadEvent::onReload); + return true; } diff --git a/src/main/java/ch/njol/skript/bukkitutil/sounds/SoundReceiver.java b/src/main/java/ch/njol/skript/bukkitutil/sounds/SoundReceiver.java index b9533a30142..1885115fd3b 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/sounds/SoundReceiver.java +++ b/src/main/java/ch/njol/skript/bukkitutil/sounds/SoundReceiver.java @@ -19,7 +19,7 @@ */ public interface SoundReceiver { - boolean ADVENTURE_API = Skript.classExists("net.kyori.adventure.sound.Sound$Builder"); + boolean ADVENTURE_API = Skript.classExists("net.kyori.adventure.sound.Sound$Builder") && Skript.methodExists(SoundCategory.class, "soundSource"); boolean SPIGOT_SOUND_SEED = Skript.methodExists(Player.class, "playSound", Entity.class, Sound.class, SoundCategory.class, float.class, float.class, long.class); boolean ENTITY_EMITTER_SOUND = Skript.methodExists(Player.class, "playSound", Entity.class, Sound.class, SoundCategory.class, float.class, float.class); boolean ENTITY_EMITTER_STRING = Skript.methodExists(Player.class, "playSound", Entity.class, String.class, SoundCategory.class, float.class, float.class); diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java index c2992a8b8d2..64a47196bb5 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -91,6 +91,7 @@ import org.bukkit.inventory.BlockInventoryHolder; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.metadata.Metadatable; import org.bukkit.potion.PotionEffect; @@ -1529,6 +1530,11 @@ public String toVariableNameString(EnchantmentOffer eo) { .description("Represents a transform reason of an entity transform event.") .since("2.8.0")); + Classes.registerClass(new EnumClassInfo<>(ItemFlag.class, "itemflag", "item flags") + .user("item ?flags?") + .name("Item Flag") + .description("Represents flags that may be applied to hide certain attributes of an item.") + .since("INSERT VERSION")); Classes.registerClass(new EnumClassInfo<>(EntityPotionEffectEvent.Cause.class, "entitypotioncause", "entity potion causes") .user("(entity )?potion ?effect ?cause") diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java index f7cfcff0f11..c37f7485443 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java @@ -42,6 +42,9 @@ import ch.njol.skript.util.EnchantmentType; import ch.njol.skript.util.Getter; import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Color; +import ch.njol.skript.util.SkriptColor; +import ch.njol.skript.util.ColorRGB; import ch.njol.skript.util.slot.InventorySlot; import ch.njol.skript.util.slot.Slot; import com.destroystokyo.paper.event.block.AnvilDamagedEvent; @@ -1638,6 +1641,26 @@ public FireworkEffect get(FireworkExplodeEvent e) { return effects.get(0); } }, 0); + EventValues.registerEventValue(FireworkExplodeEvent.class, Color[].class, new Getter() { + @Override + public Color @Nullable [] get(FireworkExplodeEvent event) { + List effects = event.getEntity().getFireworkMeta().getEffects(); + if (effects.isEmpty()) + return null; + List colors = new ArrayList<>(); + for (FireworkEffect fireworkEffect : effects) { + for (org.bukkit.Color color : fireworkEffect.getColors()) { + if (SkriptColor.fromBukkitColor(color) != null) + colors.add(SkriptColor.fromBukkitColor(color)); + else + colors.add(ColorRGB.fromBukkitColor(color)); + } + } + if (colors.isEmpty()) + return null; + return colors.toArray(Color[]::new); + } + }, EventValues.TIME_NOW); //PlayerRiptideEvent EventValues.registerEventValue(PlayerRiptideEvent.class, ItemStack.class, new Getter() { @Override @@ -1779,7 +1802,7 @@ public Egg get(PlayerEggThrowEvent event) { EventValues.registerEventValue(PlayerStopUsingItemEvent.class, Timespan.class, new Getter() { @Override public Timespan get(PlayerStopUsingItemEvent event) { - return Timespan.fromTicks(event.getTicksHeldFor()); + return new Timespan(Timespan.TimePeriod.TICK, event.getTicksHeldFor()); } }, EventValues.TIME_NOW); EventValues.registerEventValue(PlayerStopUsingItemEvent.class, ItemType.class, new Getter() { diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java index c88a98bdf88..dbaf030c374 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultComparators.java @@ -456,7 +456,7 @@ public boolean supportsOrdering() { Comparators.registerComparator(Timespan.class, Timespan.class, new Comparator() { @Override public Relation compare(Timespan t1, Timespan t2) { - return Relation.get(t1.getMilliSeconds() - t2.getMilliSeconds()); + return Relation.get(t1.getAs(Timespan.TimePeriod.MILLISECOND) - t2.getAs(Timespan.TimePeriod.MILLISECOND)); } @Override diff --git a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java index 33c4be3fdd1..644004b8d3a 100644 --- a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java @@ -1,21 +1,3 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.classes.data; import ch.njol.skript.Skript; @@ -33,16 +15,65 @@ import ch.njol.skript.util.Utils; import ch.njol.util.StringUtils; import ch.njol.yggdrasil.Fields; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; import org.joml.Quaternionf; +import java.util.function.Function; +import java.util.regex.Matcher; import java.util.regex.Pattern; public class JavaClasses { public static final int VARIABLENAME_NUMBERACCURACY = 8; - public static final Pattern INTEGER_PATTERN = Pattern.compile("-?\\d+(_\\d+)*"); - public static final Pattern NUMBER_PATTERN = Pattern.compile("-?\\d+(_\\d+)*(?>\\.\\d+(_\\d+)*)?%?"); + + /** + * The format of an integer. + *

+ * Has an optional negative sign and may contain one underscores followed by any number of digits. + *

+ */ + public static final String INTEGER_NUMBER_PATTERN = "-?\\d+(_\\d+)*"; + + /** + * Matches an integer with an optional unit of radians or degrees. + *

+ * First, the actual number format {@code num} is specified. Then, an optional angle unit is specified. + * For this, the {@code rad} group is used. This specifies that the number is in radians. + * This is used to determine if the number should be converted to degrees. + * Degrees is not a named group because it just returns the value in the {@code num} group, which + * is the default behaviour. + * Optionally, the user can use {@code x in degrees} instead of {@code x degrees}. + *

+ */ + public static final Pattern INTEGER_PATTERN = + Pattern.compile("(?" + INTEGER_NUMBER_PATTERN + ")" + + "(?: (?:in )?(?:(?rad(?:ian)?s?)|deg(?:ree)?s?))?"); + + /** + * The format of a decimal number. + *

+ * Has an optional negative sign and may contain one underscores followed by any number of digits, + * in the whole part or the fractional part. The fractional part is optional. May be followed by a percentage sign, + * to indicate that the number is a percentage. + *

+ */ + public static final String DECIMAL_NUMBER_PATTERN = "-?\\d+(_\\d+)*(?>\\.\\d+(_\\d+)*)?%?"; + + /** + * Matches a decimal number with an optional unit of radians or degrees. + *

+ * First, the actual number format {@code num} is specified. Then, an optional angle unit is specified. + * For this, the {@code rad} group is used. This specifies that the number is in radians. + * This is used to determine if the number should be converted to degrees. + * Degrees is not a named group because it just returns the value in the {@code num} group, which + * is the default behaviour. + * Optionally, the user can use {@code x in degrees} instead of {@code x degrees}. + *

+ */ + public static final Pattern DECIMAL_PATTERN = + Pattern.compile("(?" + DECIMAL_NUMBER_PATTERN + ")" + + "(?: (?:in )?(?:(?rad(?:ian)?s?)|deg(?:ree)?s?))?"); static { Classes.registerClass(new ClassInfo<>(Object.class, "object") @@ -52,328 +83,55 @@ public class JavaClasses { .usage("") .examples("") .since("1.0")); - + Classes.registerClass(new ClassInfo<>(Number.class, "number") .user("num(ber)?s?") .name("Number") - .description("A number, e.g. 2.5, 3, or -9812454.", - "Please note that many expressions only need integers, i.e. will discard any fractional parts of any numbers without producing an error.") - .usage("[-]###[.###] (any amount of digits; very large numbers will be truncated though)") - .examples("set the player's health to 5.5", - "set {_temp} to 2*{_temp} - 2.5") + .description( + "A number, e.g. 2.5, 3, -9812454, 30 degrees or 3.14 radians.", + "Please note that many expressions only need integers, i.e. " + + "will discard any fractional parts of any numbers without producing an error.", + "Radians will be converted to degrees.") + .usage("[-]###[.###] [[in ](rad[ian][s]|deg[ree][s])] (any amount of digits; very large numbers will be truncated though)") + .examples( + "set the player's health to 5.5", + "set {_temp} to 2*{_temp} - 2.5", + "set {_angle} to 3.14 in radians # will be converted to degrees" + ) .since("1.0") // is registered after all other number classes .defaultExpression(new SimpleLiteral<>(1, true)) - .parser(new Parser() { - @Override - @Nullable - public Number parse(String s, ParseContext context) { - if (!NUMBER_PATTERN.matcher(s).matches()) - return null; - if (INTEGER_PATTERN.matcher(s).matches()) { - try { - return Long.valueOf(s.replace("_", "")); - } catch (NumberFormatException ignored) { } - } - try { - s = s.replace("_", ""); + .parser(new NumberParser()) + .serializer(new NumberSerializer())); - Double d = s.endsWith("%") ? Double.parseDouble(s.substring(0, s.length() - 1)) / 100 : Double.parseDouble(s); - if (d.isNaN() || d.isInfinite()) - return null; - return d; - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public String toString(Number n, int flags) { - return StringUtils.toString(n.doubleValue(), SkriptConfig.numberAccuracy.value()); - } - - @Override - public String toVariableNameString(Number n) { - return StringUtils.toString(n.doubleValue(), VARIABLENAME_NUMBERACCURACY); - } - }).serializer(new Serializer() { - @Override - public Fields serialize(Number n) { - throw new IllegalStateException(); // serialised natively by Yggdrasil - } - - @Override - public boolean canBeInstantiated() { - return true; - } - - @Override - public void deserialize(Number o, Fields f) { - assert false; - } - - @Override - @Nullable - public Number deserialize(String s) { - try { - return Integer.valueOf(s); - } catch (NumberFormatException ignored) {} - try { - return Double.valueOf(s); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); - Classes.registerClass(new ClassInfo<>(Long.class, "long") .user("int(eger)?s?") .name(ClassInfo.NO_DOC) .before("integer", "short", "byte") .defaultExpression(new SimpleLiteral<>((long) 1, true)) - .parser(new Parser() { - @Override - @Nullable - public Long parse(String s, ParseContext context) { - if (!INTEGER_PATTERN.matcher(s).matches()) - return null; - try { - return Long.valueOf(s.replace("_", "")); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public String toString(Long l, int flags) { - return "" + l; - } - - @Override - public String toVariableNameString(Long l) { - return "" + l; - } - }).serializer(new Serializer() { - @Override - public Fields serialize(Long n) { - throw new IllegalStateException(); // serialised natively by Yggdrasil - } - - @Override - public boolean canBeInstantiated() { - return true; - } - - @Override - public void deserialize(Long o, Fields f) { - assert false; - } + .parser(new LongParser()) + .serializer(new LongSerializer())); - @Override - @Nullable - public Long deserialize(String s) { - try { - return Long.parseLong(s); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); - Classes.registerClass(new ClassInfo<>(Integer.class, "integer") .name(ClassInfo.NO_DOC) .defaultExpression(new SimpleLiteral<>(1, true)) - .parser(new Parser() { - @Override - @Nullable - public Integer parse(String s, ParseContext context) { - if (!INTEGER_PATTERN.matcher(s).matches()) - return null; - try { - return Integer.valueOf(s.replace("_", "")); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public String toString(Integer i, int flags) { - return "" + i; - } - - @Override - public String toVariableNameString(Integer i) { - return "" + i; - } - }).serializer(new Serializer() { - @Override - public Fields serialize(Integer n) { - throw new IllegalStateException(); // serialised natively by Yggdrasil - } - - @Override - public boolean canBeInstantiated() { - return true; - } - - @Override - public void deserialize(Integer o, Fields f) { - assert false; - } + .parser(new IntegerParser()) + .serializer(new IntegerSerializer())); - @Override - @Nullable - public Integer deserialize(String s) { - try { - return Integer.parseInt(s); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); - Classes.registerClass(new ClassInfo<>(Double.class, "double") .name(ClassInfo.NO_DOC) .defaultExpression(new SimpleLiteral<>(1., true)) .after("long") .before("float", "integer", "short", "byte") - .parser(new Parser() { - @Override - @Nullable - public Double parse(String s, ParseContext context) { - if (!NUMBER_PATTERN.matcher(s).matches()) - return null; - try { - s = s.replace("_", ""); - - Double d = s.endsWith("%") ? Double.parseDouble(s.substring(0, s.length() - 1)) / 100 : Double.parseDouble(s); - if (d.isNaN() || d.isInfinite()) - return null; - return d; - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public String toString(Double d, int flags) { - return StringUtils.toString(d, SkriptConfig.numberAccuracy.value()); - } - - @Override - public String toVariableNameString(Double d) { - return StringUtils.toString(d, VARIABLENAME_NUMBERACCURACY); - } - }).serializer(new Serializer() { - @Override - public Fields serialize(Double n) { - throw new IllegalStateException(); // serialised natively by Yggdrasil - } - - @Override - public boolean canBeInstantiated() { - return true; - } - - @Override - public void deserialize(Double o, Fields f) { - assert false; - } + .parser(new DoubleParser()) + .serializer(new DoubleSerializer())); - @Override - @Nullable - public Double deserialize(String s) { - try { - return Double.parseDouble(s); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); - Classes.registerClass(new ClassInfo<>(Float.class, "float") .name(ClassInfo.NO_DOC) .defaultExpression(new SimpleLiteral<>(1f, true)) - .parser(new Parser() { - @Override - @Nullable - public Float parse(String s, ParseContext context) { - if (!NUMBER_PATTERN.matcher(s).matches()) - return null; - try { - s = s.replace("_", ""); - - Float f = s.endsWith("%") ? Float.parseFloat(s.substring(0, s.length() - 1)) / 100 : Float.parseFloat(s); - if (f.isNaN() || f.isInfinite()) { - return null; - } - return f; - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public String toString(Float f, int flags) { - return StringUtils.toString(f, SkriptConfig.numberAccuracy.value()); - } - - @Override - public String toVariableNameString(Float f) { - return StringUtils.toString(f.doubleValue(), VARIABLENAME_NUMBERACCURACY); - } - }).serializer(new Serializer() { - @Override - public Fields serialize(Float n) { - throw new IllegalStateException(); // serialised natively by Yggdrasil - } - - @Override - public boolean canBeInstantiated() { - return true; - } - - @Override - public void deserialize(Float o, Fields f) { - assert false; - } + .parser(new FloatParser()) + .serializer(new FloatSerializer())); - @Override - @Nullable - public Float deserialize(String s) { - try { - return Float.parseFloat(s); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); - Classes.registerClass(new ClassInfo<>(Boolean.class, "boolean") .user("booleans?") .name("Boolean") @@ -381,10 +139,10 @@ public boolean mustSyncDeserialization() { .usage("true/yes/on or false/no/off") .examples("set {config.%player%.use mod} to false") .since("1.0") - .parser(new Parser() { + .parser(new Parser<>() { private final RegexMessage truePattern = new RegexMessage("boolean.true.pattern"); private final RegexMessage falsePattern = new RegexMessage("boolean.false.pattern"); - + @Override @Nullable public Boolean parse(String s, ParseContext context) { @@ -394,30 +152,30 @@ public Boolean parse(String s, ParseContext context) { return Boolean.FALSE; return null; } - + private final Message trueName = new Message("boolean.true.name"); private final Message falseName = new Message("boolean.false.name"); - + @Override public String toString(Boolean b, int flags) { return b ? trueName.toString() : falseName.toString(); } - + @Override public String toVariableNameString(Boolean b) { return "" + b; } - }).serializer(new Serializer() { + }).serializer(new Serializer() { @Override public Fields serialize(Boolean n) { throw new IllegalStateException(); // serialised natively by Yggdrasil } - + @Override public boolean canBeInstantiated() { return true; } - + @Override public void deserialize(Boolean o, Fields f) { assert false; @@ -432,127 +190,25 @@ public Boolean deserialize(String s) { return Boolean.FALSE; return null; } - + @Override public boolean mustSyncDeserialization() { return false; } })); - + Classes.registerClass(new ClassInfo<>(Short.class, "short") .name(ClassInfo.NO_DOC) .defaultExpression(new SimpleLiteral<>((short) 1, true)) - .parser(new Parser() { - @Override - @Nullable - public Short parse(String s, ParseContext context) { - if (!INTEGER_PATTERN.matcher(s).matches()) - return null; - try { - return Short.valueOf(s.replace("_", "")); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public String toString(Short s, int flags) { - return "" + s; - } - - @Override - public String toVariableNameString(Short s) { - return "" + s; - } - }).serializer(new Serializer() { - @Override - public Fields serialize(Short n) { - throw new IllegalStateException(); // serialised natively by Yggdrasil - } - - @Override - public boolean canBeInstantiated() { - return true; - } - - @Override - public void deserialize(Short o, Fields f) { - assert false; - } + .parser(new ShortParser()) + .serializer(new ShortSerializer())); - @Override - @Nullable - public Short deserialize(String s) { - try { - return Short.parseShort(s); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); - Classes.registerClass(new ClassInfo<>(Byte.class, "byte") .name(ClassInfo.NO_DOC) .defaultExpression(new SimpleLiteral<>((byte) 1, true)) - .parser(new Parser() { - @Override - @Nullable - public Byte parse(String s, ParseContext context) { - if (!INTEGER_PATTERN.matcher(s).matches()) - return null; - try { - return Byte.valueOf(s.replace("_", "")); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public String toString(Byte b, int flags) { - return "" + b; - } - - @Override - public String toVariableNameString(Byte b) { - return "" + b; - } - }).serializer(new Serializer() { - @Override - public Fields serialize(Byte n) { - throw new IllegalStateException(); // serialised natively by Yggdrasil - } - - @Override - public boolean canBeInstantiated() { - return true; - } - - @Override - public void deserialize(Byte o, Fields f) { - assert false; - } + .parser(new ByteParser()) + .serializer(new ByteSerializer())); - @Override - @Nullable - public Byte deserialize(String s) { - try { - return Byte.parseByte(s); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public boolean mustSyncDeserialization() { - return false; - } - })); - Classes.registerClass(new ClassInfo<>(String.class, "string") .user("(text|string)s?") .name("Text") @@ -568,7 +224,7 @@ public boolean mustSyncDeserialization() { "message \"Hello %player%\"", "message \"The id of \"\"%type of tool%\"\" is %id of tool%.\"") .since("1.0") - .parser(new Parser() { + .parser(new Parser<>() { @Override @Nullable public String parse(String s, ParseContext context) { @@ -590,37 +246,37 @@ public String parse(String s, ParseContext context) { assert false; return null; } - + @Override public boolean canParse(ParseContext context) { return context != ParseContext.DEFAULT; } - + @Override public String toString(String s, int flags) { return s; } - + @Override public String getDebugMessage(String s) { return '"' + s + '"'; } - + @Override public String toVariableNameString(String s) { return s; } - }).serializer(new Serializer() { + }).serializer(new Serializer() { @Override public Fields serialize(String n) { throw new IllegalStateException(); // serialised natively by Yggdrasil } - + @Override public boolean canBeInstantiated() { return true; } - + @Override public void deserialize(String o, Fields f) { assert false; @@ -630,7 +286,7 @@ public void deserialize(String o, Fields f) { public String deserialize(String s) { return s; } - + @Override public boolean mustSyncDeserialization() { return false; @@ -640,35 +296,500 @@ public boolean mustSyncDeserialization() { // joml type - for display entities if (Skript.classExists("org.joml.Quaternionf")) Classes.registerClass(new ClassInfo<>(Quaternionf.class, "quaternion") - .user("quaternionf?s?") - .name("Quaternion") - .description("Quaternions are four dimensional vectors, often used for representing rotations.") - .since("INSERT VERSION") - .parser(new Parser<>() { - public boolean canParse(ParseContext context) { - return false; - } + .user("quaternionf?s?") + .name("Quaternion") + .description("Quaternions are four dimensional vectors, often used for representing rotations.") + .since("INSERT VERSION") + .parser(new Parser<>() { + public boolean canParse(ParseContext context) { + return false; + } - @Override - public String toString(Quaternionf quaternion, int flags) { - return "w:" + Skript.toString(quaternion.w()) + ", x:" + Skript.toString(quaternion.x()) + ", y:" + Skript.toString(quaternion.y()) + ", z:" + Skript.toString(quaternion.z()); - } + @Override + public String toString(Quaternionf quaternion, int flags) { + return "w:" + Skript.toString(quaternion.w()) + ", x:" + Skript.toString(quaternion.x()) + ", y:" + Skript.toString(quaternion.y()) + ", z:" + Skript.toString(quaternion.z()); + } - @Override - public String toVariableNameString(Quaternionf quaternion) { - return quaternion.w() + "," + quaternion.x() + "," + quaternion.y() + "," + quaternion.z(); - } - }) - .defaultExpression(new EventValueExpression<>(Quaternionf.class)) - .cloner(quaternion -> { - try { - // Implements cloneable, but doesn't return a Quaternionf. - // org.joml improper override. Returns Object. - return (Quaternionf) quaternion.clone(); - } catch (CloneNotSupportedException e) { - return null; - } - })); + @Override + public String toVariableNameString(Quaternionf quaternion) { + return quaternion.w() + "," + quaternion.x() + "," + quaternion.y() + "," + quaternion.z(); + } + }) + .defaultExpression(new EventValueExpression<>(Quaternionf.class)) + .cloner(quaternion -> { + try { + // Implements cloneable, but doesn't return a Quaternionf. + // org.joml improper override. Returns Object. + return (Quaternionf) quaternion.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + })); + } + + /** + * Converts a string to a number formatted as an integer. + *

+ * Applies {@code stringToNumber} for parsing the number, then tries to + * convert radians to degrees if the string contains a radian group. + *

+ * + * @param string The string with the possible number. + * @param stringToNumber The function to parse the number, e.g. {@link Integer#parseInt(String)}. + * @return The parsed string, or null if the string could not be parsed. + */ + @Contract(pure = true) + private static @Nullable T convertIntegerFormatted( + String string, + Function stringToNumber + ) { + Matcher matcher = INTEGER_PATTERN.matcher(string); + + if (!matcher.matches()) + return null; + + String number = matcher.group("num").replace("_", ""); + if (matcher.group("rad") != null) { + try { + //noinspection unchecked + return (T) (Double) Math.toDegrees(stringToNumber.apply(number).doubleValue()); + } catch (NumberFormatException ignored) { + } + } else { + try { + return stringToNumber.apply(number); + } catch (NumberFormatException ignored) { + } + } + + return null; + } + + /** + * Converts a string to a number formatted as a decimal. + *

+ * Applies {@code stringToNumber} for parsing the number. + * If the number is a percentage, it gets parsed using the double value divided by 100, and {@code fromDouble}. + * Then tries to convert radians to degrees if the string contains a radian group. + *

+ * + * @param string The string with the possible number. + * @param stringToNumber The function to parse the number, e.g. {@link Integer#parseInt(String)}. + * @return The parsed string, or null if the string could not be parsed. + */ + @Contract(pure = true) + private static @Nullable T convertDecimalFormatted( + String string, + Function stringToNumber + ) { + Matcher matcher = DECIMAL_PATTERN.matcher(string); + + if (!matcher.matches()) + return null; + + String number = matcher.group("num").replace("_", ""); + try { + T result; + if (number.endsWith("%")) { + T extracted = stringToNumber.apply(number.substring(0, number.length() - 1)); + //noinspection unchecked + result = (T) (Double) (extracted.doubleValue() / 100.0); + } else { + result = stringToNumber.apply(number); + } + + if (matcher.group("rad") != null) { + try { + //noinspection unchecked + return (T) (Double) Math.toDegrees(result.doubleValue()); + } catch (NumberFormatException ignored) { + } + } + + return result; + } catch (NumberFormatException ex) { + return null; + } + } + + private static class NumberParser extends Parser { + + @Override + public @Nullable Number parse(String string, ParseContext context) { + Matcher numberMatcher = DECIMAL_PATTERN.matcher(string); + if (!numberMatcher.matches()) + return null; + + Integer integerAttempt = convertIntegerFormatted(string, Integer::parseInt); + if (integerAttempt != null) + return integerAttempt; + + Double parsed = convertDecimalFormatted(string, Double::parseDouble); + return parsed == null || parsed.isInfinite() || parsed.isNaN() ? null : parsed; + } + + @Override + public String toString(Number number, int flags) { + return StringUtils.toString(number.doubleValue(), SkriptConfig.numberAccuracy.value()); + } + + @Override + public String toVariableNameString(Number number) { + return StringUtils.toString(number.doubleValue(), VARIABLENAME_NUMBERACCURACY); + } + + } + + private static class NumberSerializer extends Serializer { + + @Override + public Fields serialize(Number number) { + throw new IllegalStateException(); // serialised natively by Yggdrasil + } + + @Override + public boolean canBeInstantiated() { + return true; + } + + @Override + public void deserialize(Number number, Fields fields) { + assert false; + } + + @Override + public @Nullable Number deserialize(String string) { + try { + return Integer.valueOf(string); + } catch (NumberFormatException ignored) { + } + try { + return Double.valueOf(string); + } catch (NumberFormatException e) { + return null; + } + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } + + } + + private static class LongParser extends Parser { + + @Override + public @Nullable Long parse(String string, ParseContext context) { + return convertIntegerFormatted(string, Long::parseLong); + } + + @Override + public String toString(Long l, int flags) { + return l.toString(); + } + + @Override + public String toVariableNameString(Long l) { + return l.toString(); + } + + } + + private static class LongSerializer extends Serializer { + + @Override + public Fields serialize(Long l) { + throw new IllegalStateException(); // serialised natively by Yggdrasil + } + + @Override + public boolean canBeInstantiated() { + return true; + } + + @Override + public void deserialize(Long l, Fields fields) { + assert false; + } + + @Override + public @Nullable Long deserialize(String string) { + try { + return Long.parseLong(string); + } catch (NumberFormatException ex) { + return null; + } + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } + + } + + private static class IntegerParser extends Parser { + + @Override + public @Nullable Integer parse(String string, ParseContext context) { + return convertIntegerFormatted(string, Integer::parseInt); + } + + @Override + public String toString(Integer i, int flags) { + return i.toString(); + } + + @Override + public String toVariableNameString(Integer i) { + return i.toString(); + } + + } + + private static class IntegerSerializer extends Serializer { + + @Override + public Fields serialize(Integer i) { + throw new IllegalStateException(); // serialised natively by Yggdrasil + } + + @Override + public boolean canBeInstantiated() { + return true; + } + + @Override + public void deserialize(Integer i, Fields fields) { + assert false; + } + + @Override + public @Nullable Integer deserialize(String string) { + try { + return Integer.parseInt(string); + } catch (NumberFormatException ex) { + return null; + } + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } + + } + + private static class DoubleParser extends Parser { + + @Override + public @Nullable Double parse(String string, ParseContext context) { + Double parsed = convertDecimalFormatted(string, Double::parseDouble); + + return parsed == null || parsed.isInfinite() || parsed.isNaN() ? null : parsed; + } + + @Override + public String toString(Double d, int flags) { + return StringUtils.toString(d, SkriptConfig.numberAccuracy.value()); + } + + @Override + public String toVariableNameString(Double d) { + return StringUtils.toString(d, VARIABLENAME_NUMBERACCURACY); + } + + } + + private static class DoubleSerializer extends Serializer { + + @Override + public Fields serialize(Double d) { + throw new IllegalStateException(); // serialised natively by Yggdrasil + } + + @Override + public boolean canBeInstantiated() { + return true; + } + + @Override + public void deserialize(Double d, Fields fields) { + assert false; + } + + @Override + public @Nullable Double deserialize(String string) { + try { + return Double.parseDouble(string); + } catch (NumberFormatException ex) { + return null; + } + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } + + } + + private static class FloatParser extends Parser { + + @Override + public @Nullable Float parse(String string, ParseContext context) { + Float parsed = convertDecimalFormatted(string, Float::parseFloat); + + return parsed == null || parsed.isInfinite() || parsed.isNaN() ? null : parsed; + } + + @Override + public String toString(Float f, int flags) { + return StringUtils.toString(f, SkriptConfig.numberAccuracy.value()); + } + + @Override + public String toVariableNameString(Float f) { + return StringUtils.toString(f.doubleValue(), VARIABLENAME_NUMBERACCURACY); + } + + } + + private static class FloatSerializer extends Serializer { + + @Override + public Fields serialize(Float f) { + throw new IllegalStateException(); // serialised natively by Yggdrasil + } + + @Override + public boolean canBeInstantiated() { + return true; + } + + @Override + public void deserialize(Float f, Fields fields) { + assert false; + } + + @Override + public @Nullable Float deserialize(String string) { + try { + return Float.parseFloat(string); + } catch (NumberFormatException ex) { + return null; + } + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } + + } + + private static class ShortParser extends Parser { + + @Override + public @Nullable Short parse(String string, ParseContext context) { + return convertIntegerFormatted(string, Short::parseShort); + } + + @Override + public String toString(Short s, int flags) { + return s.toString(); + } + + @Override + public String toVariableNameString(Short s) { + return s.toString(); + } + + } + + private static class ShortSerializer extends Serializer { + + @Override + public Fields serialize(Short s) { + throw new IllegalStateException(); // serialised natively by Yggdrasil + } + + @Override + public boolean canBeInstantiated() { + return true; + } + + @Override + public void deserialize(Short s, Fields fields) { + assert false; + } + + @Override + public @Nullable Short deserialize(String string) { + try { + return Short.parseShort(string); + } catch (NumberFormatException ex) { + return null; + } + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } + + } + + private static class ByteParser extends Parser { + + @Override + public @Nullable Byte parse(String string, ParseContext context) { + return convertIntegerFormatted(string, Byte::parseByte); + } + + @Override + public String toString(Byte b, int flags) { + return b.toString(); + } + + @Override + public String toVariableNameString(Byte b) { + return b.toString(); + } + + } + + private static class ByteSerializer extends Serializer { + + @Override + public Fields serialize(Byte b) { + throw new IllegalStateException(); // serialised natively by Yggdrasil + } + + @Override + public boolean canBeInstantiated() { + return true; + } + + @Override + public void deserialize(Byte b, Fields fields) { + assert false; + } + + @Override + public @Nullable Byte deserialize(String string) { + try { + return Byte.parseByte(string); + } catch (NumberFormatException ex) { + return null; + } + } + + @Override + public boolean mustSyncDeserialization() { + return false; + } } diff --git a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java index c846b00e680..57704e760ee 100644 --- a/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/SkriptClasses.java @@ -289,7 +289,7 @@ public String toString(final Timespan t, final int flags) { @Override public String toVariableNameString(final Timespan o) { - return "timespan:" + o.getMilliSeconds(); + return "timespan:" + o.getAs(Timespan.TimePeriod.MILLISECOND); } }).serializer(new YggdrasilSerializer<>())); diff --git a/src/main/java/ch/njol/skript/command/ScriptCommand.java b/src/main/java/ch/njol/skript/command/ScriptCommand.java index eca22e78a68..52e320c9298 100644 --- a/src/main/java/ch/njol/skript/command/ScriptCommand.java +++ b/src/main/java/ch/njol/skript/command/ScriptCommand.java @@ -556,7 +556,7 @@ public long getRemainingMilliseconds(UUID uuid, Event event) { return 0; Timespan cooldown = this.cooldown; assert cooldown != null; - long remaining = cooldown.getMilliSeconds() - getElapsedMilliseconds(uuid, event); + long remaining = cooldown.getAs(Timespan.TimePeriod.MILLISECOND) - getElapsedMilliseconds(uuid, event); if (remaining < 0) remaining = 0; return remaining; @@ -565,7 +565,7 @@ public long getRemainingMilliseconds(UUID uuid, Event event) { public void setRemainingMilliseconds(UUID uuid, Event event, long milliseconds) { Timespan cooldown = this.cooldown; assert cooldown != null; - long cooldownMs = cooldown.getMilliSeconds(); + long cooldownMs = cooldown.getAs(Timespan.TimePeriod.MILLISECOND); if (milliseconds > cooldownMs) milliseconds = cooldownMs; setElapsedMilliSeconds(uuid, event, cooldownMs - milliseconds); diff --git a/src/main/java/ch/njol/skript/conditions/CondDate.java b/src/main/java/ch/njol/skript/conditions/CondDate.java index ba2da40b81b..7172e9e80b1 100644 --- a/src/main/java/ch/njol/skript/conditions/CondDate.java +++ b/src/main/java/ch/njol/skript/conditions/CondDate.java @@ -73,7 +73,7 @@ public boolean check(final Event e) { final long now = System.currentTimeMillis(); return date.check(e, date -> delta.check(e, - timespan -> now - date.getTimestamp() >= timespan.getMilliSeconds() + timespan -> now - date.getTimestamp() >= timespan.getAs(Timespan.TimePeriod.MILLISECOND) ), isNegated()); } diff --git a/src/main/java/ch/njol/skript/conditions/CondFromMobSpawner.java b/src/main/java/ch/njol/skript/conditions/CondFromMobSpawner.java new file mode 100644 index 00000000000..fe515cfc50c --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondFromMobSpawner.java @@ -0,0 +1,45 @@ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.*; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.entity.Entity; + +@Name("Is From A Mob Spawner") +@Description("Checks if an entity was spawned from a mob spawner.") +@Examples("send whether target is from a mob spawner") +@RequiredPlugins("PaperMC") +@Since("INSERT VERSION") +public class CondFromMobSpawner extends PropertyCondition { + + static { + if (Skript.methodExists(Entity.class, "fromMobSpawner")) + Skript.registerCondition(CondFromMobSpawner.class, + "%entities% (is|are) from a [mob] spawner", + "%entities% (isn't|aren't|is not|are not) from a [mob] spawner", + "%entities% (was|were) spawned (from|by) a [mob] spawner", + "%entities% (wasn't|weren't|was not|were not) spawned (from|by) a [mob] spawner"); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setNegated(matchedPattern == 1 || matchedPattern == 3); + setExpr((Expression) exprs[0]); + return true; + } + + @Override + public boolean check(Entity entity) { + return entity.fromMobSpawner(); + } + + @Override + protected String getPropertyName() { + return "from a mob spawner"; + } + +} + diff --git a/src/main/java/ch/njol/skript/conditions/CondIsCustomNameVisible.java b/src/main/java/ch/njol/skript/conditions/CondIsCustomNameVisible.java new file mode 100644 index 00000000000..8548d847e86 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsCustomNameVisible.java @@ -0,0 +1,46 @@ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +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.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.entity.Entity; + +@Name("Is Custom Name Visible") +@Description("Checks if an entity's custom name is visible.") +@Examples("send true if target's custom name is visible") +@Since("INSERT VERSION") +public class CondIsCustomNameVisible extends PropertyCondition { + + static { + Skript.registerCondition(CondIsCustomNameVisible.class, + "%entities%'[s] custom name[s] (is|are) visible", + "%entities%'[s] custom name[s] (isn't|is not|are not|aren't) visible", + "custom name of %entities% (is|are) visible", + "custom name of %entities% (isn't|is not|are not|aren't) visible"); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setNegated(matchedPattern == 1 || matchedPattern == 3); + setExpr((Expression) exprs[0]); + return true; + } + + @Override + public boolean check(Entity entity) { + return entity.isCustomNameVisible(); + } + + @Override + protected String getPropertyName() { + return "custom name"; + } + +} + diff --git a/src/main/java/ch/njol/skript/conditions/CondIsSaddled.java b/src/main/java/ch/njol/skript/conditions/CondIsSaddled.java new file mode 100644 index 00000000000..a2a4cf6da01 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsSaddled.java @@ -0,0 +1,54 @@ +package ch.njol.skript.conditions; + +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.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.Material; +import org.bukkit.entity.AbstractHorse; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Steerable; +import org.bukkit.inventory.ItemStack; + +@Name("Is Saddled") +@Description({ + "Checks whether a given entity (horse or steerable) is saddled.", + "If 'properly' is used, this will only return true if the entity is wearing specifically a saddle item." +}) +@Examples("send whether {_horse} is saddled") +@Since("INSERT VERSION") +public class CondIsSaddled extends PropertyCondition { + + static { + register(CondIsSaddled.class, "[:properly] saddled", "livingentities"); + } + + private boolean properly; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + properly = parseResult.hasTag("properly"); + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Override + public boolean check(LivingEntity entity) { + if (entity instanceof Steerable steerable) { + return steerable.hasSaddle(); + } else if (entity instanceof AbstractHorse horse) { + ItemStack saddle = horse.getInventory().getSaddle(); + return properly ? (saddle != null && saddle.equals(new ItemStack(Material.SADDLE))) : (saddle != null); + } + return false; + } + + @Override + protected String getPropertyName() { + return properly ? "properly saddled" : "saddled"; + } + +} diff --git a/src/main/java/ch/njol/skript/conditions/CondIsTicking.java b/src/main/java/ch/njol/skript/conditions/CondIsTicking.java new file mode 100644 index 00000000000..6b5e20d5846 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsTicking.java @@ -0,0 +1,31 @@ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.doc.*; +import org.bukkit.entity.Entity; + +@Name("Is Ticking") +@Description("Checks if an entity is ticking.") +@Examples("send true if target is ticking") +@RequiredPlugins("PaperMC") +@Since("INSERT VERSION") +public class CondIsTicking extends PropertyCondition { + + static { + if (Skript.methodExists(Entity.class, "isTicking")) + register(CondIsTicking.class, "ticking", "entities"); + } + + @Override + public boolean check(Entity entity) { + return entity.isTicking(); + } + + @Override + protected String getPropertyName() { + return "ticking"; + } + +} + diff --git a/src/main/java/ch/njol/skript/conditions/CondIsWearing.java b/src/main/java/ch/njol/skript/conditions/CondIsWearing.java index 396ecf1b8ee..ece4ba03a29 100644 --- a/src/main/java/ch/njol/skript/conditions/CondIsWearing.java +++ b/src/main/java/ch/njol/skript/conditions/CondIsWearing.java @@ -1,29 +1,5 @@ -/** - * 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 org.bukkit.entity.LivingEntity; -import org.bukkit.event.Event; -import org.bukkit.inventory.EntityEquipment; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Nullable; - import ch.njol.skript.aliases.ItemType; import ch.njol.skript.conditions.base.PropertyCondition; import ch.njol.skript.conditions.base.PropertyCondition.PropertyType; @@ -34,15 +10,24 @@ import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; -/** - * @author Peter Güttinger - */ @Name("Is Wearing") -@Description("Checks whether a player is wearing some armour.") -@Examples({"player is wearing an iron chestplate and iron leggings", - "player is wearing all diamond armour"}) +@Description("Checks whether an entity is wearing some items (usually armor).") +@Examples({ + "player is wearing an iron chestplate and iron leggings", + "player is wearing all diamond armour", + "target is wearing wolf armor" +}) @Since("1.0") public class CondIsWearing extends Condition { @@ -50,14 +35,14 @@ public class CondIsWearing extends Condition { PropertyCondition.register(CondIsWearing.class, "wearing %itemtypes%", "livingentities"); } - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private Expression entities; - @SuppressWarnings("null") + @SuppressWarnings("NotNullFieldNotInitialized") private Expression types; @SuppressWarnings({"unchecked", "null"}) @Override - public boolean init(final Expression[] vars, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { + public boolean init(Expression[] vars, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { entities = (Expression) vars[0]; types = (Expression) vars[1]; setNegated(matchedPattern == 1); @@ -65,26 +50,32 @@ public boolean init(final Expression[] vars, final int matchedPattern, final } @Override - public boolean check(final Event e) { - return entities.check(e, - en -> types.check(e, - t -> { - EntityEquipment equip = en.getEquipment(); - if (equip == null) - return false; // No equipment -> not wearing anything - for (final ItemStack is : equip.getArmorContents()) { - if (t.isOfType(is) ^ t.isAll()) - return !t.isAll(); - } - return t.isAll(); - }), - isNegated()); + public boolean check(Event event) { + ItemType[] cachedTypes = types.getAll(event); + + return entities.check(event, entity -> { + EntityEquipment equipment = entity.getEquipment(); + if (equipment == null) + return false; // spigot nullability, no identifier as to why this occurs + + ItemStack[] contents = Arrays.stream(EquipmentSlot.values()) + .map(equipment::getItem) + .toArray(ItemStack[]::new); + + return SimpleExpression.check(cachedTypes, type -> { + for (ItemStack content : contents) { + if (type.isOfType(content) ^ type.isAll()) + return !type.isAll(); + } + return type.isAll(); + }, false, false); + }, isNegated()); } - + @Override - public String toString(final @Nullable Event e, final boolean debug) { - return PropertyCondition.toString(this, PropertyType.BE, e, debug, entities, - "wearing " + types.toString(e, debug)); + public String toString(@Nullable Event event, boolean debug) { + return PropertyCondition.toString(this, PropertyType.BE, event, debug, entities, + "wearing " + types.toString(event, debug)); } } diff --git a/src/main/java/ch/njol/skript/effects/Delay.java b/src/main/java/ch/njol/skript/effects/Delay.java index 1794e9c37d0..55f19199ef1 100644 --- a/src/main/java/ch/njol/skript/effects/Delay.java +++ b/src/main/java/ch/njol/skript/effects/Delay.java @@ -65,7 +65,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye duration = (Expression) exprs[0]; if (duration instanceof Literal) { // If we can, do sanity check for delays - long millis = ((Literal) duration).getSingle().getMilliSeconds(); + long millis = ((Literal) duration).getSingle().getAs(Timespan.TimePeriod.MILLISECOND); if (millis < 50) { Skript.warning("Delays less than one tick are not possible, defaulting to one tick."); } @@ -108,7 +108,7 @@ protected TriggerItem walk(Event event) { Variables.removeLocals(event); // Clean up local vars, we may be exiting now SkriptTimings.stop(timing); // Stop timing if it was even started - }, Math.max(duration.getTicks(), 1)); // Minimum delay is one tick, less than it is useless! + }, Math.max(duration.getAs(Timespan.TimePeriod.TICK), 1)); // Minimum delay is one tick, less than it is useless! } return null; } diff --git a/src/main/java/ch/njol/skript/effects/EffBan.java b/src/main/java/ch/njol/skript/effects/EffBan.java index ee5d39cab9e..bbaf1c1d7d7 100644 --- a/src/main/java/ch/njol/skript/effects/EffBan.java +++ b/src/main/java/ch/njol/skript/effects/EffBan.java @@ -93,7 +93,7 @@ public boolean init(final Expression[] exprs, final int matchedPattern, final protected void execute(final Event e) { final String reason = this.reason != null ? this.reason.getSingle(e) : null; // don't check for null, just ignore an invalid reason Timespan ts = this.expires != null ? this.expires.getSingle(e) : null; - final Date expires = ts != null ? new Date(System.currentTimeMillis() + ts.getMilliSeconds()) : null; + final Date expires = ts != null ? new Date(System.currentTimeMillis() + ts.getAs(Timespan.TimePeriod.MILLISECOND)) : null; final String source = "Skript ban effect"; for (final Object o : players.getArray(e)) { if (o instanceof Player) { diff --git a/src/main/java/ch/njol/skript/effects/EffContinue.java b/src/main/java/ch/njol/skript/effects/EffContinue.java index b83fefec4f2..3c3fb33a407 100644 --- a/src/main/java/ch/njol/skript/effects/EffContinue.java +++ b/src/main/java/ch/njol/skript/effects/EffContinue.java @@ -1,21 +1,3 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.effects; import ch.njol.skript.Skript; @@ -60,7 +42,7 @@ public class EffContinue extends Effect { static { Skript.registerEffect(EffContinue.class, "continue [this loop|[the] [current] loop]", - "continue [the] <" + JavaClasses.INTEGER_PATTERN + ">(st|nd|rd|th) loop" + "continue [the] <" + JavaClasses.INTEGER_NUMBER_PATTERN + ">(st|nd|rd|th) loop" ); } diff --git a/src/main/java/ch/njol/skript/effects/EffCustomName.java b/src/main/java/ch/njol/skript/effects/EffCustomName.java new file mode 100644 index 00000000000..26f178ae975 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffCustomName.java @@ -0,0 +1,53 @@ +package ch.njol.skript.effects; + +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.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +@Name("Toggle Custom Name Visibility") +@Description("Toggles the custom name visibility of an entity.") +@Examples({ + "show the custom name of event-entity", + "hide target's display name" +}) +@Since("INSERT VERSION") +public class EffCustomName extends Effect { + + static { + Skript.registerEffect(EffCustomName.class, + "(:show|hide) [the] (custom|display)[ ]name of %entities%", + "(:show|hide) %entities%'[s] (custom|display)[ ]name"); + } + + private boolean showCustomName; + private Expression entities; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + showCustomName = parseResult.hasTag("show"); + entities = (Expression) exprs[0]; + return true; + } + + @Override + protected void execute(Event event) { + for (Entity entity : entities.getArray(event)) { + entity.setCustomNameVisible(showCustomName); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return showCustomName ? "show" : "hide" + " the custom name of " + entities.toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/effects/EffEquip.java b/src/main/java/ch/njol/skript/effects/EffEquip.java index 74ca1572497..f0246d4bff6 100644 --- a/src/main/java/ch/njol/skript/effects/EffEquip.java +++ b/src/main/java/ch/njol/skript/effects/EffEquip.java @@ -14,19 +14,29 @@ import ch.njol.util.Kleenean; import org.bukkit.Material; import org.bukkit.Tag; -import org.bukkit.entity.*; +import org.bukkit.entity.AbstractHorse; +import org.bukkit.entity.ChestedHorse; +import org.bukkit.entity.Horse; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Llama; +import org.bukkit.entity.Player; +import org.bukkit.entity.Steerable; +import org.bukkit.entity.Wolf; import org.bukkit.event.Event; +import org.bukkit.inventory.AbstractHorseInventory; import org.bukkit.inventory.EntityEquipment; -import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.HorseInventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.LlamaInventory; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; @Name("Equip") -@Description( - "Equips or unequips an entity with some given armor. " + - "This will replace any armor that the entity is wearing." -) +@Description({ + "Equips or unequips an entity with the given itemtypes (usually armor).", + "This effect will replace any armor that the entity is already wearing." +}) @Examples({ "equip player with diamond helmet", "equip player with all diamond armor", @@ -34,63 +44,22 @@ "unequip all armor from player", "unequip player's armor" }) -@Since("1.0, 2.7 (multiple entities, unequip)") +@Since("1.0, 2.7 (multiple entities, unequip), INSERT VERSION (wolves)") public class EffEquip extends Effect { - private static final ItemType HORSE_ARMOR; - - static { - if (Skript.isRunningMinecraft(1, 14)) { - HORSE_ARMOR = new ItemType(Material.IRON_HORSE_ARMOR, Material.GOLDEN_HORSE_ARMOR, - Material.DIAMOND_HORSE_ARMOR, Material.LEATHER_HORSE_ARMOR); - } else { - HORSE_ARMOR = new ItemType(Material.IRON_HORSE_ARMOR, Material.GOLDEN_HORSE_ARMOR, - Material.DIAMOND_HORSE_ARMOR); - } - - Skript.registerEffect(EffEquip.class, - "equip [%livingentities%] with %itemtypes%", - "make %livingentities% wear %itemtypes%", - "unequip %itemtypes% [from %livingentities%]", - "unequip %livingentities%'[s] (armo[u]r|equipment)"); - } - - @SuppressWarnings("NotNullFieldNotInitialized") - private Expression entities; - @Nullable - private Expression itemTypes; - - private boolean equip = true; - - @Override - @SuppressWarnings("unchecked") - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { - if (matchedPattern == 0 || matchedPattern == 1) { - entities = (Expression) exprs[0]; - itemTypes = (Expression) exprs[1]; - } else if (matchedPattern == 2) { - itemTypes = (Expression) exprs[0]; - entities = (Expression) exprs[1]; - equip = false; - } else if (matchedPattern == 3) { - entities = (Expression) exprs[0]; - equip = false; - } - return true; - } - - private static final boolean SUPPORTS_STEERABLE = Skript.classExists("org.bukkit.entity.Steerable"); - private static ItemType CHESTPLATE; private static ItemType LEGGINGS; private static ItemType BOOTS; - private static ItemType CARPET; + private static ItemType CARPET = new ItemType(Tag.WOOL_CARPETS); + private static ItemType WOLF_ARMOR; + private static final ItemType HORSE_ARMOR = new ItemType(Material.LEATHER_HORSE_ARMOR, Material.IRON_HORSE_ARMOR, Material.GOLDEN_HORSE_ARMOR, Material.DIAMOND_HORSE_ARMOR); private static final ItemType SADDLE = new ItemType(Material.SADDLE); private static final ItemType CHEST = new ItemType(Material.CHEST); static { - boolean usesWoolCarpetTag = Skript.fieldExists(Tag.class, "WOOL_CARPET"); - CARPET = new ItemType(usesWoolCarpetTag ? Tag.WOOL_CARPETS : Tag.CARPETS); + boolean hasWolfArmor = Skript.fieldExists(Material.class, "WOLF_ARMOR"); + WOLF_ARMOR = hasWolfArmor ? new ItemType(Material.WOLF_ARMOR) : new ItemType(); + // added in 1.20.6 if (Skript.fieldExists(Tag.class, "ITEM_CHEST_ARMOR")) { CHESTPLATE = new ItemType(Tag.ITEMS_CHEST_ARMOR); @@ -103,6 +72,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye Material.GOLDEN_CHESTPLATE, Material.IRON_CHESTPLATE, Material.DIAMOND_CHESTPLATE, + Material.NETHERITE_CHESTPLATE, Material.ELYTRA ); @@ -111,7 +81,8 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye Material.CHAINMAIL_LEGGINGS, Material.GOLDEN_LEGGINGS, Material.IRON_LEGGINGS, - Material.DIAMOND_LEGGINGS + Material.DIAMOND_LEGGINGS, + Material.NETHERITE_LEGGINGS ); BOOTS = new ItemType( @@ -119,21 +90,44 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye Material.CHAINMAIL_BOOTS, Material.GOLDEN_BOOTS, Material.IRON_BOOTS, - Material.DIAMOND_BOOTS + Material.DIAMOND_BOOTS, + Material.NETHERITE_BOOTS ); - - // netherite - if (Skript.isRunningMinecraft(1,16)) { - CHESTPLATE.add(new ItemData(Material.NETHERITE_CHESTPLATE)); - LEGGINGS.add(new ItemData(Material.NETHERITE_LEGGINGS)); - BOOTS.add(new ItemData(Material.NETHERITE_BOOTS)); - } } } + private static final ItemType[] ALL_EQUIPMENT = new ItemType[] {CHESTPLATE, LEGGINGS, BOOTS, HORSE_ARMOR, SADDLE, CHEST, CARPET, WOLF_ARMOR}; + static { + Skript.registerEffect(EffEquip.class, + "equip [%livingentities%] with %itemtypes%", + "make %livingentities% wear %itemtypes%", + "unequip %itemtypes% [from %livingentities%]", + "unequip %livingentities%'[s] (armo[u]r|equipment)"); + } - private static final ItemType[] ALL_EQUIPMENT = new ItemType[] {CHESTPLATE, LEGGINGS, BOOTS, HORSE_ARMOR, SADDLE, CHEST, CARPET}; + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression entities; + private @UnknownNullability Expression itemTypes; + + private boolean equip = true; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) { + if (matchedPattern == 0 || matchedPattern == 1) { + entities = (Expression) exprs[0]; + itemTypes = (Expression) exprs[1]; + } else if (matchedPattern == 2) { + itemTypes = (Expression) exprs[0]; + entities = (Expression) exprs[1]; + equip = false; + } else if (matchedPattern == 3) { + entities = (Expression) exprs[0]; + equip = false; + } + return true; + } @Override protected void execute(Event event) { @@ -146,44 +140,44 @@ protected void execute(Event event) { unequipHelmet = true; } for (LivingEntity entity : entities.getArray(event)) { - if (SUPPORTS_STEERABLE && entity instanceof Steerable) { + if (entity instanceof Steerable steerable) { for (ItemType itemType : itemTypes) { if (SADDLE.isOfType(itemType.getMaterial())) { - ((Steerable) entity).setSaddle(equip); - } - } - } else if (entity instanceof Pig) { - for (ItemType itemType : itemTypes) { - if (itemType.isOfType(Material.SADDLE)) { - ((Pig) entity).setSaddle(equip); - break; + steerable.setSaddle(equip); } } - } else if (entity instanceof Llama) { - LlamaInventory inv = ((Llama) entity).getInventory(); + } else if (entity instanceof Llama llama) { + LlamaInventory inv = llama.getInventory(); for (ItemType itemType : itemTypes) { for (ItemStack item : itemType.getAll()) { if (CARPET.isOfType(item)) { inv.setDecor(equip ? item : null); } else if (CHEST.isOfType(item)) { - ((Llama) entity).setCarryingChest(equip); + llama.setCarryingChest(equip); } } } - } else if (entity instanceof AbstractHorse) { - // Spigot's API is bad, just bad... Abstract horse doesn't have horse inventory! - Inventory inv = ((AbstractHorse) entity).getInventory(); + } else if (entity instanceof AbstractHorse horse) { + AbstractHorseInventory inv = horse.getInventory(); for (ItemType itemType : itemTypes) { for (ItemStack item : itemType.getAll()) { if (SADDLE.isOfType(item)) { - inv.setItem(0, equip ? item : null); // Slot 0=saddle - } else if (HORSE_ARMOR.isOfType(item)) { - inv.setItem(1, equip ? item : null); // Slot 1=armor - } else if (CHEST.isOfType(item) && entity instanceof ChestedHorse) { - ((ChestedHorse) entity).setCarryingChest(equip); + inv.setSaddle(equip ? item : null); + } else if (HORSE_ARMOR.isOfType(item) && entity instanceof Horse) { + ((HorseInventory) inv).setArmor(equip ? item : null); + } else if (CHEST.isOfType(item) && entity instanceof ChestedHorse chestedHorse) { // a Donkey, Mule, Llama or TraderLlama. NOT a Horse + chestedHorse.setCarryingChest(equip); } } } + } else if (entity instanceof Wolf wolf) { + EntityEquipment equipment = wolf.getEquipment(); + for (ItemType itemType : itemTypes) { + for (ItemStack item : itemType.getAll()) { + if (WOLF_ARMOR.isOfType(item)) + equipment.setItem(EquipmentSlot.BODY, equip ? item : null); + } + } } else { EntityEquipment equipment = entity.getEquipment(); if (equipment == null) @@ -205,8 +199,8 @@ protected void execute(Event event) { equipment.setHelmet(null); } } - if (entity instanceof Player) - PlayerUtils.updateInventory((Player) entity); + if (entity instanceof Player player) + PlayerUtils.updateInventory(player); } } } diff --git a/src/main/java/ch/njol/skript/effects/EffExit.java b/src/main/java/ch/njol/skript/effects/EffExit.java index 264f466becb..7d9a2ac61ec 100644 --- a/src/main/java/ch/njol/skript/effects/EffExit.java +++ b/src/main/java/ch/njol/skript/effects/EffExit.java @@ -1,21 +1,3 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.effects; import ch.njol.skript.Skript; @@ -33,19 +15,18 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; -import java.util.ArrayList; import java.util.List; @Name("Exit") @Description("Exits a given amount of loops and conditionals, or the entire trigger.") @Examples({ "if player has any ore:", - "\tstop", + "\tstop", "message \"%player% has no ores!\"", "loop blocks above the player:", - "\tloop-block is not air:", - "\t\texit 2 sections", - "\tset loop-block to water" + "\tloop-block is not air:", + "\t\texit 2 sections", + "\tset loop-block to water" }) @Since("unknown (before 2.1)") public class EffExit extends Effect { @@ -54,7 +35,7 @@ public class EffExit extends Effect { Skript.registerEffect(EffExit.class, "(exit|stop) [trigger]", "(exit|stop) [1|a|the|this] (section|1:loop|2:conditional)", - "(exit|stop) <" + JavaClasses.INTEGER_PATTERN + "> (section|1:loop|2:conditional)s", + "(exit|stop) <" + JavaClasses.INTEGER_NUMBER_PATTERN + "> (section|1:loop|2:conditional)s", "(exit|stop) all (section|1:loop|2:conditional)s"); } diff --git a/src/main/java/ch/njol/skript/effects/EffIgnite.java b/src/main/java/ch/njol/skript/effects/EffIgnite.java index 07fa720a37a..1cd06e063ad 100644 --- a/src/main/java/ch/njol/skript/effects/EffIgnite.java +++ b/src/main/java/ch/njol/skript/effects/EffIgnite.java @@ -78,7 +78,7 @@ protected void execute(Event event) { Timespan timespan = this.duration.getSingle(event); if (timespan == null) return; - duration = (int) timespan.getTicks(); + duration = (int) timespan.getAs(Timespan.TimePeriod.TICK); } for (Entity entity : entities.getArray(event)) { if (event instanceof EntityDamageEvent && ((EntityDamageEvent) event).getEntity() == entity && !Delay.isDelayed(event)) { @@ -99,7 +99,7 @@ public void run() { @Override public String toString(@Nullable Event event, boolean debug) { if (ignite) - return "set " + entities.toString(event, debug) + " on fire for " + (duration != null ? duration.toString(event, debug) : Timespan.fromTicks(DEFAULT_DURATION).toString()); + return "set " + entities.toString(event, debug) + " on fire for " + (duration != null ? duration.toString(event, debug) : new Timespan(Timespan.TimePeriod.TICK, DEFAULT_DURATION).toString()); else return "extinguish " + entities.toString(event, debug); } diff --git a/src/main/java/ch/njol/skript/effects/EffPlaySound.java b/src/main/java/ch/njol/skript/effects/EffPlaySound.java index c67a86adde3..16a1f9ddf54 100644 --- a/src/main/java/ch/njol/skript/effects/EffPlaySound.java +++ b/src/main/java/ch/njol/skript/effects/EffPlaySound.java @@ -67,9 +67,8 @@ public class EffPlaySound extends Effect { // World - Location/Entity - Sound/String // 1.20 - spigot adds sound seeds - private static final boolean ADVENTURE_API = Skript.classExists("net.kyori.adventure.sound.Sound$Builder"); private static final boolean SPIGOT_SOUND_SEED = Skript.methodExists(Player.class, "playSound", Entity.class, Sound.class, SoundCategory.class, float.class, float.class, long.class); - private static final boolean HAS_SEED = ADVENTURE_API || SPIGOT_SOUND_SEED; + private static final boolean HAS_SEED = SoundReceiver.ADVENTURE_API || SPIGOT_SOUND_SEED; private static final boolean ENTITY_EMITTER_SOUND = Skript.methodExists(Player.class, "playSound", Entity.class, Sound.class, SoundCategory.class, float.class, float.class); private static final boolean ENTITY_EMITTER_STRING = Skript.methodExists(Player.class, "playSound", Entity.class, String.class, SoundCategory.class, float.class, float.class); private static final boolean ENTITY_EMITTER = ENTITY_EMITTER_SOUND || ENTITY_EMITTER_STRING; diff --git a/src/main/java/ch/njol/skript/effects/EffPoison.java b/src/main/java/ch/njol/skript/effects/EffPoison.java index 323bf18ff4e..5ca2aad6ba9 100644 --- a/src/main/java/ch/njol/skript/effects/EffPoison.java +++ b/src/main/java/ch/njol/skript/effects/EffPoison.java @@ -81,7 +81,7 @@ protected void execute(final Event e) { if (!cure) { Timespan dur; int d = (int) (duration != null && (dur = duration.getSingle(e)) != null ? - (dur.getTicks() >= Integer.MAX_VALUE ? Integer.MAX_VALUE : dur.getTicks()) : DEFAULT_DURATION); + (dur.getAs(Timespan.TimePeriod.TICK) >= Integer.MAX_VALUE ? Integer.MAX_VALUE : dur.getAs(Timespan.TimePeriod.TICK)) : DEFAULT_DURATION); if (le.hasPotionEffect(PotionEffectType.POISON)) { for (final PotionEffect pe : le.getActivePotionEffects()) { if (pe.getType() != PotionEffectType.POISON) diff --git a/src/main/java/ch/njol/skript/effects/EffPotion.java b/src/main/java/ch/njol/skript/effects/EffPotion.java index b95cb61e74c..6af9ad9f08d 100644 --- a/src/main/java/ch/njol/skript/effects/EffPotion.java +++ b/src/main/java/ch/njol/skript/effects/EffPotion.java @@ -125,7 +125,7 @@ protected void execute(Event event) { Timespan timespan = this.duration.getSingle(event); if (timespan == null) return; - duration = (int) Math.min(timespan.getTicks(), Integer.MAX_VALUE); + duration = (int) Math.min(timespan.getAs(Timespan.TimePeriod.TICK), Integer.MAX_VALUE); } for (LivingEntity entity : entities.getArray(event)) { for (PotionEffectType potionEffectType : potionEffectTypes) { diff --git a/src/main/java/ch/njol/skript/effects/EffSendTitle.java b/src/main/java/ch/njol/skript/effects/EffSendTitle.java index 8807b9801ad..279b1cd3415 100644 --- a/src/main/java/ch/njol/skript/effects/EffSendTitle.java +++ b/src/main/java/ch/njol/skript/effects/EffSendTitle.java @@ -101,17 +101,17 @@ protected void execute(final Event e) { if (this.fadeIn != null) { Timespan t = this.fadeIn.getSingle(e); - fadeIn = t != null ? (int) t.getTicks() : -1; + fadeIn = t != null ? (int) t.getAs(Timespan.TimePeriod.TICK) : -1; } if (this.stay != null) { Timespan t = this.stay.getSingle(e); - stay = t != null ? (int) t.getTicks() : -1; + stay = t != null ? (int) t.getAs(Timespan.TimePeriod.TICK) : -1; } if (this.fadeOut != null) { Timespan t = this.fadeOut.getSingle(e); - fadeOut = t != null ? (int) t.getTicks() : -1; + fadeOut = t != null ? (int) t.getAs(Timespan.TimePeriod.TICK) : -1; } for (Player p : recipients.getArray(e)) diff --git a/src/main/java/ch/njol/skript/effects/IndeterminateDelay.java b/src/main/java/ch/njol/skript/effects/IndeterminateDelay.java index dcbe6bc17e8..657e901b58e 100644 --- a/src/main/java/ch/njol/skript/effects/IndeterminateDelay.java +++ b/src/main/java/ch/njol/skript/effects/IndeterminateDelay.java @@ -57,7 +57,7 @@ protected TriggerItem walk(Event event) { Variables.setLocalVariables(event, localVars); TriggerItem.walk(next, event); - }, duration.getTicks()); + }, duration.getAs(Timespan.TimePeriod.TICK)); } return null; diff --git a/src/main/java/ch/njol/skript/entity/EntityData.java b/src/main/java/ch/njol/skript/entity/EntityData.java index 8423c380297..d11bb42882c 100644 --- a/src/main/java/ch/njol/skript/entity/EntityData.java +++ b/src/main/java/ch/njol/skript/entity/EntityData.java @@ -431,7 +431,7 @@ public static EntityDataInfo getInfo(final String codeName) { @SuppressWarnings("null") @Nullable public static EntityData parse(String s) { - Iterator>> it = infos.iterator(); + Iterator>> it = new ArrayList<>(infos).iterator(); return SkriptParser.parseStatic(Noun.stripIndefiniteArticle(s), it, null); } @@ -443,7 +443,7 @@ public static EntityData parse(String s) { */ @Nullable public static EntityData parseWithoutIndefiniteArticle(String s) { - Iterator>> it = infos.iterator(); + Iterator>> it = new ArrayList<>(infos).iterator(); return SkriptParser.parseStatic(s, it, null); } diff --git a/src/main/java/ch/njol/skript/events/EvtFirework.java b/src/main/java/ch/njol/skript/events/EvtFirework.java index e3f18a31527..7c64987f0de 100644 --- a/src/main/java/ch/njol/skript/events/EvtFirework.java +++ b/src/main/java/ch/njol/skript/events/EvtFirework.java @@ -1,21 +1,3 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.events; import ch.njol.skript.Skript; @@ -25,8 +7,8 @@ import ch.njol.skript.util.Color; import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; +import ch.njol.skript.util.ColorRGB; import org.bukkit.FireworkEffect; import org.bukkit.event.Event; import org.bukkit.event.entity.FireworkExplodeEvent; @@ -41,15 +23,15 @@ public class EvtFirework extends SkriptEvent { //Making the event argument type fireworkeffects, led to Skript having troubles parsing for some reason. Skript.registerEvent("Firework Explode", EvtFirework.class, FireworkExplodeEvent.class, "[a] firework explo(d(e|ing)|sion) [colo[u]red %-colors%]") .description("Called when a firework explodes.") - .examples("on firework explode", - "on firework exploding colored red, light green and black", - "on firework explosion colored light green:", - " broadcast \"A firework colored %colors% was exploded at %location%!\"")//TODO fix + .examples("on firework explode:", + "\tif event-colors contains red:", + "on firework exploding colored red, light green and black:", + "on firework explosion colored rgb 0, 255, 0:", + "\tbroadcast \"A firework colored %colors% was exploded at %location%!\"") .since("2.4"); } - - @Nullable - private Literal colors; + + private @Nullable Literal colors; @SuppressWarnings("unchecked") @Override @@ -58,16 +40,23 @@ public boolean init(Literal[] args, int matchedPattern, ParseResult parseResu colors = (Literal) args[0]; return true; } - - @SuppressWarnings("null") + @Override - public boolean check(Event e) { + public boolean check(Event event) { + if (!(event instanceof FireworkExplodeEvent fireworkExplodeEvent)) + return false; + if (colors == null) return true; - List colours = Arrays.stream(colors.getArray(e)) - .map(color -> color.asBukkitColor()) - .collect(Collectors.toList()); - FireworkMeta meta = ((FireworkExplodeEvent)e).getEntity().getFireworkMeta(); + + List colours = colors.stream(event) + .map(color -> { + if (color instanceof ColorRGB) + return color.asBukkitColor(); + return color.asDyeColor().getFireworkColor(); + }) + .toList(); + FireworkMeta meta = fireworkExplodeEvent.getEntity().getFireworkMeta(); for (FireworkEffect effect : meta.getEffects()) { if (colours.containsAll(effect.getColors())) return true; diff --git a/src/main/java/ch/njol/skript/events/EvtPeriodical.java b/src/main/java/ch/njol/skript/events/EvtPeriodical.java index b5ff9f2c60d..1167743b2c9 100644 --- a/src/main/java/ch/njol/skript/events/EvtPeriodical.java +++ b/src/main/java/ch/njol/skript/events/EvtPeriodical.java @@ -72,7 +72,7 @@ public boolean init(Literal[] args, int matchedPattern, ParseResult parseResu @Override public boolean postLoad() { - long ticks = period.getTicks(); + long ticks = period.getAs(Timespan.TimePeriod.TICK); if (worlds == null) { taskIDs = new int[]{ diff --git a/src/main/java/ch/njol/skript/expressions/ExprAffectedEntities.java b/src/main/java/ch/njol/skript/expressions/ExprAffectedEntities.java index 677a2e95cbb..71ae9176e06 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAffectedEntities.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAffectedEntities.java @@ -19,7 +19,10 @@ package ch.njol.skript.expressions; import java.util.Iterator; +import java.util.Objects; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.util.coll.CollectionUtils; import org.bukkit.entity.LivingEntity; import org.bukkit.event.Event; import org.bukkit.event.entity.AreaEffectCloudApplyEvent; @@ -58,20 +61,52 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye return true; } - @Nullable @Override - protected LivingEntity[] get(Event e) { - if (e instanceof AreaEffectCloudApplyEvent) - return ((AreaEffectCloudApplyEvent) e).getAffectedEntities().toArray(new LivingEntity[0]); + protected LivingEntity @Nullable [] get(Event event) { + if (event instanceof AreaEffectCloudApplyEvent areaEvent) + return areaEvent.getAffectedEntities().toArray(new LivingEntity[0]); return null; } - @Nullable @Override - public Iterator iterator(Event e) { - if (e instanceof AreaEffectCloudApplyEvent) - return ((AreaEffectCloudApplyEvent) e).getAffectedEntities().iterator(); - return super.iterator(e); + public @Nullable Iterator iterator(Event event) { + if (event instanceof AreaEffectCloudApplyEvent areaEvent) + return areaEvent.getAffectedEntities().iterator(); + return super.iterator(event); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + return switch (mode) { + case ADD, SET, DELETE, REMOVE -> CollectionUtils.array(LivingEntity[].class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + if (!(event instanceof AreaEffectCloudApplyEvent areaEvent)) + return; + + LivingEntity[] entities = (LivingEntity[]) delta; + switch (mode) { + case REMOVE: + for (LivingEntity entity : entities) { + areaEvent.getAffectedEntities().remove(entity); + } + break; + case SET: + areaEvent.getAffectedEntities().clear(); + // FALLTHROUGH + case ADD: + for (LivingEntity entity : entities) { + areaEvent.getAffectedEntities().add(entity); + } + break; + case RESET, DELETE: + areaEvent.getAffectedEntities().clear(); + break; + } } @Override @@ -90,7 +125,7 @@ public Class getReturnType() { } @Override - public String toString(@Nullable Event e, boolean debug) { + public String toString(@Nullable Event event, boolean debug) { return "the affected entities"; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprAngle.java b/src/main/java/ch/njol/skript/expressions/ExprAngle.java new file mode 100644 index 00000000000..7916f8fd405 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprAngle.java @@ -0,0 +1,77 @@ +package ch.njol.skript.expressions; + +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.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.jetbrains.annotations.Nullable; + +@Name("Angle") +@Description({ + "Represents the passed number value in degrees.", + "If radians is specified, converts the passed value to degrees. This conversion may not be entirely accurate, " + + "due to floating point precision.", +}) +@Examples({ + "set {_angle} to 90 degrees", + "{_angle} is 90 # true", + "180 degrees is pi # true", + "pi radians is 180 degrees # true" +}) +@Since("INSERT VERSION") +public class ExprAngle extends SimpleExpression { + + static { + Skript.registerExpression(ExprAngle.class, Number.class, ExpressionType.SIMPLE, + "%number% [in] deg[ree][s]", + "%number% [in] rad[ian][s]", + "%numbers% in deg[ree][s]", + "%numbers% in rad[ian][s]"); + } + + private Expression angle; + private boolean isRadians; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, + Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + angle = (Expression) expressions[0]; + isRadians = matchedPattern % 2 != 0; + return true; + } + + @Override + protected Number @Nullable [] get(Event event) { + Number[] numbers = angle.getArray(event); + + if (isRadians) + for (int i = 0; i < numbers.length; i++) + numbers[i] = Math.toDegrees(numbers[i].doubleValue()); + + return numbers; + } + + @Override + public boolean isSingle() { + return angle.isSingle(); + } + + @Override + public Class getReturnType() { + return Number.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return angle.toString(event, debug) + " in " + (isRadians ? "degrees" : "radians"); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java b/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java index 15497fa6e0e..884cc629639 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java +++ b/src/main/java/ch/njol/skript/expressions/ExprArmorSlot.java @@ -18,6 +18,7 @@ */ package ch.njol.skript.expressions; +import ch.njol.skript.Skript; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Keywords; @@ -30,39 +31,53 @@ import ch.njol.skript.util.slot.EquipmentSlot.EquipSlot; import ch.njol.skript.util.slot.Slot; import ch.njol.util.Kleenean; -import org.bukkit.entity.LivingEntity; +import org.bukkit.Material; +import org.bukkit.entity.*; import org.bukkit.event.Event; import org.bukkit.inventory.EntityEquipment; import org.jetbrains.annotations.Nullable; -import java.util.Arrays; -import java.util.Locale; +import java.util.*; import java.util.stream.Stream; -@Name("Armour Slot") -@Description("Equipment of living entities, i.e. the boots, leggings, chestplate or helmet.") +@Name("Armor Slot") +@Description({ + "Equipment of living entities, i.e. the boots, leggings, chestplate or helmet.", + "Body armor is a special slot that can only be used for:", + "
    ", + "
  • Horses: Horse armour (doesn't work on zombie or skeleton horses)
  • ", + "
  • Wolves: Wolf Armor
  • ", + "
  • Llamas (regular or trader): Carpet
  • ", + "
" +}) @Examples({ "set chestplate of the player to a diamond chestplate", "helmet of player is neither a helmet nor air # player is wearing a block, e.g. from another plugin" }) @Keywords("armor") -@Since("1.0, 2.8.0 (Armour)") +@Since("1.0, 2.8.0 (armor), INSERT VERSION (body armor)") public class ExprArmorSlot extends PropertyExpression { + private static final Set> bodyEntities = new HashSet<>(Arrays.asList(Horse.class, Llama.class, TraderLlama.class)); + static { - register(ExprArmorSlot.class, Slot.class, "((boots:(boots|shoes)|leggings:leg[ging]s|chestplate:chestplate[s]|helmet:helmet[s]) [(item|:slot)]|armour:armo[u]r[s])", "livingentities"); + if (Material.getMaterial("WOLF_ARMOR") != null) + bodyEntities.add(Wolf.class); + + register(ExprArmorSlot.class, Slot.class, "((boots:(boots|shoes)|leggings:leg[ging]s|chestplate:chestplate[s]|helmet:helmet[s]) [(item|:slot)]|armour:armo[u]r[s]|bodyarmor:body armo[u]r)", "livingentities"); } - @Nullable - private EquipSlot slot; + private @Nullable EquipSlot slot; private boolean explicitSlot; private boolean isArmor; + private boolean isBody; @Override @SuppressWarnings("unchecked") public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + isBody = parseResult.hasTag("bodyarmor"); isArmor = parseResult.hasTag("armour"); - slot = isArmor ? null : EquipSlot.valueOf(parseResult.tags.get(0).toUpperCase(Locale.ENGLISH)); + slot = (isArmor || isBody) ? null : EquipSlot.valueOf(parseResult.tags.get(0).toUpperCase(Locale.ENGLISH)); explicitSlot = parseResult.hasTag("slot"); // User explicitly asked for SLOT, not item setExpr((Expression) exprs[0]); return true; @@ -76,6 +91,11 @@ protected Slot[] get(Event event, LivingEntity[] source) { .flatMap(equipment -> { if (equipment == null) return null; + if (isBody) { + if (!bodyEntities.contains(equipment.getHolder().getType().getEntityClass())) + return null; + return Stream.of(new EquipmentSlot(equipment, EquipSlot.BODY, explicitSlot)); + } return Stream.of( new EquipmentSlot(equipment, EquipSlot.HELMET, explicitSlot), new EquipmentSlot(equipment, EquipSlot.CHESTPLATE, explicitSlot), @@ -96,7 +116,7 @@ protected Slot[] get(Event event, LivingEntity[] source) { @Override public boolean isSingle() { - return !isArmor && super.isSingle(); + return isBody || (!isArmor && super.isSingle()); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java b/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java index 1777e73799f..5445865f858 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBurnCookTime.java @@ -88,14 +88,14 @@ protected Timespan[] get(Event event, Block[] source) { if (isEvent) { if (!(event instanceof FurnaceBurnEvent)) return new Timespan[0]; - return CollectionUtils.array(Timespan.fromTicks(((FurnaceBurnEvent) event).getBurnTime())); + return CollectionUtils.array(new Timespan(Timespan.TimePeriod.TICK, ((FurnaceBurnEvent) event).getBurnTime())); } else { return Arrays.stream(source) .map(Block::getState) .filter(blockState -> blockState instanceof Furnace) .map(state -> { Furnace furnace = (Furnace) state; - return Timespan.fromTicks(cookTime ? furnace.getCookTime() : furnace.getBurnTime()); + return new Timespan(Timespan.TimePeriod.TICK, cookTime ? furnace.getCookTime() : furnace.getBurnTime()); }) .toArray(Timespan[]::new); } @@ -136,7 +136,7 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { return; FurnaceBurnEvent burnEvent = (FurnaceBurnEvent) event; - burnEvent.setBurnTime((int) value.apply(Timespan.fromTicks(burnEvent.getBurnTime())).getTicks()); + burnEvent.setBurnTime((int) value.apply(new Timespan(Timespan.TimePeriod.TICK, burnEvent.getBurnTime())).getAs(Timespan.TimePeriod.TICK)); return; } @@ -145,7 +145,7 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { if (!(state instanceof Furnace)) continue; Furnace furnace = (Furnace) block.getState(); - long time = value.apply(Timespan.fromTicks(cookTime ? furnace.getCookTime() : furnace.getBurnTime())).getTicks(); + long time = value.apply(new Timespan(Timespan.TimePeriod.TICK, cookTime ? furnace.getCookTime() : furnace.getBurnTime())).getAs(Timespan.TimePeriod.TICK); if (cookTime) { furnace.setCookTime((short) time); diff --git a/src/main/java/ch/njol/skript/expressions/ExprCmdCooldownInfo.java b/src/main/java/ch/njol/skript/expressions/ExprCmdCooldownInfo.java index 14e2d003f12..a1904c5fcac 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprCmdCooldownInfo.java +++ b/src/main/java/ch/njol/skript/expressions/ExprCmdCooldownInfo.java @@ -138,7 +138,7 @@ public void change(Event e, @Nullable Object[] delta, Changer.ChangeMode mode) { CommandSender sender = commandEvent.getSender(); if (cooldown == null || !(sender instanceof Player)) return; - long cooldownMs = cooldown.getMilliSeconds(); + long cooldownMs = cooldown.getAs(Timespan.TimePeriod.MILLISECOND); UUID uuid = ((Player) sender).getUniqueId(); if (pattern <= 1) { @@ -146,7 +146,7 @@ public void change(Event e, @Nullable Object[] delta, Changer.ChangeMode mode) { switch (mode) { case ADD: case REMOVE: - long change = (mode == Changer.ChangeMode.ADD ? 1 : -1) * timespan.getMilliSeconds(); + long change = (mode == Changer.ChangeMode.ADD ? 1 : -1) * timespan.getAs(Timespan.TimePeriod.MILLISECOND); if (pattern == 0) { long remaining = command.getRemainingMilliseconds(uuid, commandEvent); long changed = remaining + change; @@ -169,9 +169,9 @@ public void change(Event e, @Nullable Object[] delta, Changer.ChangeMode mode) { break; case SET: if (pattern == 0) - command.setRemainingMilliseconds(uuid, commandEvent, timespan.getMilliSeconds()); + command.setRemainingMilliseconds(uuid, commandEvent, timespan.getAs(Timespan.TimePeriod.MILLISECOND)); else - command.setElapsedMilliSeconds(uuid, commandEvent, timespan.getMilliSeconds()); + command.setElapsedMilliSeconds(uuid, commandEvent, timespan.getAs(Timespan.TimePeriod.MILLISECOND)); break; } } else if (pattern == 3) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprEntityItemUseTime.java b/src/main/java/ch/njol/skript/expressions/ExprEntityItemUseTime.java index 7b7dba018f7..892ea3c7409 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprEntityItemUseTime.java +++ b/src/main/java/ch/njol/skript/expressions/ExprEntityItemUseTime.java @@ -63,8 +63,8 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override public Timespan convert(LivingEntity livingEntity) { if (remaining) - return Timespan.fromTicks(livingEntity.getItemUseRemainingTime()); - return Timespan.fromTicks(livingEntity.getHandRaisedTime()); + return new Timespan(Timespan.TimePeriod.TICK, livingEntity.getItemUseRemainingTime()); + return new Timespan(Timespan.TimePeriod.TICK, livingEntity.getHandRaisedTime()); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprFireTicks.java b/src/main/java/ch/njol/skript/expressions/ExprFireTicks.java index e12c124c1c1..5dd6370b39e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFireTicks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFireTicks.java @@ -24,7 +24,11 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Timespan.TimePeriod; +import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; import org.bukkit.entity.Entity; import org.bukkit.event.Event; @@ -32,30 +36,46 @@ @Name("Entity Fire Burn Duration") @Description("How much time an entity will be burning for.") -@Examples({"send \"You will stop burning in %fire time of player%\""}) -@Since("2.7") +@Examples({ + "send \"You will stop burning in %fire time of player%\"", + "send the max burn time of target" +}) +@Since("2.7, INSERT VERSION (maximum)") public class ExprFireTicks extends SimplePropertyExpression { static { - register(ExprFireTicks.class, Timespan.class, "(burn[ing]|fire) (time|duration)", "entities"); + register(ExprFireTicks.class, Timespan.class, "[:max[imum]] (burn[ing]|fire) (time|duration)", "entities"); + } + + private boolean max; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + max = (parseResult.hasTag("max")); + return true; } @Override @Nullable public Timespan convert(Entity entity) { - return Timespan.fromTicks(Math.max(entity.getFireTicks(), 0)); + return new Timespan(TimePeriod.TICK, (max ? entity.getMaxFireTicks() : Math.max(entity.getFireTicks(), 0))); } @Override @Nullable public Class[] acceptChange(ChangeMode mode) { - return (mode != ChangeMode.REMOVE_ALL) ? CollectionUtils.array(Timespan.class) : null; + if (max) + return null; + return switch (mode) { + case ADD, SET, RESET, DELETE, REMOVE -> CollectionUtils.array(Timespan.class); + default -> null; + }; } @Override public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { Entity[] entities = getExpr().getArray(event); - int change = delta == null ? 0 : (int) ((Timespan) delta[0]).getTicks(); + int change = delta == null ? 0 : (int) ((Timespan) delta[0]).getAs(Timespan.TimePeriod.TICK); switch (mode) { case REMOVE: change = -change; diff --git a/src/main/java/ch/njol/skript/expressions/ExprFireworkEffect.java b/src/main/java/ch/njol/skript/expressions/ExprFireworkEffect.java index 8765a2aa69d..802b43192e4 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFireworkEffect.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFireworkEffect.java @@ -1,23 +1,6 @@ -/** - * 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.util.ColorRGB; import org.bukkit.FireworkEffect; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -86,11 +69,19 @@ protected FireworkEffect[] get(Event e) { return null; FireworkEffect.Builder builder = FireworkEffect.builder().with(type); - for (Color colour : color.getArray(e)) - builder.withColor(colour.asBukkitColor()); + for (Color colour : color.getArray(e)) { + if (colour instanceof ColorRGB) + builder.withColor(colour.asBukkitColor()); + else + builder.withColor(colour.asDyeColor().getFireworkColor()); + } if (hasFade) - for (Color colour : fade.getArray(e)) - builder.withFade(colour.asBukkitColor()); + for (Color colour : fade.getArray(e)) { + if (colour instanceof ColorRGB) + builder.withFade(colour.asBukkitColor()); + else + builder.withFade(colour.asDyeColor().getFireworkColor()); + } builder.flicker(flicker); builder.trail(trail); diff --git a/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java b/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java index 003cc77fae3..11588c466a3 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFreezeTicks.java @@ -48,7 +48,7 @@ public class ExprFreezeTicks extends SimplePropertyExpression @Override @Nullable public Timespan convert(Entity entity) { - return Timespan.fromTicks(entity.getFreezeTicks()); + return new Timespan(Timespan.TimePeriod.TICK, entity.getFreezeTicks()); } @Override @@ -59,7 +59,7 @@ public Class[] acceptChange(ChangeMode mode) { @Override public void change(Event e, @Nullable Object[] delta, ChangeMode mode) { - int time = delta == null ? 0 : (int) ((Timespan) delta[0]).getTicks(); + int time = delta == null ? 0 : (int) ((Timespan) delta[0]).getAs(Timespan.TimePeriod.TICK); int newTime; switch (mode) { case ADD: diff --git a/src/main/java/ch/njol/skript/expressions/ExprItemCooldown.java b/src/main/java/ch/njol/skript/expressions/ExprItemCooldown.java index e981cfabea1..74dcee55cbb 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprItemCooldown.java +++ b/src/main/java/ch/njol/skript/expressions/ExprItemCooldown.java @@ -83,7 +83,7 @@ protected Timespan[] get(Event event) { int i = 0; for (Player player : players) { for (ItemType itemType : itemTypes) { - timespan[i++] = Timespan.fromTicks_i(player.getCooldown(itemType.getMaterial())); + timespan[i++] = new Timespan(Timespan.TimePeriod.TICK, player.getCooldown(itemType.getMaterial())); } } return timespan; @@ -100,7 +100,7 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { if (mode != ChangeMode.RESET && mode != ChangeMode.DELETE && delta == null) return; - int ticks = delta != null ? (int) ((Timespan) delta[0]).getTicks_i() : 0; // 0 for DELETE/RESET + int ticks = delta != null ? (int) ((Timespan) delta[0]).getAs(Timespan.TimePeriod.TICK) : 0; // 0 for DELETE/RESET Player[] players = this.players.getArray(event); List itemTypes = this.itemtypes.stream(event) .filter(ItemType::hasType) diff --git a/src/main/java/ch/njol/skript/expressions/ExprItemFlags.java b/src/main/java/ch/njol/skript/expressions/ExprItemFlags.java new file mode 100644 index 00000000000..29b7b7a8904 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprItemFlags.java @@ -0,0 +1,97 @@ +package ch.njol.skript.expressions; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.PropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.event.Event; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.meta.ItemMeta; +import ch.njol.skript.aliases.ItemType; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.Set; + +@Name("Item Flags") +@Description("The item flags of an item. Can be modified.") +@Examples({ + "set item flags of player's tool to hide enchants and hide attributes", + "add hide potion effects to item flags of player's held item", + "remove hide enchants from item flags of {legendary sword}" +}) +@Since("INSERT VERSION") +public class ExprItemFlags extends PropertyExpression { + + static { + register(ExprItemFlags.class, ItemFlag.class, "item flags", "itemtypes"); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + setExpr((Expression) exprs[0]); + return true; + } + + @Override + protected ItemFlag[] get(Event event, ItemType[] source) { + Set flags = new HashSet<>(); + for (ItemType itemType : source) { + ItemMeta meta = itemType.getItemMeta(); + flags.addAll(meta.getItemFlags()); + } + return flags.toArray(new ItemFlag[0]); + } + + @Override + public @Nullable Class[] acceptChange(ChangeMode mode) { + return switch (mode) { + case SET, ADD, REMOVE, RESET, DELETE -> CollectionUtils.array(ItemFlag[].class); + default -> null; + }; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + ItemFlag[] flags = delta != null ? (ItemFlag[]) delta : new ItemFlag[0]; + + for (ItemType itemType : getExpr().getArray(event)) { + ItemMeta meta = itemType.getItemMeta(); + switch (mode) { + case SET -> { + meta.removeItemFlags(ItemFlag.values()); + meta.addItemFlags(flags); + } + case ADD -> meta.addItemFlags(flags); + case REMOVE -> meta.removeItemFlags(flags); + case RESET, DELETE -> meta.removeItemFlags(ItemFlag.values()); + default -> { + return; + } + } + itemType.setItemMeta(meta); + } + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class getReturnType() { + return ItemFlag.class; + } + + @Override + public String toString(Event event, boolean debug) { + return "item flags of " + getExpr().toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprMaxFreezeTicks.java b/src/main/java/ch/njol/skript/expressions/ExprMaxFreezeTicks.java index 4ff19a44791..13c72a32f61 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprMaxFreezeTicks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprMaxFreezeTicks.java @@ -48,7 +48,7 @@ public class ExprMaxFreezeTicks extends SimplePropertyExpression public Timespan convert(Entity entity) { if (!(entity instanceof Item)) return null; - return Timespan.fromTicks(((Item) entity).getPickupDelay()); + return new Timespan(Timespan.TimePeriod.TICK, ((Item) entity).getPickupDelay()); } @@ -70,7 +70,7 @@ public Class[] acceptChange(ChangeMode mode) { @Override public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { Entity[] entities = getExpr().getArray(event); - int change = delta == null ? 0 : (int) ((Timespan) delta[0]).getTicks(); + int change = delta == null ? 0 : (int) ((Timespan) delta[0]).getAs(Timespan.TimePeriod.TICK); switch (mode) { case REMOVE: change = -change; diff --git a/src/main/java/ch/njol/skript/expressions/ExprPortalCooldown.java b/src/main/java/ch/njol/skript/expressions/ExprPortalCooldown.java index 722d423ca13..830152c0c00 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPortalCooldown.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPortalCooldown.java @@ -59,7 +59,7 @@ public class ExprPortalCooldown extends SimplePropertyExpression[] acceptChange(ChangeMode mode) { @Override public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { Entity[] entities = getExpr().getArray(event); - int change = delta == null ? 0 : (int) ((Timespan) delta[0]).getTicks_i(); + int change = delta == null ? 0 : (int) ((Timespan) delta[0]).getAs(Timespan.TimePeriod.TICK); switch (mode) { case REMOVE: change = -change; // allow fall-through to avoid duplicate code diff --git a/src/main/java/ch/njol/skript/expressions/ExprPotionEffect.java b/src/main/java/ch/njol/skript/expressions/ExprPotionEffect.java index c19972fc7c5..c8939bb6754 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprPotionEffect.java +++ b/src/main/java/ch/njol/skript/expressions/ExprPotionEffect.java @@ -85,7 +85,7 @@ protected PotionEffect[] get(final Event e) { if (this.timespan != null) { Timespan timespan = this.timespan.getSingle(e); if (timespan != null) - ticks = (int) timespan.getTicks(); + ticks = (int) timespan.getAs(Timespan.TimePeriod.TICK); } return new PotionEffect[]{new PotionEffect(potionEffectType, ticks, tier, ambient, particles)}; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprResonatingTime.java b/src/main/java/ch/njol/skript/expressions/ExprResonatingTime.java index 8abd40edb43..ef4067dfd46 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprResonatingTime.java +++ b/src/main/java/ch/njol/skript/expressions/ExprResonatingTime.java @@ -51,7 +51,7 @@ public class ExprResonatingTime extends SimplePropertyExpression { public @Nullable Timespan convert(Block from) { if (from.getState() instanceof Bell) { int shakingTicks = ((Bell) from.getState(false)).getShakingTicks(); - return shakingTicks == 0 ? null : Timespan.fromTicks(shakingTicks); + return shakingTicks == 0 ? null : new Timespan(Timespan.TimePeriod.TICK, shakingTicks); } return null; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprSkullOwner.java b/src/main/java/ch/njol/skript/expressions/ExprSkullOwner.java index bc102126022..204354bf567 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSkullOwner.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSkullOwner.java @@ -1,5 +1,6 @@ package ch.njol.skript.expressions; +import ch.njol.skript.aliases.ItemType; import ch.njol.skript.bukkitutil.ItemUtils; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.doc.Description; @@ -7,39 +8,48 @@ import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.util.slot.Slot; import ch.njol.util.coll.CollectionUtils; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.block.Block; -import org.bukkit.block.BlockState; import org.bukkit.block.Skull; import org.bukkit.event.Event; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; +import org.bukkit.profile.PlayerProfile; import org.jetbrains.annotations.Nullable; +import java.util.function.Consumer; + @Name("Skull Owner") @Description("The skull owner of a player skull.") @Examples({ "set {_owner} to the skull owner of event-block", - "set skull owner of {_block} to \"Njol\" parsed as offlineplayer" + "set skull owner of {_block} to \"Njol\" parsed as offlineplayer", + "set head owner of player's tool to {_player}" }) -@Since("2.9.0") -public class ExprSkullOwner extends SimplePropertyExpression { +@Since("2.9.0, INSERT VERSION (of items)") +public class ExprSkullOwner extends SimplePropertyExpression { static { - register(ExprSkullOwner.class, OfflinePlayer.class, "(head|skull) owner", "blocks"); + register(ExprSkullOwner.class, OfflinePlayer.class, "(head|skull) owner", "slots/itemtypes/itemstacks/blocks"); } @Override - public @Nullable OfflinePlayer convert(Block block) { - BlockState state = block.getState(); - if (!(state instanceof Skull)) - return null; - return ((Skull) state).getOwningPlayer(); + public @Nullable OfflinePlayer convert(Object object) { + if (object instanceof Block block && block.getState() instanceof Skull skull) { + return skull.getOwningPlayer(); + } else { + ItemStack skullItem = ItemUtils.asItemStack(object); + if (skullItem == null || !(skullItem.getItemMeta() instanceof SkullMeta skullMeta)) + return null; + return skullMeta.getOwningPlayer(); + } } @Override - @Nullable - public Class[] acceptChange(ChangeMode mode) { + public Class @Nullable [] acceptChange(ChangeMode mode) { if (mode == ChangeMode.SET) return CollectionUtils.array(OfflinePlayer.class); return null; @@ -48,25 +58,55 @@ public Class[] acceptChange(ChangeMode mode) { @Override public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { OfflinePlayer offlinePlayer = (OfflinePlayer) delta[0]; - for (Block block : getExpr().getArray(event)) { - BlockState state = block.getState(); - if (!(state instanceof Skull)) - continue; - - Skull skull = (Skull) state; - if (offlinePlayer.getName() != null) { - skull.setOwningPlayer(offlinePlayer); - } else if (ItemUtils.CAN_CREATE_PLAYER_PROFILE) { - //noinspection deprecation - skull.setOwnerProfile(Bukkit.createPlayerProfile(offlinePlayer.getUniqueId(), "")); + Consumer blockChanger = getBlockChanger(offlinePlayer); + Consumer metaChanger = getMetaChanger(offlinePlayer); + for (Object object : getExpr().getArray(event)) { + if (object instanceof Block block && block.getState() instanceof Skull skull) { + blockChanger.accept(skull); + skull.update(true, false); } else { - //noinspection deprecation - skull.setOwner(""); + ItemStack skullItem = ItemUtils.asItemStack(object); + if (skullItem == null || !(skullItem.getItemMeta() instanceof SkullMeta skullMeta)) + continue; + metaChanger.accept(skullMeta); + if (object instanceof Slot slot) { + skullItem.setItemMeta(skullMeta); + slot.setItem(skullItem); + } else if (object instanceof ItemType itemType) { + itemType.setItemMeta(skullMeta); + } else if (object instanceof ItemStack itemStack) { + itemStack.setItemMeta(skullMeta); + } } - skull.update(true, false); } } + private Consumer getBlockChanger(OfflinePlayer offlinePlayer) { + if (offlinePlayer.getName() != null) { + return skull -> skull.setOwningPlayer(offlinePlayer); + } else if (ItemUtils.CAN_CREATE_PLAYER_PROFILE) { + //noinspection deprecation + PlayerProfile profile = Bukkit.createPlayerProfile(offlinePlayer.getUniqueId(), ""); + //noinspection deprecation + return skull -> skull.setOwnerProfile(profile); + } + //noinspection deprecation + return skull -> skull.setOwner(""); + } + + private Consumer getMetaChanger(OfflinePlayer offlinePlayer) { + if (offlinePlayer.getName() != null) { + return skullMeta -> skullMeta.setOwningPlayer(offlinePlayer); + } else if (ItemUtils.CAN_CREATE_PLAYER_PROFILE) { + //noinspection deprecation + PlayerProfile profile = Bukkit.createPlayerProfile(offlinePlayer.getUniqueId(), ""); + //noinspection deprecation + return skullMeta -> skullMeta.setOwnerProfile(profile); + } + //noinspection deprecation + return skullMeta -> skullMeta.setOwner(""); + } + @Override public Class getReturnType() { return OfflinePlayer.class; diff --git a/src/main/java/ch/njol/skript/expressions/ExprTime.java b/src/main/java/ch/njol/skript/expressions/ExprTime.java index e2199e18a80..c3a83a91ca1 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTime.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTime.java @@ -74,7 +74,7 @@ public Class[] acceptChange(final ChangeMode mode) { @Override public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { - if (getExpr() == null || delta == null) + if (delta == null) return; Object time = delta[0]; diff --git a/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java b/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java index 0ab6041da01..c1a861e98ba 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTimePlayed.java @@ -75,7 +75,7 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { if (delta == null) return; - long ticks = ((Timespan) delta[0]).getTicks(); + long ticks = ((Timespan) delta[0]).getAs(Timespan.TimePeriod.TICK); for (OfflinePlayer offlinePlayer : getExpr().getArray(event)) { if (!IS_OFFLINE_SUPPORTED && !offlinePlayer.isOnline()) continue; @@ -84,7 +84,7 @@ public void change(Event event, @Nullable Object[] delta, ChangeMode mode) { if (playerTimespan == null) continue; - long playerTicks = playerTimespan.getTicks(); + long playerTicks = playerTimespan.getAs(Timespan.TimePeriod.TICK); switch (mode) { case ADD: ticks = playerTicks + ticks; @@ -115,9 +115,9 @@ protected String getPropertyName() { @Nullable private Timespan getTimePlayed(OfflinePlayer offlinePlayer) { if (IS_OFFLINE_SUPPORTED) { - return Timespan.fromTicks(offlinePlayer.getStatistic(Statistic.PLAY_ONE_MINUTE)); + return new Timespan(Timespan.TimePeriod.TICK, offlinePlayer.getStatistic(Statistic.PLAY_ONE_MINUTE)); } else if (offlinePlayer.isOnline()) { - return Timespan.fromTicks(offlinePlayer.getPlayer().getStatistic(Statistic.PLAY_ONE_MINUTE)); + return new Timespan(Timespan.TimePeriod.TICK, offlinePlayer.getPlayer().getStatistic(Statistic.PLAY_ONE_MINUTE)); } return null; } diff --git a/src/main/java/ch/njol/skript/expressions/ExprTimes.java b/src/main/java/ch/njol/skript/expressions/ExprTimes.java index f9d99e09abf..16d4de265d5 100755 --- a/src/main/java/ch/njol/skript/expressions/ExprTimes.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTimes.java @@ -51,7 +51,7 @@ public class ExprTimes extends SimpleExpression { static { Skript.registerExpression(ExprTimes.class, Long.class, ExpressionType.SIMPLE, - "%number% time[s]", "once", "twice"); + "%number% time[s]", "once", "twice", "thrice"); } @SuppressWarnings("null") diff --git a/src/main/java/ch/njol/skript/expressions/ExprTimespanDetails.java b/src/main/java/ch/njol/skript/expressions/ExprTimespanDetails.java index 06f334e982b..7f3800e5d4e 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprTimespanDetails.java +++ b/src/main/java/ch/njol/skript/expressions/ExprTimespanDetails.java @@ -57,7 +57,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable public Long convert(Timespan time) { - return time.getMilliSeconds() / type.getTime(); + return time.getAs(Timespan.TimePeriod.MILLISECOND) / type.getTime(); } @Override diff --git a/src/main/java/ch/njol/skript/expressions/ExprWithItemFlags.java b/src/main/java/ch/njol/skript/expressions/ExprWithItemFlags.java new file mode 100644 index 00000000000..b295beda96f --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprWithItemFlags.java @@ -0,0 +1,80 @@ +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.Since; +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.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Nullable; + +@Name("Item with Item Flags") +@Description("Creates a new item with the specified item flags.") +@Examples({ + "give player diamond sword with item flags hide enchants and hide attributes", + "set {_item} to player's tool with item flag hide additional tooltip", + "give player torch with hide placed on item flag", + "set {_item} to diamond sword with all item flags" +}) +@Since("INSERT VERSION") +public class ExprWithItemFlags extends SimpleExpression { + + static { + Skript.registerExpression(ExprWithItemFlags.class, ItemType.class, ExpressionType.COMBINED, + "%itemtypes% with [the] item flag[s] %itemflags%", + "%itemtypes% with [the] %itemflags% item flag[s]"); + } + + private Expression itemFlags; + private Expression itemTypes; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + itemTypes = (Expression) exprs[0]; + itemFlags = (Expression) exprs[1]; + return true; + } + + @Override + protected ItemType[] get(Event event) { + ItemType[] types = itemTypes.getArray(event); + ItemFlag[] flags = itemFlags.getArray(event); + + ItemType[] result = new ItemType[types.length]; + for (int i = 0; i < types.length; i++) { + ItemType clonedType = types[i].clone(); + ItemMeta meta = clonedType.getItemMeta(); + if (meta != null) { + meta.addItemFlags(flags); + clonedType.setItemMeta(meta); + } + result[i] = clonedType; + } + + return result; + } + + @Override + public boolean isSingle() { + return itemTypes.isSingle(); + } + + @Override + public Class getReturnType() { + return ItemType.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return itemTypes.toString(event, debug) + " with item flags " + itemFlags.toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java b/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java index 772559136fc..71f5efa9fca 100644 --- a/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java +++ b/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java @@ -1,6 +1,5 @@ package ch.njol.skript.expressions.base; - import ch.njol.skript.Skript; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; @@ -8,8 +7,10 @@ import ch.njol.skript.lang.SyntaxElement; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Kleenean; +import com.google.common.base.Preconditions; import org.bukkit.event.Event; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnknownNullability; import org.skriptlang.skript.lang.converter.Converter; import org.skriptlang.skript.lang.converter.Converters; @@ -95,15 +96,15 @@ public static void registerDefault(Class> expression Skript.registerExpression(expressionClass, type, ExpressionType.PROPERTY, getDefaultPatterns(property, fromType)); } - @Nullable - private Expression expr; + private @UnknownNullability Expression expr; /** * Sets the expression this expression represents a property of. No reference to the expression should be kept. * - * @param expr + * @param expr The expression this expression represents a property of. */ - protected final void setExpr(Expression expr) { + protected final void setExpr(@NotNull Expression expr) { + Preconditions.checkNotNull(expr, "The expr param cannot be null"); this.expr = expr; } diff --git a/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java b/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java index bc4d550dabf..dafe28d61e6 100644 --- a/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java +++ b/src/main/java/ch/njol/skript/hooks/economy/classes/Money.java @@ -158,7 +158,7 @@ private static Money parseMoney(String s, String addition) { @Nullable private static Double parseDouble(String s) { - if (!JavaClasses.NUMBER_PATTERN.matcher(s).matches()) + if (!JavaClasses.DECIMAL_PATTERN.matcher(s).matches()) return null; try { return Double.parseDouble(s); diff --git a/src/main/java/ch/njol/skript/lang/Condition.java b/src/main/java/ch/njol/skript/lang/Condition.java index 9ba8c40e853..5e3d287fce1 100644 --- a/src/main/java/ch/njol/skript/lang/Condition.java +++ b/src/main/java/ch/njol/skript/lang/Condition.java @@ -21,8 +21,10 @@ import ch.njol.skript.Skript; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.util.Checker; +import ch.njol.util.Kleenean; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.condition.Conditional; import java.util.Iterator; @@ -31,7 +33,7 @@ * * @see Skript#registerCondition(Class, String...) */ -public abstract class Condition extends Statement { +public abstract class Condition extends Statement implements Conditional { public enum ConditionType { /** @@ -67,6 +69,11 @@ protected Condition() {} */ public abstract boolean check(Event event); + @Override + public Kleenean evaluate(Event event) { + return Kleenean.get(check(event)); + } + @Override public final boolean run(Event event) { return check(event); diff --git a/src/main/java/ch/njol/skript/lang/SyntaxStringBuilder.java b/src/main/java/ch/njol/skript/lang/SyntaxStringBuilder.java index b9ce40d7b19..e2ea883c5b7 100644 --- a/src/main/java/ch/njol/skript/lang/SyntaxStringBuilder.java +++ b/src/main/java/ch/njol/skript/lang/SyntaxStringBuilder.java @@ -30,31 +30,36 @@ public SyntaxStringBuilder(@Nullable Event event, boolean debug) { } /** - * Adds an object to the string. + * Adds an object to the string and returns the builder. * Spaces are automatically added between the provided objects. * If the object is a {@link Debuggable} it will be formatted using * {@link Debuggable#toString(Event, boolean)}. * * @param object The object to add. + * @return The builder. */ - public void append(@NotNull Object object) { + public SyntaxStringBuilder append(@NotNull Object object) { Preconditions.checkNotNull(object); if (object instanceof Debuggable debuggable) { joiner.add(debuggable.toString(event, debug)); } else { joiner.add(object.toString()); } + return this; } /** - * Adds multiple objects to the string. + * Adds multiple objects to the string and returns the builder. * Spaces are automatically added between the provided objects. + * * @param objects The objects to add. + * @return The builder. */ - public void append(@NotNull Object... objects) { + public SyntaxStringBuilder append(@NotNull Object... objects) { for (Object object : objects) { append(object); } + return this; } @Override diff --git a/src/main/java/ch/njol/skript/lang/Variable.java b/src/main/java/ch/njol/skript/lang/Variable.java index 4d499b814c1..423286455ff 100644 --- a/src/main/java/ch/njol/skript/lang/Variable.java +++ b/src/main/java/ch/njol/skript/lang/Variable.java @@ -615,6 +615,15 @@ public void change(Event event, Object @Nullable [] delta, ChangeMode mode) thro } } + /** + * {@inheritDoc} + * @param getAll This has no effect for a Variable, as {@link #getArray(Event)} is the same as {@link #getAll(Event)}. + */ + @Override + public void changeInPlace(Event event, Function changeFunction, boolean getAll) { + changeInPlace(event, changeFunction); + } + @Override public void changeInPlace(Event event, Function changeFunction) { if (!list) { diff --git a/src/main/java/ch/njol/skript/sections/EffSecSpawn.java b/src/main/java/ch/njol/skript/sections/EffSecSpawn.java index 7f627f0f750..da243abf56b 100644 --- a/src/main/java/ch/njol/skript/sections/EffSecSpawn.java +++ b/src/main/java/ch/njol/skript/sections/EffSecSpawn.java @@ -136,9 +136,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye } @Override - @Nullable - @SuppressWarnings({"unchecked", "rawtypes"}) - protected TriggerItem walk(Event event) { + protected @Nullable TriggerItem walk(Event event) { lastSpawned = null; Consumer consumer; @@ -146,13 +144,7 @@ protected TriggerItem walk(Event event) { consumer = entity -> { lastSpawned = entity; SpawnEvent spawnEvent = new SpawnEvent(entity); - // Copy the local variables from the calling code to this section - Variables.setLocalVariables(spawnEvent, Variables.copyLocalVariables(event)); - TriggerItem.walk(trigger, spawnEvent); - // And copy our (possibly modified) local variables back to the calling code - Variables.setLocalVariables(event, Variables.copyLocalVariables(spawnEvent)); - // Clear spawnEvent's local variables as it won't be done automatically - Variables.removeLocals(spawnEvent); + Variables.withLocalVariables(event, spawnEvent, () -> TriggerItem.walk(trigger, spawnEvent)); }; } else { consumer = null; @@ -167,6 +159,7 @@ protected TriggerItem walk(Event event) { double typeAmount = amount * type.getAmount(); for (int i = 0; i < typeAmount; i++) { if (consumer != null) { + //noinspection unchecked,rawtypes type.data.spawn(location, (Consumer) consumer); // lastSpawned set within Consumer } else { lastSpawned = type.data.spawn(location); diff --git a/src/main/java/ch/njol/skript/sections/SecConditional.java b/src/main/java/ch/njol/skript/sections/SecConditional.java index 01aac81bf50..02830488cc6 100644 --- a/src/main/java/ch/njol/skript/sections/SecConditional.java +++ b/src/main/java/ch/njol/skript/sections/SecConditional.java @@ -1,21 +1,3 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.sections; import ch.njol.skript.ScriptLoader; @@ -37,6 +19,9 @@ import com.google.common.collect.Iterables; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; +import org.skriptlang.skript.lang.condition.Conditional; +import org.skriptlang.skript.lang.condition.Conditional.Operator; import java.util.ArrayList; import java.util.Iterator; @@ -68,7 +53,6 @@ "" }) @Since("1.0") -@SuppressWarnings("NotNullFieldNotInitialized") public class SecConditional extends Section { private static final SkriptPattern THEN_PATTERN = PatternCompiler.compile("then [run]"); @@ -93,7 +77,7 @@ private enum ConditionalType { } private ConditionalType type; - private List conditions = new ArrayList<>(); + private @UnknownNullability Conditional conditional; private boolean ifAny; private boolean parseIf; private boolean parseIfPassed; @@ -112,7 +96,7 @@ public boolean init(Expression[] exprs, type = CONDITIONAL_PATTERNS.getInfo(matchedPattern); ifAny = parseResult.hasTag("any"); parseIf = parseResult.hasTag("parse"); - multiline = parseResult.regexes.size() == 0 && type != ConditionalType.ELSE; + multiline = parseResult.regexes.isEmpty() && type != ConditionalType.ELSE; ParserInstance parser = getParser(); // ensure this conditional is chained correctly (e.g. an else must have an if) @@ -168,6 +152,8 @@ public boolean init(Expression[] exprs, Class[] currentEvents = parser.getCurrentEvents(); String currentEventName = parser.getCurrentEventName(); + List> conditionals = new ArrayList<>(); + // Change event if using 'parse if' if (parseIf) { //noinspection unchecked @@ -196,7 +182,7 @@ public boolean init(Expression[] exprs, // if this condition was invalid, don't bother parsing the rest if (condition == null) return false; - conditions.add(condition); + conditionals.add(condition); } } parser.setNode(sectionNode); @@ -206,7 +192,7 @@ public boolean init(Expression[] exprs, // Don't print a default error if 'if' keyword wasn't provided Condition condition = Condition.parse(expr, parseResult.hasTag("implicit") ? null : "Can't understand this condition: '" + expr + "'"); if (condition != null) - conditions.add(condition); + conditionals.add(condition); } if (parseIf) { @@ -214,8 +200,10 @@ public boolean init(Expression[] exprs, parser.setCurrentEventName(currentEventName); } - if (conditions.isEmpty()) + if (conditionals.isEmpty()) return false; + + conditional = Conditional.compound(ifAny ? Operator.OR : Operator.AND, conditionals); } // ([else] parse if) If condition is valid and false, do not parse the section @@ -289,14 +277,12 @@ && getElseIfs(triggerItems).stream().map(SecConditional::getHasDelayAfter).allMa } @Override - @Nullable - public TriggerItem getNext() { + public @Nullable TriggerItem getNext() { return getSkippedNext(); } - @Nullable @Override - protected TriggerItem walk(Event event) { + protected @Nullable TriggerItem walk(Event event) { if (type == ConditionalType.THEN || (parseIf && !parseIfPassed)) { return getActualNext(); } else if (parseIf || checkConditions(event)) { @@ -327,7 +313,7 @@ public ExecutionIntent triggerExecutionIntent() { @Nullable private TriggerItem getSkippedNext() { TriggerItem next = getActualNext(); - while (next instanceof SecConditional && ((SecConditional) next).type != ConditionalType.IF) + while (next instanceof SecConditional nextSecCond && nextSecCond.type != ConditionalType.IF) next = next.getActualNext(); return next; } @@ -335,22 +321,20 @@ private TriggerItem getSkippedNext() { @Override public String toString(@Nullable Event event, boolean debug) { String parseIf = this.parseIf ? "parse " : ""; - switch (type) { - case IF: + return switch (type) { + case IF -> { if (multiline) - return parseIf + "if " + (ifAny ? "any" : "all"); - return parseIf + "if " + conditions.get(0).toString(event, debug); - case ELSE_IF: + yield parseIf + "if " + (ifAny ? "any" : "all"); + yield parseIf + "if " + conditional.toString(event, debug); + } + case ELSE_IF -> { if (multiline) - return "else " + parseIf + "if " + (ifAny ? "any" : "all"); - return "else " + parseIf + "if " + conditions.get(0).toString(event, debug); - case ELSE: - return "else"; - case THEN: - return "then"; - default: - throw new IllegalStateException(); - } + yield "else " + parseIf + "if " + (ifAny ? "any" : "all"); + yield "else " + parseIf + "if " + conditional.toString(event, debug); + } + case ELSE -> "else"; + case THEN -> "then"; + }; } private Kleenean getHasDelayAfter() { @@ -363,19 +347,18 @@ private Kleenean getHasDelayAfter() { * @param type the type of conditional section to find. if null is provided, any type is allowed. * @return the closest conditional section */ - @Nullable - private static SecConditional getPrecedingConditional(List triggerItems, @Nullable ConditionalType type) { + private static @Nullable SecConditional getPrecedingConditional(List triggerItems, @Nullable ConditionalType type) { // loop through the triggerItems in reverse order so that we find the most recent items first for (int i = triggerItems.size() - 1; i >= 0; i--) { TriggerItem triggerItem = triggerItems.get(i); - if (triggerItem instanceof SecConditional conditionalSection) { - if (conditionalSection.type == ConditionalType.ELSE) { + if (triggerItem instanceof SecConditional precedingSecConditional) { + if (precedingSecConditional.type == ConditionalType.ELSE) { // if the conditional is an else, return null because it belongs to a different condition and ends // this one return null; - } else if (type == null || conditionalSection.type == type) { + } else if (type == null || precedingSecConditional.type == type) { // if the conditional matches the type argument, we found our most recent preceding conditional section - return conditionalSection; + return precedingSecConditional; } } else { return null; @@ -404,8 +387,8 @@ private static List getElseIfs(List triggerItems) { List list = new ArrayList<>(); for (int i = triggerItems.size() - 1; i >= 0; i--) { TriggerItem triggerItem = triggerItems.get(i); - if (triggerItem instanceof SecConditional secConditional && secConditional.type == ConditionalType.ELSE_IF) { - list.add(secConditional); + if (triggerItem instanceof SecConditional precedingSecConditional && precedingSecConditional.type == ConditionalType.ELSE_IF) { + list.add(precedingSecConditional); } else { break; } @@ -414,17 +397,10 @@ private static List getElseIfs(List triggerItems) { } private boolean checkConditions(Event event) { - if (conditions.isEmpty()) { // else and then - return true; - } else if (ifAny) { - return conditions.stream().anyMatch(c -> c.check(event)); - } else { - return conditions.stream().allMatch(c -> c.check(event)); - } + return conditional == null || conditional.evaluate(event).isTrue(); } - @Nullable - private Node getNextNode(Node precedingNode, ParserInstance parser) { + private @Nullable Node getNextNode(Node precedingNode, ParserInstance parser) { // iterating over the parent node causes the current node to change, so we need to store it to reset it later Node originalCurrentNode = parser.getNode(); SectionNode parentNode = precedingNode.getParent(); diff --git a/src/main/java/ch/njol/skript/util/Date.java b/src/main/java/ch/njol/skript/util/Date.java index 50e2fd88fe9..85df22cb927 100644 --- a/src/main/java/ch/njol/skript/util/Date.java +++ b/src/main/java/ch/njol/skript/util/Date.java @@ -87,7 +87,7 @@ public long getTimestamp() { * @param span Timespan to add */ public void add(final Timespan span) { - timestamp += span.getMilliSeconds(); + timestamp += span.getAs(Timespan.TimePeriod.MILLISECOND); } /** @@ -96,7 +96,7 @@ public void add(final Timespan span) { * @param span Timespan to subtract */ public void subtract(final Timespan span) { - timestamp -= span.getMilliSeconds(); + timestamp -= span.getAs(Timespan.TimePeriod.MILLISECOND); } /** @@ -106,7 +106,7 @@ public void subtract(final Timespan span) { * @return New Date with the added timespan */ public Date plus(Timespan span) { - return new Date(timestamp + span.getMilliSeconds()); + return new Date(timestamp + span.getAs(Timespan.TimePeriod.MILLISECOND)); } /** @@ -116,7 +116,7 @@ public Date plus(Timespan span) { * @return New Date with the subtracted timespan */ public Date minus(Timespan span) { - return new Date(timestamp - span.getMilliSeconds()); + return new Date(timestamp - span.getAs(Timespan.TimePeriod.MILLISECOND)); } @Override diff --git a/src/main/java/ch/njol/skript/util/PotionEffectUtils.java b/src/main/java/ch/njol/skript/util/PotionEffectUtils.java index 6616190b133..8059c89ae9e 100644 --- a/src/main/java/ch/njol/skript/util/PotionEffectUtils.java +++ b/src/main/java/ch/njol/skript/util/PotionEffectUtils.java @@ -118,7 +118,7 @@ public static String toString(PotionEffect potionEffect) { builder.append(" of tier ").append(potionEffect.getAmplifier() + 1); if (!potionEffect.hasParticles()) builder.append(" without particles"); - builder.append(" for ").append(potionEffect.getDuration() == -1 ? "infinity" : Timespan.fromTicks(Math.abs(potionEffect.getDuration()))); + builder.append(" for ").append(potionEffect.getDuration() == -1 ? "infinity" : new Timespan(Timespan.TimePeriod.TICK, Math.abs(potionEffect.getDuration()))); if (!potionEffect.hasIcon()) builder.append(" without icon"); return builder.toString(); diff --git a/src/main/java/ch/njol/skript/util/SkriptColor.java b/src/main/java/ch/njol/skript/util/SkriptColor.java index 598fc66b6fa..445c6bdac2d 100644 --- a/src/main/java/ch/njol/skript/util/SkriptColor.java +++ b/src/main/java/ch/njol/skript/util/SkriptColor.java @@ -193,10 +193,9 @@ public static SkriptColor fromDyeColor(DyeColor dye) { public static SkriptColor fromBukkitColor(org.bukkit.Color color) { for (SkriptColor c : colors) { - if (c.asBukkitColor().equals(color)) + if (c.asBukkitColor().equals(color) || c.asDyeColor().getFireworkColor().equals(color)) return c; } - assert false; return null; } diff --git a/src/main/java/ch/njol/skript/util/Timespan.java b/src/main/java/ch/njol/skript/util/Timespan.java index 4fa26717c3f..2522e2d2e36 100644 --- a/src/main/java/ch/njol/skript/util/Timespan.java +++ b/src/main/java/ch/njol/skript/util/Timespan.java @@ -1,115 +1,33 @@ -/** - * 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.Skript; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.localization.GeneralWords; import ch.njol.skript.localization.Language; -import ch.njol.skript.localization.LanguageChangeListener; import ch.njol.skript.localization.Noun; import ch.njol.util.NonNullPair; import ch.njol.util.coll.CollectionUtils; import ch.njol.yggdrasil.YggdrasilSerializable; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval; +import com.google.common.base.Preconditions; import org.jetbrains.annotations.Nullable; import java.time.Duration; import java.time.temporal.*; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static java.time.temporal.ChronoUnit.*; +import static java.time.temporal.ChronoUnit.MILLIS; +/** + * Represents a duration of time, such as 2 days, similar to {@link Duration}. + */ public class Timespan implements YggdrasilSerializable, Comparable, TemporalAmount { // REMIND unit - public enum TimePeriod implements TemporalUnit { - - MILLISECOND(1L), - TICK(50L), - SECOND(1000L), - MINUTE(SECOND.time * 60L), - HOUR(MINUTE.time * 60L), - DAY(HOUR.time * 24L), - WEEK(DAY.time * 7L), - MONTH(DAY.time * 30L), // Who cares about 28, 29 or 31 days? - YEAR(DAY.time * 365L); - - private final Noun name; - private final Noun shortName; - private final long time; - - TimePeriod(long time) { - this.name = new Noun("time." + this.name().toLowerCase(Locale.ENGLISH) + ".full"); - this.shortName = new Noun("time." + this.name().toLowerCase(Locale.ENGLISH) + ".short"); - this.time = time; - } - - public long getTime() { - return time; - } - - public String getFullForm() { - return name.toString(); - } - - public String getShortForm() { - return shortName.toString(); - } - - @Override - public Duration getDuration() { - return Duration.ofMillis(time); - } - - @Override - public boolean isDurationEstimated() { - return false; - } - - @Override - public boolean isDateBased() { - return false; - } - - @Override - public boolean isTimeBased() { - return true; - } - - @Override - public R addTo(R temporal, long amount) { - return (R) temporal.plus(amount, this); - } - - @Override - public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) { - return temporal1Inclusive.until(temporal2Exclusive, this); - } - - } + private static final Pattern TIMESPAN_PATTERN = Pattern.compile("^(\\d+):(\\d\\d)(:\\d\\d){0,2}(?\\.\\d{1,4})?$"); + private static final Pattern TIMESPAN_NUMBER_PATTERN = Pattern.compile("^\\d+(\\.\\d+)?$"); + private static final Pattern TIMESPAN_SPLIT_PATTERN = Pattern.compile("[:.]"); + private static final Pattern SHORT_FORM_PATTERN = Pattern.compile("^(\\d+(?:\\.\\d+)?)([a-zA-Z]+)$"); private static final List> SIMPLE_VALUES = Arrays.asList( new NonNullPair<>(TimePeriod.YEAR.name, TimePeriod.YEAR.time), @@ -124,33 +42,21 @@ public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) { private static final Map PARSE_VALUES = new HashMap<>(); static { - Language.addListener(new LanguageChangeListener() { - @Override - public void onLanguageChange() { - for (TimePeriod time : TimePeriod.values()) { - PARSE_VALUES.put(time.name.getSingular().toLowerCase(Locale.ENGLISH), time.getTime()); - PARSE_VALUES.put(time.name.getPlural().toLowerCase(Locale.ENGLISH), time.getTime()); - PARSE_VALUES.put(time.shortName.getSingular().toLowerCase(Locale.ENGLISH), time.getTime()); - PARSE_VALUES.put(time.shortName.getPlural().toLowerCase(Locale.ENGLISH), time.getTime()); - } + Language.addListener(() -> { + for (TimePeriod time : TimePeriod.values()) { + PARSE_VALUES.put(time.name.getSingular().toLowerCase(Locale.ENGLISH), time.getTime()); + PARSE_VALUES.put(time.name.getPlural().toLowerCase(Locale.ENGLISH), time.getTime()); + PARSE_VALUES.put(time.shortName.getSingular().toLowerCase(Locale.ENGLISH), time.getTime()); + PARSE_VALUES.put(time.shortName.getPlural().toLowerCase(Locale.ENGLISH), time.getTime()); } }); } - private static final Pattern TIMESPAN_PATTERN = Pattern.compile("^(\\d+):(\\d\\d)(:\\d\\d){0,2}(?\\.\\d{1,4})?$"); - private static final Pattern TIMESPAN_NUMBER_PATTERN = Pattern.compile("^\\d+(\\.\\d+)?$"); - private static final Pattern TIMESPAN_SPLIT_PATTERN = Pattern.compile("[:.]"); - private static final Pattern SHORT_FORM_PATTERN = Pattern.compile("^(\\d+(?:\\.\\d+)?)([a-zA-Z]+)$"); - - private final long millis; - - @Nullable - public static Timespan parse(String value) { + public static @Nullable Timespan parse(String value) { return parse(value, ParseContext.DEFAULT); } - @Nullable - public static Timespan parse(String value, ParseContext context) { + public static @Nullable Timespan parse(String value, ParseContext context) { if (value.isEmpty()) return null; @@ -172,7 +78,7 @@ else if (length == 3 && !hasMs || length == 4) // HH:MM:SS[.ms] offset = 1; for (int i = 0; i < substring.length; i++) { - totalMillis += times[offset + i] * Utils.parseLong("" + substring[i]); + totalMillis += times[offset + i] * Utils.parseLong(substring[i]); } } else { // minutes/seconds/.. etc String[] substring = value.toLowerCase(Locale.ENGLISH).split("\\s+"); @@ -238,6 +144,37 @@ else if (length == 3 && !hasMs || length == 4) // HH:MM:SS[.ms] return new Timespan(totalMillis); } + public static Timespan fromDuration(Duration duration) { + return new Timespan(duration.toMillis()); + } + + public static String toString(long millis) { + return toString(millis, 0); + } + + public static String toString(long millis, int flags) { + for (int i = 0; i < SIMPLE_VALUES.size() - 1; i++) { + NonNullPair pair = SIMPLE_VALUES.get(i); + long second1 = pair.getSecond(); + if (millis >= second1) { + long remainder = millis % second1; + double second = 1. * remainder / SIMPLE_VALUES.get(i + 1).getSecond(); + if (!"0".equals(Skript.toString(second))) { // bad style but who cares... + return toString(Math.floor(1. * millis / second1), pair, flags) + " " + GeneralWords.and + " " + toString(remainder, flags); + } else { + return toString(1. * millis / second1, pair, flags); + } + } + } + return toString(1. * millis / SIMPLE_VALUES.get(SIMPLE_VALUES.size() - 1).getSecond(), SIMPLE_VALUES.get(SIMPLE_VALUES.size() - 1), flags); + } + + private static String toString(double amount, NonNullPair pair, int flags) { + return pair.getFirst().withAmount(amount, flags); + } + + private final long millis; + public Timespan() { millis = 0; } @@ -248,8 +185,7 @@ public Timespan() { * @param millis The milliseconds of Timespan */ public Timespan(long millis) { - if (millis < 0) - throw new IllegalArgumentException("millis must be >= 0"); + Preconditions.checkArgument(millis >= 0, "millis must be >= 0"); this.millis = millis; } @@ -257,57 +193,51 @@ public Timespan(long millis) { * Builds a Timespan from the given long parameter of a specific {@link TimePeriod}. * * @param timePeriod The requested TimePeriod - * @param time The time of the requested TimePeriod + * @param time The time of the requested TimePeriod */ public Timespan(TimePeriod timePeriod, long time) { - if (time < 0) - throw new IllegalArgumentException("time must be >= 0"); + Preconditions.checkArgument(time >= 0, "time must be >= 0"); this.millis = time * timePeriod.getTime(); } /** - * Builds a Timespan from the given long parameter. - * * @deprecated Use {@link #Timespan(TimePeriod, long)} - * - * @param ticks The amount of Minecraft ticks to convert to a timespan. - * @return Timespan based on the provided long. */ - @Deprecated - @ApiStatus.ScheduledForRemoval + @Deprecated(forRemoval = true) public static Timespan fromTicks(long ticks) { return new Timespan(ticks * 50L); } /** - * @deprecated Use {@link Timespan#fromTicks(long)} instead. Old API naming changes. + * @deprecated Use {@link #Timespan(TimePeriod, long)} instead. */ - @Deprecated - @ScheduledForRemoval + @Deprecated(forRemoval = true) public static Timespan fromTicks_i(long ticks) { return new Timespan(ticks * 50L); } /** * @deprecated Use {@link Timespan#getAs(TimePeriod)} - * - * @return the amount of milliseconds this timespan represents. */ - @Deprecated - @ScheduledForRemoval + @Deprecated(forRemoval = true) public long getMilliSeconds() { - return millis; + return getAs(TimePeriod.MILLISECOND); } /** * @deprecated Use {@link Timespan#getAs(TimePeriod)} - * - * @return the amount of Minecraft ticks this timespan represents. */ - @Deprecated - @ScheduledForRemoval + @Deprecated(forRemoval = true) public long getTicks() { - return Math.round((millis / 50.0)); + return getAs(TimePeriod.TICK); + } + + /** + * @deprecated Use {@link Timespan#getAs(TimePeriod)} + */ + @Deprecated(forRemoval = true) + public long getTicks_i() { + return getAs(TimePeriod.TICK); } /** @@ -318,52 +248,48 @@ public long getAs(TimePeriod timePeriod) { } /** - * @deprecated Use getTicks() instead. Old API naming changes. + * @return Converts this timespan to a {@link Duration}. */ - @Deprecated - @ScheduledForRemoval - public long getTicks_i() { - return Math.round((millis / 50.0)); + public Duration getDuration() { + return Duration.ofMillis(millis); } @Override - public String toString() { - return toString(millis); - } + public long get(TemporalUnit unit) { + if (unit instanceof TimePeriod period) + return this.getAs(period); - public String toString(int flags) { - return toString(millis, flags); + if (!(unit instanceof ChronoUnit chrono)) + throw new UnsupportedTemporalTypeException("Not a supported temporal unit: " + unit); + + return switch (chrono) { + case MILLIS -> this.getAs(TimePeriod.MILLISECOND); + case SECONDS -> this.getAs(TimePeriod.SECOND); + case MINUTES -> this.getAs(TimePeriod.MINUTE); + case HOURS -> this.getAs(TimePeriod.HOUR); + case DAYS -> this.getAs(TimePeriod.DAY); + case WEEKS -> this.getAs(TimePeriod.WEEK); + case MONTHS -> this.getAs(TimePeriod.MONTH); + case YEARS -> this.getAs(TimePeriod.YEAR); + default -> throw new UnsupportedTemporalTypeException("Not a supported time unit: " + chrono); + }; } - public static String toString(long millis) { - return toString(millis, 0); + @Override + public List getUnits() { + return List.of(TimePeriod.values()).reversed(); } - @SuppressWarnings("null") - public static String toString(long millis, int flags) { - for (int i = 0; i < SIMPLE_VALUES.size() - 1; i++) { - if (millis >= SIMPLE_VALUES.get(i).getSecond()) { - long remainder = millis % SIMPLE_VALUES.get(i).getSecond(); - double second = 1. * remainder / SIMPLE_VALUES.get(i + 1).getSecond(); - if (!"0".equals(Skript.toString(second))) { // bad style but who cares... - return toString(Math.floor(1. * millis / SIMPLE_VALUES.get(i).getSecond()), SIMPLE_VALUES.get(i), flags) + " " + GeneralWords.and + " " + toString(remainder, flags); - } else { - return toString(1. * millis / SIMPLE_VALUES.get(i).getSecond(), SIMPLE_VALUES.get(i), flags); - } - } - } - return toString(1. * millis / SIMPLE_VALUES.get(SIMPLE_VALUES.size() - 1).getSecond(), SIMPLE_VALUES.get(SIMPLE_VALUES.size() - 1), flags); + @Override + public Temporal addTo(Temporal temporal) { + return temporal.plus(millis, MILLIS); } - private static String toString(double amount, NonNullPair pair, int flags) { - return pair.getFirst().withAmount(amount, flags); + @Override + public Temporal subtractFrom(Temporal temporal) { + return temporal.minus(millis, MILLIS); } - /** - * Compare this Timespan with another - * @param time the Timespan to be compared. - * @return -1 if this Timespan is less than argument Timespan, 0 if equals and 1 if greater than - */ @Override public int compareTo(@Nullable Timespan time) { return Long.compare(millis, time == null ? millis : time.millis); @@ -371,10 +297,7 @@ public int compareTo(@Nullable Timespan time) { @Override public int hashCode() { - int prime = 31; - int result = 1; - result = prime * result + (int) (millis / Integer.MAX_VALUE); - return result; + return 31 + (int) (millis / Integer.MAX_VALUE); } @Override @@ -383,52 +306,89 @@ public boolean equals(@Nullable Object obj) { return true; if (obj == null) return false; - if (!(obj instanceof Timespan)) + if (!(obj instanceof Timespan other)) return false; - return millis == ((Timespan) obj).millis; + return millis == other.millis; } - public Duration getDuration() { - return Duration.ofMillis(millis); + @Override + public String toString() { + return toString(millis); } - public static Timespan fromDuration(Duration duration) { - return new Timespan(duration.toMillis()); + public String toString(int flags) { + return toString(millis, flags); } - @Override - public long get(TemporalUnit unit) { - if (unit instanceof TimePeriod period) - return this.getAs(period); - if (!(unit instanceof ChronoUnit chrono)) - throw new UnsupportedTemporalTypeException("Not a supported temporal unit: " + unit); - return switch (chrono) { - case MILLIS -> this.getAs(TimePeriod.MILLISECOND); - case SECONDS -> this.getAs(TimePeriod.SECOND); - case MINUTES -> this.getAs(TimePeriod.MINUTE); - case HOURS -> this.getAs(TimePeriod.HOUR); - case DAYS -> this.getAs(TimePeriod.DAY); - case WEEKS -> this.getAs(TimePeriod.WEEK); - case MONTHS -> this.getAs(TimePeriod.MONTH); - case YEARS -> this.getAs(TimePeriod.YEAR); - default -> throw new UnsupportedTemporalTypeException("Not a supported time unit: " + chrono); - }; - } + /** + * Represents the unit used for the current {@link Timespan}. + */ + public enum TimePeriod implements TemporalUnit { - @Override - public List getUnits() { - return List.of(TimePeriod.values()).reversed(); - } + MILLISECOND(1L), + TICK(50L), + SECOND(1000L), + MINUTE(SECOND.time * 60L), + HOUR(MINUTE.time * 60L), + DAY(HOUR.time * 24L), + WEEK(DAY.time * 7L), + MONTH(DAY.time * 30L), // Who cares about 28, 29 or 31 days? + YEAR(DAY.time * 365L); - @Override - public Temporal addTo(Temporal temporal) { - return temporal.plus(millis, MILLIS); - } + private final Noun name; + private final Noun shortName; + private final long time; + + TimePeriod(long time) { + this.name = new Noun("time." + this.name().toLowerCase(Locale.ENGLISH) + ".full"); + this.shortName = new Noun("time." + this.name().toLowerCase(Locale.ENGLISH) + ".short"); + this.time = time; + } + + public long getTime() { + return time; + } + + public String getFullForm() { + return name.toString(); + } + + public String getShortForm() { + return shortName.toString(); + } + + @Override + public Duration getDuration() { + return Duration.ofMillis(time); + } + + @Override + public boolean isDurationEstimated() { + return false; + } + + @Override + public boolean isDateBased() { + return false; + } + + @Override + public boolean isTimeBased() { + return true; + } + + @Override + public R addTo(R temporal, long amount) { + //noinspection unchecked + return (R) temporal.plus(amount, this); + } + + @Override + public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) { + return temporal1Inclusive.until(temporal2Exclusive, this); + } - @Override - public Temporal subtractFrom(Temporal temporal) { - return temporal.minus(millis, MILLIS); } } diff --git a/src/main/java/ch/njol/skript/util/slot/EquipmentSlot.java b/src/main/java/ch/njol/skript/util/slot/EquipmentSlot.java index d20628ce6d2..9d86efb1c2e 100644 --- a/src/main/java/ch/njol/skript/util/slot/EquipmentSlot.java +++ b/src/main/java/ch/njol/skript/util/slot/EquipmentSlot.java @@ -110,6 +110,18 @@ public ItemStack get(final EntityEquipment e) { public void set(final EntityEquipment e, final @Nullable ItemStack item) { e.setBoots(item); } + }, + + BODY() { + @Override + public @Nullable ItemStack get(EntityEquipment equipment) { + return equipment.getItem(org.bukkit.inventory.EquipmentSlot.BODY); + } + + @Override + public void set(EntityEquipment equipment, @Nullable ItemStack item) { + equipment.setItem(org.bukkit.inventory.EquipmentSlot.BODY, item); + } }; public final int slotNumber; 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 2b5f32ef769..0d37ad1e7dc 100644 --- a/src/main/java/ch/njol/skript/util/visual/VisualEffects.java +++ b/src/main/java/ch/njol/skript/util/visual/VisualEffects.java @@ -238,7 +238,7 @@ private static void registerDataSupplier(String id, BiFunction { int delay = 0; if (raw instanceof Timespan) - delay = (int) Math.min(Math.max(((Timespan) raw).getTicks(), 0), Integer.MAX_VALUE); + delay = (int) Math.min(Math.max(((Timespan) raw).getAs(Timespan.TimePeriod.TICK), 0), Integer.MAX_VALUE); return delay; }); @@ -253,7 +253,7 @@ 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); + arrivalTime = (int) Math.min(Math.max(((Timespan) data[1]).getAs(Timespan.TimePeriod.TICK), 0), Integer.MAX_VALUE); if (data[0] instanceof Entity) { Entity entity = (Entity) data[0]; if (arrivalTime == -1) diff --git a/src/main/java/ch/njol/skript/variables/FlatFileStorage.java b/src/main/java/ch/njol/skript/variables/FlatFileStorage.java index c9dadeb265b..e18ef041d71 100644 --- a/src/main/java/ch/njol/skript/variables/FlatFileStorage.java +++ b/src/main/java/ch/njol/skript/variables/FlatFileStorage.java @@ -126,10 +126,10 @@ public class FlatFileStorage extends VariablesStorage { /** * Create a new CSV storage of the given name. * - * @param name the name. + * @param type the databse type i.e. CSV. */ - FlatFileStorage(String name) { - super(name); + FlatFileStorage(String type) { + super(type); } /** @@ -439,7 +439,7 @@ public final void saveVariables(boolean finalSave) { pw.close(); FileUtils.move(tempFile, file, true); } catch (IOException e) { - Skript.error("Unable to make a final save of the database '" + databaseName + + Skript.error("Unable to make a final save of the database '" + getUserConfigurationName() + "' (no variables are lost): " + ExceptionUtils.toString(e)); // FIXME happens at random - check locks/threads } diff --git a/src/main/java/ch/njol/skript/variables/MySQLStorage.java b/src/main/java/ch/njol/skript/variables/MySQLStorage.java index d81594c3bc7..884529388ab 100644 --- a/src/main/java/ch/njol/skript/variables/MySQLStorage.java +++ b/src/main/java/ch/njol/skript/variables/MySQLStorage.java @@ -25,8 +25,8 @@ public class MySQLStorage extends SQLStorage { - MySQLStorage(String name) { - super(name, "CREATE TABLE IF NOT EXISTS %s (" + + MySQLStorage(String type) { + super(type, "CREATE TABLE IF NOT EXISTS %s (" + "rowid BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY," + "name VARCHAR(" + MAX_VARIABLE_NAME_LENGTH + ") NOT NULL UNIQUE," + "type VARCHAR(" + MAX_CLASS_CODENAME_LENGTH + ")," + diff --git a/src/main/java/ch/njol/skript/variables/SQLStorage.java b/src/main/java/ch/njol/skript/variables/SQLStorage.java index 6dce0d4910a..a5d2b4c4444 100644 --- a/src/main/java/ch/njol/skript/variables/SQLStorage.java +++ b/src/main/java/ch/njol/skript/variables/SQLStorage.java @@ -79,11 +79,11 @@ public abstract class SQLStorage extends VariablesStorage { /** * Creates a SQLStorage with a create table query. * - * @param name The name to be sent through this constructor when newInstance creates this class. + * @param type The database type i.e. CSV. * @param createTableQuery The create table query to send to the SQL engine. */ - public SQLStorage(String name, String createTableQuery) { - super(name); + public SQLStorage(String type, String createTableQuery) { + super(type); this.createTableQuery = createTableQuery; this.tableName = "variables21"; } @@ -134,7 +134,7 @@ protected boolean load_i(SectionNode n) { if (monitor_changes == null || monitor_interval == null) return false; monitor = monitor_changes; - this.monitor_interval = monitor_interval.getMilliSeconds(); + this.monitor_interval = monitor_interval.getAs(Timespan.TimePeriod.MILLISECOND); final Database db; try { @@ -160,14 +160,14 @@ protected boolean load_i(SectionNode n) { final boolean hadNewTable = db.isTable(getTableName()); if (getFormattedCreateQuery() == null){ - Skript.error("Could not create the variables table in the database. The query to create the variables table '" + tableName + "' in the database '" + databaseName + "' is null."); + Skript.error("Could not create the variables table in the database. The query to create the variables table '" + tableName + "' in the database '" + getUserConfigurationName() + "' is null."); return false; } try { db.query(getFormattedCreateQuery()); } catch (final SQLException e) { - Skript.error("Could not create the variables table '" + tableName + "' in the database '" + databaseName + "': " + e.getLocalizedMessage() + ". " + Skript.error("Could not create the variables table '" + tableName + "' in the database '" + getUserConfigurationName() + "': " + e.getLocalizedMessage() + ". " + "Please create the table yourself using the following query: " + String.format(createTableQuery, tableName).replace(",", ", ").replaceAll("\\s+", " ")); return false; } @@ -200,7 +200,7 @@ protected boolean load_i(SectionNode n) { // store old variables in new table and delete the old table if (hasOldTable) { if (!hadNewTable) { - Skript.info("[2.1] Updating the database '" + databaseName + "' to the new format..."); + Skript.info("[2.1] Updating the database '" + getUserConfigurationName() + "' to the new format..."); try { Variables.getReadLock().lock(); for (final Entry v : Variables.getVariablesHashMap().entrySet()) { @@ -221,7 +221,7 @@ protected boolean load_i(SectionNode n) { final ResultSet r = db.query("SELECT * FROM " + OLD_TABLE_NAME + " LIMIT 1"); try { if (r.next()) {// i.e. the old table is not empty - Skript.error("Could not successfully convert & transfer all variables to the new table in the database '" + databaseName + "'. " + Skript.error("Could not successfully convert & transfer all variables to the new table in the database '" + getUserConfigurationName() + "'. " + "Variables that could not be transferred are left in the old table and Skript will reattempt to transfer them whenever it starts until the old table is empty or is manually deleted. " + "Please note that variables recreated by scripts will count as converted and will be removed from the old table on the next restart."); } else { @@ -231,13 +231,13 @@ protected boolean load_i(SectionNode n) { connect(); db.query("DROP TABLE " + OLD_TABLE_NAME); } catch (final SQLException e) { - Skript.error("There was an error deleting the old variables table from the database '" + databaseName + "', please delete it yourself: " + e.getLocalizedMessage()); + Skript.error("There was an error deleting the old variables table from the database '" + getUserConfigurationName() + "', please delete it yourself: " + e.getLocalizedMessage()); error = true; } if (!error) - Skript.info("Successfully deleted the old variables table from the database '" + databaseName + "'."); + Skript.info("Successfully deleted the old variables table from the database '" + getUserConfigurationName() + "'."); if (!hadNewTable) - Skript.info("Database '" + databaseName + "' successfully updated."); + Skript.info("Database '" + getUserConfigurationName() + "' successfully updated."); } } finally { r.close(); @@ -265,7 +265,7 @@ public void run() { } catch (final InterruptedException e) {} } } - }, "Skript database '" + databaseName + "' connection keep-alive thread").start(); + }, "Skript database '" + getUserConfigurationName() + "' connection keep-alive thread").start(); return true; } @@ -273,7 +273,7 @@ public void run() { @Override protected void allLoaded() { - Skript.debug("Database " + databaseName + " loaded. Queue size = " + changesQueue.size()); + Skript.debug("Database " + getUserConfigurationName() + " loaded. Queue size = " + changesQueue.size()); // start committing thread. Its first execution will also commit the first batch of changed variables. Skript.newThread(new Runnable() { @@ -296,7 +296,7 @@ public void run() { } catch (final InterruptedException e) {} } } - }, "Skript database '" + databaseName + "' transaction committing thread").start(); + }, "Skript database '" + getUserConfigurationName() + "' transaction committing thread").start(); if (monitor) { Skript.newThread(new Runnable() { @@ -327,7 +327,7 @@ public void run() { } } } - }, "Skript database '" + databaseName + "' monitor thread").start(); + }, "Skript database '" + getUserConfigurationName() + "' monitor thread").start(); } } @@ -352,9 +352,9 @@ private final boolean connect(final boolean first) { final Database db = this.db.get(); if (db == null || !db.open()) { if (first) - Skript.error("Cannot connect to the database '" + databaseName + "'! Please make sure that all settings are correct");// + (type == Type.MYSQL ? " and that the database software is running" : "") + "."); + Skript.error("Cannot connect to the database '" + getUserConfigurationName() + "'! Please make sure that all settings are correct");// + (type == Type.MYSQL ? " and that the database software is running" : "") + "."); else - Skript.exception("Cannot reconnect to the database '" + databaseName + "'!"); + Skript.exception("Cannot reconnect to the database '" + getUserConfigurationName() + "'!"); return false; } try { @@ -400,7 +400,7 @@ private boolean prepareQueries() { } catch (final SQLException e) {} monitorCleanUpQuery = db.prepare("DELETE FROM " + getTableName() + " WHERE value IS NULL AND rowid < ?"); } catch (final SQLException e) { - Skript.exception(e, "Could not prepare queries for the database '" + databaseName + "': " + e.getLocalizedMessage()); + Skript.exception(e, "Could not prepare queries for the database '" + getUserConfigurationName() + "': " + e.getLocalizedMessage()); return false; } } @@ -577,7 +577,7 @@ public SQLException call() throws Exception { int i = 1; final String name = r.getString(i++); if (name == null) { - Skript.error("Variable with NULL name found in the database '" + databaseName + "', ignoring it"); + Skript.error("Variable with NULL name found in the database '" + getUserConfigurationName() + "', ignoring it"); continue; } final String type = r.getString(i++); @@ -590,7 +590,7 @@ public SQLException call() throws Exception { @SuppressWarnings("unused") Serializer s; if (c == null || (s = c.getSerializer()) == null) { - Skript.error("Cannot load the variable {" + name + "} from the database '" + databaseName + "', because the type '" + type + "' cannot be recognised or cannot be stored in variables"); + Skript.error("Cannot load the variable {" + name + "} from the database '" + getUserConfigurationName() + "', because the type '" + type + "' cannot be recognised or cannot be stored in variables"); continue; } // if (s.mustSyncDeserialization()) { @@ -598,7 +598,7 @@ public SQLException call() throws Exception { // } else { final Object d = Classes.deserialize(c, value); if (d == null) { - Skript.error("Cannot load the variable {" + name + "} from the database '" + databaseName + "', because it cannot be loaded as " + c.getName().withIndefiniteArticle()); + Skript.error("Cannot load the variable {" + name + "} from the database '" + getUserConfigurationName() + "', because it cannot be loaded as " + c.getName().withIndefiniteArticle()); continue; } Variables.variableLoaded(name, d, SQLStorage.this); @@ -623,7 +623,7 @@ public SQLException call() throws Exception { // for (final VariableInfo o : syncDeserializing) { // final Object d = Classes.deserialize(o.ci, o.value); // if (d == null) { -// Skript.error("Cannot load the variable {" + o.name + "} from the database " + databaseName + ", because it cannot be loaded as a " + o.ci.getName()); +// Skript.error("Cannot load the variable {" + o.name + "} from the database " + getUserConfigurationName() + ", because it cannot be loaded as a " + o.ci.getName()); // continue; // } // Variables.variableLoaded(o.name, d, DatabaseStorage.this); @@ -655,7 +655,7 @@ public SQLException call() throws Exception { private void oldLoadVariables(final ResultSet r, final boolean hadNewTable) throws SQLException { // synchronized (oldSyncDeserializing) { - final VariablesStorage temp = new VariablesStorage(databaseName + " old variables table") { + final VariablesStorage temp = new VariablesStorage(getUserConfigurationName() + " old variables table") { @Override protected boolean save(final String name, @Nullable final String type, @Nullable final byte[] value) { assert type == null : name + "; " + type; diff --git a/src/main/java/ch/njol/skript/variables/SQLiteStorage.java b/src/main/java/ch/njol/skript/variables/SQLiteStorage.java index fc4c54072d6..bb52883eba0 100644 --- a/src/main/java/ch/njol/skript/variables/SQLiteStorage.java +++ b/src/main/java/ch/njol/skript/variables/SQLiteStorage.java @@ -27,8 +27,8 @@ public class SQLiteStorage extends SQLStorage { - SQLiteStorage(String name) { - super(name, "CREATE TABLE IF NOT EXISTS %s (" + + SQLiteStorage(String type) { + super(type, "CREATE TABLE IF NOT EXISTS %s (" + "name VARCHAR(" + MAX_VARIABLE_NAME_LENGTH + ") NOT NULL PRIMARY KEY," + "type VARCHAR(" + MAX_CLASS_CODENAME_LENGTH + ")," + "value BLOB(" + MAX_VALUE_SIZE + ")," + diff --git a/src/main/java/ch/njol/skript/variables/Variables.java b/src/main/java/ch/njol/skript/variables/Variables.java index 8bdca7e77bd..1781c748bad 100644 --- a/src/main/java/ch/njol/skript/variables/Variables.java +++ b/src/main/java/ch/njol/skript/variables/Variables.java @@ -244,7 +244,7 @@ public static boolean load() { constructor.setAccessible(true); variablesStorage = (VariablesStorage) constructor.newInstance(type); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { - Skript.error("Failed to initalize database type '" + type + "'"); + Skript.error("Failed to initialize database `" + name + "`"); successful = false; continue; } @@ -413,8 +413,7 @@ public static void setLocalVariables(Event event, @Nullable Object map) { * @param event the event to copy local variables from. * @return the copy. */ - @Nullable - public static Object copyLocalVariables(Event event) { + public static @Nullable Object copyLocalVariables(Event event) { VariablesMap from = localVariables.get(event); if (from == null) return null; @@ -422,6 +421,20 @@ public static Object copyLocalVariables(Event event) { return from.copy(); } + /** + * Copies local variables from provider to user, runs action, then copies variables back to provider. + * Removes local variables from user after action is finished. + * @param provider The originator of the local variables. + * @param user The event to copy the variables to and back from. + * @param action The code to run while the variables are copied. + */ + public static void withLocalVariables(Event provider, Event user, @NotNull Runnable action) { + Variables.setLocalVariables(user, Variables.copyLocalVariables(provider)); + action.run(); + Variables.setLocalVariables(provider, Variables.copyLocalVariables(user)); + Variables.removeLocals(user); + } + /** * Returns the internal value of the requested variable. *

@@ -745,8 +758,8 @@ static boolean variableLoaded(String name, @Nullable Object value, VariablesStor // Warn if needed if (loadConflicts <= MAX_CONFLICT_WARNINGS) { Skript.warning("The variable {" + name + "} was loaded twice from different databases (" + - existingVariableStorage.databaseName + " and " + source.databaseName + - "), only the one from " + source.databaseName + " will be kept."); + existingVariableStorage.getUserConfigurationName() + " and " + source.getUserConfigurationName() + + "), only the one from " + source.getUserConfigurationName() + " will be kept."); } else if (loadConflicts == MAX_CONFLICT_WARNINGS + 1) { Skript.warning("[!] More than " + MAX_CONFLICT_WARNINGS + " variables were loaded more than once from different databases, " + diff --git a/src/main/java/ch/njol/skript/variables/VariablesStorage.java b/src/main/java/ch/njol/skript/variables/VariablesStorage.java index d130ec83b2a..0ddb0275648 100644 --- a/src/main/java/ch/njol/skript/variables/VariablesStorage.java +++ b/src/main/java/ch/njol/skript/variables/VariablesStorage.java @@ -20,6 +20,9 @@ import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; @@ -68,9 +71,14 @@ public abstract class VariablesStorage implements Closeable { protected volatile boolean closed = false; /** - * The name of the database, i.e. this storage. + * The name of the database */ - protected final String databaseName; + private String databaseName; + + /** + * The type of the database, i.e. CSV. + */ + private final String databaseType; /** * The file associated with this variable storage. @@ -98,11 +106,11 @@ public abstract class VariablesStorage implements Closeable { * This will also create the {@link #writeThread}, but it must be started * with {@link #load(SectionNode)}. * - * @param name the name. + * @param type the database type i.e. CSV. */ - protected VariablesStorage(String name) { - assert name != null; - databaseName = name; + protected VariablesStorage(String type) { + assert type != null; + databaseType = type; writeThread = Skript.newThread(() -> { while (!closed) { @@ -120,7 +128,29 @@ protected VariablesStorage(String name) { // Ignored as the `closed` field will indicate whether the thread actually needs to stop } } - }, "Skript variable save thread for database '" + name + "'"); + }, "Skript variable save thread for database '" + type + "'"); + } + + /** + * Get the config name of a database + *

+ * Note: Returns the user set name for the database, ex: + *

{@code
+	 * default: <- Config Name
+	 *    type: CSV
+	 * }
+ * @return name of database + */ + protected final String getUserConfigurationName() { + return databaseName; + } + + /** + * Get the config type of a database + * @return type of databse + */ + protected final String getDatabaseType() { + return databaseType; } /** @@ -170,6 +200,8 @@ protected T getValue(SectionNode sectionNode, String key, Class type) { } } + private static final Set registeredFiles = new HashSet<>(); + /** * Loads the configuration for this variable storage * from the given section node. @@ -178,6 +210,8 @@ protected T getValue(SectionNode sectionNode, String key, Class type) { * @return whether the loading succeeded. */ public final boolean load(SectionNode sectionNode) { + databaseName = sectionNode.getKey(); + String pattern = getValue(sectionNode, "pattern"); if (pattern == null) return false; @@ -222,6 +256,12 @@ public final boolean load(SectionNode sectionNode) { return false; } + if (registeredFiles.contains(file)) { + Skript.error("Database `" + databaseName + "` failed to load. The file `" + fileName + "` is already registered to another database."); + return false; + } + registeredFiles.add(file); + // Set the backup interval, if present & enabled if (!"0".equals(getValue(sectionNode, "backup interval"))) { Timespan backupInterval = getValue(sectionNode, "backup interval", Timespan.class); @@ -325,9 +365,9 @@ public final boolean load(SectionNode sectionNode) { */ public void startBackupTask(Timespan backupInterval, boolean removeBackups, int toKeep) { // File is null or backup interval is invalid - if (file == null || backupInterval.getTicks() == 0) + if (file == null || backupInterval.getAs(Timespan.TimePeriod.TICK) == 0) return; - backupTask = new Task(Skript.getInstance(), backupInterval.getTicks(), backupInterval.getTicks(), true) { + backupTask = new Task(Skript.getInstance(), backupInterval.getAs(Timespan.TimePeriod.TICK), backupInterval.getAs(Timespan.TimePeriod.TICK), true) { @Override public void run() { synchronized (connectionLock) { diff --git a/src/main/java/org/skriptlang/skript/bukkit/misc/effects/EffRotate.java b/src/main/java/org/skriptlang/skript/bukkit/misc/effects/EffRotate.java index 14ed2fc1737..7a560456640 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/misc/effects/EffRotate.java +++ b/src/main/java/org/skriptlang/skript/bukkit/misc/effects/EffRotate.java @@ -50,9 +50,9 @@ public class EffRotate extends Effect { static { Skript.registerEffect(EffRotate.class, - "rotate %vectors/quaternions/displays% around [the] [global] (:x|:y|:z)(-| )axis by %number% [degrees]", - "rotate %quaternions/displays% around [the|its|their] local (:x|:y|:z)(-| )ax(i|e)s by %number% [degrees]", - "rotate %vectors/quaternions/displays% around [the] %vector% by %number% [degrees]", + "rotate %vectors/quaternions/displays% around [the] [global] (:x|:y|:z)(-| )axis by %number%", + "rotate %quaternions/displays% around [the|its|their] local (:x|:y|:z)(-| )ax(i|e)s by %number%", + "rotate %vectors/quaternions/displays% around [the] %vector% by %number%", "rotate %quaternions/displays% by x %number%, y %number%(, [and]| and) z %number%" ); } diff --git a/src/main/java/org/skriptlang/skript/bukkit/misc/expressions/ExprRotate.java b/src/main/java/org/skriptlang/skript/bukkit/misc/expressions/ExprRotate.java index ee60bf91907..eb5cfaffab9 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/misc/expressions/ExprRotate.java +++ b/src/main/java/org/skriptlang/skript/bukkit/misc/expressions/ExprRotate.java @@ -46,9 +46,9 @@ public class ExprRotate extends SimpleExpression { static { Skript.registerExpression(ExprRotate.class, Object.class, ExpressionType.SIMPLE, - "%quaternions/vectors% rotated around [the] [global] (:x|:y|:z)(-| )axis by %number% [degrees]", - "%quaternions% rotated around [the|its|their] local (:x|:y|:z)(-| )ax(i|e)s by %number% [degrees]", - "%quaternions/vectors% rotated around [the] %vector% by %number% [degrees]", + "%quaternions/vectors% rotated around [the] [global] (:x|:y|:z)(-| )axis by %number%", + "%quaternions% rotated around [the|its|their] local (:x|:y|:z)(-| )ax(i|e)s by %number%", + "%quaternions/vectors% rotated around [the] %vector% by %number%", "%quaternions% rotated by x %number%, y %number%(, [and]| and) z %number%"); } @@ -157,6 +157,11 @@ public Class getReturnType() { return (matchedPattern == 1 || matchedPattern == 3) ? Quaternionf.class : toRotate.getReturnType(); } + @Override + public Class[] possibleReturnTypes() { + return new Class[]{Quaternionf.class, Vector.class}; + } + @Override public String toString(@Nullable Event event, boolean debug) { return switch (matchedPattern) { diff --git a/src/main/java/org/skriptlang/skript/bukkit/misc/expressions/ExprTextOf.java b/src/main/java/org/skriptlang/skript/bukkit/misc/expressions/ExprTextOf.java index 1a502d098d5..7afd9bf0e4e 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/misc/expressions/ExprTextOf.java +++ b/src/main/java/org/skriptlang/skript/bukkit/misc/expressions/ExprTextOf.java @@ -33,7 +33,8 @@ public class ExprTextOf extends SimplePropertyExpression { static { String types = ""; if (Skript.classExists("org.bukkit.entity.Display")) { - serializer = BungeeComponentSerializer.get(); + if (IS_RUNNING_PAPER) + serializer = BungeeComponentSerializer.get(); types += "displays"; } // This is because this expression is setup to support future types. diff --git a/src/main/java/org/skriptlang/skript/lang/condition/CompoundConditional.java b/src/main/java/org/skriptlang/skript/lang/condition/CompoundConditional.java new file mode 100644 index 00000000000..9e6e0657535 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/condition/CompoundConditional.java @@ -0,0 +1,155 @@ +package org.skriptlang.skript.lang.condition; + +import ch.njol.util.Kleenean; +import com.google.common.base.Preconditions; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * A {@link Conditional} that is built of other {@link Conditional}s. + * It is composed of an ordered {@link Set} of {@link Conditional}s that are acted upon by a single {@link Operator}. + * @param The context class to use for evaluation. + * @see DNFConditionalBuilder + */ +class CompoundConditional implements Conditional { + + private final LinkedHashSet> componentConditionals = new LinkedHashSet<>(); + private final Operator operator; + + /** + * Whether a cache will be used during evaluation. True if this compound contains other compounds, otherwise false. + */ + private boolean useCache; + + /** + * @param operator The {@link Operator} to use to combine the conditionals. If {@link Operator#NOT} is used, + * {@code conditionals} must exactly 1 conditional. + * @param conditionals A collection of conditionals to combine using the operator. Must be >= 1 in length, + * or exactly 1 if {@link Operator#NOT} is used. + */ + CompoundConditional(Operator operator, @NotNull Collection> conditionals) { + Preconditions.checkArgument(!conditionals.isEmpty(), + "CompoundConditionals must contain at least 1 component conditional."); + Preconditions.checkArgument(!(operator == Operator.NOT && conditionals.size() != 1), + "The NOT operator cannot be applied to multiple Conditionals."); + + this.componentConditionals.addAll(conditionals); + useCache = conditionals.stream().anyMatch(cond -> cond instanceof CompoundConditional); + this.operator = operator; + } + + /** + * @param operator The {@link Operator} to use to combine the conditionals. If {@link Operator#NOT} is used, + * {@code conditionals} must exactly 1 conditional. + * @param conditionals Conditionals to combine using the operator. Must be >= 1 in length, + * or exactly 1 if {@link Operator#NOT} is used. + */ + @SafeVarargs + CompoundConditional(Operator operator, Conditional... conditionals) { + this(operator, List.of(conditionals)); + } + + @Override + public Kleenean evaluate(T context) { + Map, Kleenean> cache = null; + // only use overhead of a cache if we think it will be useful (stacked conditionals) + if (useCache) + cache = new HashMap<>(); + return evaluate(context, cache); + } + + @Override + public Kleenean evaluate(T context, Map, Kleenean> cache) { + Kleenean result; + return switch (operator) { + case OR -> { + result = Kleenean.FALSE; + for (Conditional conditional : componentConditionals) { + result = conditional.evaluateOr(result, context, cache); + } + yield result; + } + case AND -> { + result = Kleenean.TRUE; + for (Conditional conditional : componentConditionals) { + result = conditional.evaluateAnd(result, context, cache); + } + yield result; + } + case NOT -> { + if (componentConditionals.size() > 1) + throw new IllegalStateException("Cannot apply NOT to multiple conditionals! Cannot evaluate."); + // best workaround since getFirst is java 21 + for (Conditional conditional : componentConditionals) { + yield conditional.evaluate(context, cache).not(); + } + throw new IllegalStateException("Cannot apply NOT to zero conditionals! Cannot evaluate."); + } + }; + } + + /** + * @return An immutable list of the component conditionals of this object. + */ + public @Unmodifiable List> getConditionals() { + return componentConditionals.stream().toList(); + } + + /** + * @return The operator used in this object. + */ + public Operator getOperator() { + return operator; + } + + /** + * @param conditionals Adds more conditionals to this object's component conditionals. + */ + @SafeVarargs + public final void addConditionals(Conditional... conditionals) { + addConditionals(List.of(conditionals)); + } + + /** + * @param conditionals Adds more conditionals to this object's component conditionals. + */ + public void addConditionals(Collection> conditionals) { + componentConditionals.addAll(conditionals); + useCache |= conditionals.stream().anyMatch(cond -> cond instanceof CompoundConditional); + } + + //TODO: replace event with context object in debuggable rework pr + @Override + public String toString(@Nullable Event event, boolean debug) { + String output = joinConditionals(event, debug); + + if (componentConditionals.size() > 1) + output = "(" + output + ")"; + + if (operator == Operator.NOT) + return "!" + output; + return output; + } + + private String joinConditionals(@Nullable Event event, boolean debug) { + return componentConditionals.stream() + .map(conditional -> conditional.toString(event, debug)) + .collect(Collectors.joining(" " + operator.getSymbol() + " ")); + } + + @Override + public String toString() { + return toString(null, false); + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/condition/Conditional.java b/src/main/java/org/skriptlang/skript/lang/condition/Conditional.java new file mode 100644 index 00000000000..04766831629 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/condition/Conditional.java @@ -0,0 +1,285 @@ +package org.skriptlang.skript.lang.condition; + +import ch.njol.skript.lang.Debuggable; +import ch.njol.util.Kleenean; +import com.google.common.base.Preconditions; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * An object which can evaluate to `true`, `false`, or `unknown`. + * `unknown` is currently unused, but intended for future handling of unexpected runtime situations, where some aspect of + * the condition in ill-defined by the user and would result in ambiguous or undefined behavior. + * @param The context class to use for evaluation. + */ +public interface Conditional extends Debuggable { + + /** + * Evaluates this object as `true`, `false`, or `unknown`. + * This value may change between subsequent callings. + * + * @param context The context with which to evaluate this object. + * @return The evaluation of this object. + */ + @Contract(pure = true) + Kleenean evaluate(T context); + + /** + * Evaluates this object as `true`, `false`, or `unknown`. + * This value may change between subsequent callings. + * May use a mutable cache of evaluated conditionals to prevent duplicate evaluations. + * + * @param context The context with which to evaluate this object. + * @param cache The cache of evaluated conditionals. + * @return The evaluation of this object. + */ + @Contract(pure = true) + default Kleenean evaluate(T context, @Nullable Map, Kleenean> cache) { + if (cache == null) + return evaluate(context); + //noinspection DataFlowIssue + return cache.computeIfAbsent(this, cond -> cond.evaluate(context)); + } + + /** + * Computes {@link Kleenean#and(Kleenean)} with the evaluations of this {@link Conditional} and the other. + * Evaluates the other first, and shortcuts if it is not {@link Kleenean#TRUE}. + * + * @param other The {@link Conditional} to AND with. Will always be evaluated. + * @param context The context with which to evaluate the conditionals. + * @return The result of {@link Kleenean#and(Kleenean)}, given the evaluations of the two conditionals. + */ + @Contract(pure = true) + default Kleenean evaluateAnd(Conditional other, T context) { + return evaluateAnd(other.evaluate(context, null), context, null); + } + + /** + * Computes {@link Kleenean#and(Kleenean)} with the evaluations of this {@link Conditional} and the other. + * Evaluates the other first, and shortcuts if it is not {@link Kleenean#TRUE}. + * Uses a mutable cache of evaluated conditionals to prevent duplicate evaluations. + * + * @param other The {@link Conditional} to AND with. Will always be evaluated. + * @param context The context with which to evaluate the conditionals. + * @param cache The cache of evaluated conditionals. + * @return The result of {@link Kleenean#and(Kleenean)}, given the evaluations of the two conditionals. + */ + @Contract(pure = true) + default Kleenean evaluateAnd(Conditional other, T context, @Nullable Map, Kleenean> cache) { + return evaluateAnd(other.evaluate(context, cache), context, cache); + } + + /** + * Computes {@link Kleenean#and(Kleenean)} with the evaluation of this {@link Conditional} and the given Kleenean. + * Evaluates this object iff the given {@link Kleenean} is {@link Kleenean#TRUE}. + * + * @param other The {@link Kleenean} to AND with. + * @param context The context with which to evaluate the conditional, if necessary. + * @return The result of {@link Kleenean#and(Kleenean)}, given the evaluation of the conditional. + */ + @Contract(pure = true) + default Kleenean evaluateAnd(Kleenean other, T context) { + return evaluateAnd(other, context, null); + } + + /** + * Computes {@link Kleenean#and(Kleenean)} with the evaluation of this {@link Conditional} and the given Kleenean. + * Evaluates this object iff the given {@link Kleenean} is not {@link Kleenean#FALSE}. + * Uses a mutable cache of evaluated conditionals to prevent duplicate evaluations. + * + * @param other The {@link Kleenean} to AND with. + * @param context The context with which to evaluate the conditional, if necessary. + * @param cache The cache of evaluated conditionals. + * @return The result of {@link Kleenean#and(Kleenean)}, given the evaluation of the conditional. + */ + @Contract(pure = true) + default Kleenean evaluateAnd(Kleenean other, T context, @Nullable Map, Kleenean> cache) { + if (other.isFalse()) + return other; + return other.and(evaluate(context, cache)); + } + + /** + * Computes {@link Kleenean#or(Kleenean)} with the evaluations of this {@link Conditional} and the other. + * Evaluates the other first, and shortcuts if it is not {@link Kleenean#TRUE}. + * + * @param other The {@link Conditional} to OR with. Will always be evaluated. + * @param context The context with which to evaluate the conditionals. + * @return The result of {@link Kleenean#or(Kleenean)}, given the evaluations of the two conditionals. + */ + @Contract(pure = true) + default Kleenean evaluateOr(Conditional other, T context) { + return evaluateOr(other.evaluate(context, null), context, null); + } + + /** + * Computes {@link Kleenean#or(Kleenean)} with the evaluations of this {@link Conditional} and the other. + * Evaluates the other first, and shortcuts if it is not {@link Kleenean#TRUE}. + * Uses a mutable cache of evaluated conditionals to prevent duplicate evaluations. + * + * @param other The {@link Conditional} to OR with. Will always be evaluated. + * @param context The context with which to evaluate the conditionals. + * @param cache The cache of evaluated conditionals. + * @return The result of {@link Kleenean#and(Kleenean)}, given the evaluations of the two conditionals. + */ + @Contract(pure = true) + default Kleenean evaluateOr(Conditional other, T context, @Nullable Map, Kleenean> cache) { + return evaluateOr(other.evaluate(context, cache), context, cache); + } + + /** + * Computes {@link Kleenean#or(Kleenean)} with the evaluation of this {@link Conditional} and the given Kleenean. + * Evaluates this object iff the given {@link Kleenean} is {@link Kleenean#FALSE} or {@link Kleenean#UNKNOWN}. + * + * @param other The {@link Kleenean} to OR with. + * @param context The context with which to evaluate the conditional, if necessary. + * @return The result of {@link Kleenean#or(Kleenean)}, given the evaluation of the conditional. + */ + @Contract(pure = true) + default Kleenean evaluateOr(Kleenean other, T context) { + return evaluateOr(other, context, null); + } + + /** + * Computes {@link Kleenean#or(Kleenean)} with the evaluation of this {@link Conditional} and the given Kleenean. + * Evaluates this object iff the given {@link Kleenean} is {@link Kleenean#FALSE} or {@link Kleenean#UNKNOWN}. + * Uses a mutable cache of evaluated conditionals to prevent duplicate evaluations. + * + * @param other The {@link Kleenean} to OR with. + * @param context The context with which to evaluate the conditional, if necessary. + * @param cache The cache of evaluated conditionals. + * @return The result of {@link Kleenean#or(Kleenean)}, given the evaluation of the conditional. + */ + @Contract(pure = true) + default Kleenean evaluateOr(Kleenean other, T context, @Nullable Map, Kleenean> cache) { + if (other.isTrue()) + return other; + return other.or(evaluate(context, cache)); + } + + /** + * Computes {@link Kleenean#not()} on the evaluation of this {@link Conditional}. + * @param context The context with which to evaluate the conditional. + * @return The result of {@link Kleenean#not()}, given the evaluation of the conditional. + */ + @Contract(pure = true) + default Kleenean evaluateNot(T context) { + return evaluateNot(context, null); + } + + /** + * Computes {@link Kleenean#not()} on the evaluation of this {@link Conditional}. + * @param context The context with which to evaluate the conditional. + * @return The result of {@link Kleenean#not()}, given the evaluation of the conditional. + */ + @Contract(pure = true) + default Kleenean evaluateNot(T context, @Nullable Map, Kleenean> cache) { + return this.evaluate(context, cache).not(); + } + + /** + * Creates a compound conditional from multiple conditionals using {@link Operator#AND} or {@link Operator#OR}. + * This does not maintain DNF. Use {@link DNFConditionalBuilder} for that purpose. + * @param operator The operator to use (AND or OR). + * @param conditionals The conditionals to combine. + * @return A new conditional that contains this conditional and the given conditionals + */ + @Contract("_, _ -> new") + static Conditional compound(Operator operator, Collection> conditionals) { + Preconditions.checkArgument(operator != Operator.NOT, "Cannot combine conditionals using NOT!"); + return new CompoundConditional<>(operator, conditionals); + } + + /** + * Creates a compound conditional from multiple conditionals using {@link Operator#AND} or {@link Operator#OR}. + * This does not maintain DNF. Use {@link DNFConditionalBuilder} for that purpose. + * @param operator The operator to use (AND or OR). + * @param conditionals The conditionals to combine. + * @return A new conditional that contains this conditional and the given conditionals + */ + @SuppressWarnings("unchecked") + @Contract("_, _ -> new") + static Conditional compound(Operator operator, Conditional... conditionals) { + return compound(operator, List.of(conditionals)); + } + + /** + * Provides a builder for conditions in disjunctive normal form. ex: {@code (A && B) || C || (!D &&E)} + * @param ignoredContextClass The class of the context to use for the built condition. + * @return a new builder object for making DNF conditions. + */ + @Contract(value = "_ -> new", pure = true) + static @NotNull DNFConditionalBuilder builderDNF(Class ignoredContextClass) { + return new DNFConditionalBuilder<>(); + } + + /** + * @param conditional A conditional to begin the builder with. + * @return a new builder object for making conditions, specifically compound ones. + */ + @Contract("_ -> new") + static @NotNull DNFConditionalBuilder builderDNF(Conditional conditional) { + return new DNFConditionalBuilder<>(conditional); + } + + /** + * Negates a given conditional. Follows the following transformation rules:
+ * {@code !!a -> a}
+ * {@code !(a || b) -> (!a && !b)}
+ * {@code !(a && b) -> (!a || !b)}
+ * @param conditional The conditional to negate. + * @return The negated conditional. + */ + static Conditional negate(Conditional conditional) { + if (!(conditional instanceof CompoundConditional compound)) + return new CompoundConditional<>(Operator.NOT, conditional); + + return switch (compound.getOperator()) { + // !!a -> a + case NOT -> compound.getConditionals().getFirst(); + // !(a && b) -> (!a || !b) + case AND -> { + List> newConditionals = new ArrayList<>(); + for (Conditional cond : compound.getConditionals()) { + newConditionals.add(negate(cond)); + } + yield new CompoundConditional<>(Operator.OR, newConditionals); + } + // !(a || b) -> (!a && !b) + case OR -> { + List> newConditionals = new ArrayList<>(); + for (Conditional cond : compound.getConditionals()) { + newConditionals.add(negate(cond)); + } + yield new CompoundConditional<>(Operator.AND, newConditionals); + } + }; + } + + /** + * Represents a boolean logic operator. + */ + enum Operator { + AND("&&"), + OR("||"), + NOT("!"); + + private final String symbol; + + Operator(String symbol) { + this.symbol = symbol; + } + + String getSymbol() { + return symbol; + } + + } + +} diff --git a/src/main/java/org/skriptlang/skript/lang/condition/DNFConditionalBuilder.java b/src/main/java/org/skriptlang/skript/lang/condition/DNFConditionalBuilder.java new file mode 100644 index 00000000000..1e979e4297a --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/condition/DNFConditionalBuilder.java @@ -0,0 +1,171 @@ +package org.skriptlang.skript.lang.condition; + +import com.google.common.base.Preconditions; +import org.jetbrains.annotations.Contract; +import org.skriptlang.skript.lang.condition.Conditional.Operator; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static org.skriptlang.skript.lang.condition.Conditional.negate; + +/** + * Builds a Disjunctive Normal Form {@link CompoundConditional}, meaning it is solely composed of groups of ANDs all ORed together, + * ex. {@code (a && !b && c) || b || (!c && d)}. + *
+ * A builder should no longer be used after calling {@link #build()}. + * @param The context class to use for evaluation. + */ +public class DNFConditionalBuilder { + + private CompoundConditional root; + + /** + * Creates an empty builder. + */ + DNFConditionalBuilder() { + this.root = null; + } + + /** + * Creates a builder with a single conditional. + * @param root If given a {@link CompoundConditional}, it is used as the root conditional. + * Otherwise, a OR compound conditional is created as the root with the given conditional within. + */ + DNFConditionalBuilder(Conditional root) { + if (root instanceof CompoundConditional compoundConditional) { + this.root = compoundConditional; + } else { + this.root = new CompoundConditional<>(Operator.OR, root); + } + } + + /** + * @return The root conditional, which will be DNF-compliant + * @throws IllegalStateException if the builder is empty. + */ + public Conditional build() { + return Preconditions.checkNotNull(root, "Cannot build an empty conditional!"); + } + + /** + * Adds conditionals to the root node via the AND operator: {@code (existing) && newA && newB && ...}. + * If the root is currently OR, the statement is transformed as follows to maintain DNF: + * {@code (a || b) && c -> (a && c) || (b && c)} + * @param andConditionals conditionals to AND to the existing conditional. + * @return the builder + */ + @SafeVarargs + @Contract("_ -> this") + public final DNFConditionalBuilder and(Conditional... andConditionals) { + if (root == null) { + root = new CompoundConditional<>(Operator.AND, andConditionals); + return this; + } + + // unroll conditionals if they're ANDs + List> newConditionals = unroll(List.of(andConditionals), Operator.AND); + + // if the root is still just AND, we can just append. + if (root.getOperator() == Operator.AND) { + root.addConditionals(newConditionals); + return this; + } + // Otherwise, we need to transform: + // (a || b) && c -> (a && c) || (b && c) + List> transformedConditionals = new ArrayList<>(); + newConditionals.add(0, null); // just for padding + for (Conditional conditional : root.getConditionals()) { + newConditionals.set(0, conditional); + transformedConditionals.add(new CompoundConditional<>(Operator.AND, newConditionals)); + } + + root = new CompoundConditional<>(Operator.OR, transformedConditionals); + return this; + } + + /** + * Adds conditionals to the root node via the OR operator: {@code (existing) || newA || newB || ...}. + * If the root is currently AND, a new OR root node is created containing the previous root and the new conditionals. + * @param orConditionals conditionals to OR to the existing conditional. + * @return the builder + */ + @SafeVarargs + @Contract("_ -> this") + public final DNFConditionalBuilder or(Conditional... orConditionals) { + if (root == null) { + root = new CompoundConditional<>(Operator.OR, orConditionals); + return this; + } + + // unroll conditionals if they're ORs + List> newConditionals = unroll(List.of(orConditionals), Operator.OR); + + // Since DNF is a series of ANDs, ORed together, we can simply add these to the root if it's already OR. + if (root.getOperator() == Operator.OR) { + root.addConditionals(newConditionals); + return this; + } + // otherwise we need to nest the AND/NOT condition within a new root with OR operator. + newConditionals.add(0, root); + root = new CompoundConditional<>(Operator.OR, newConditionals); + return this; + } + + /** + * Adds a negated conditional to the root node via the AND and NOT operators: {@code (existing) && !new}. + * @param conditional The conditional to negate and add. + * @return the builder + */ + @Contract("_ -> this") + public DNFConditionalBuilder andNot(Conditional conditional) { + return and(negate(conditional)); + } + + /** + * Adds a negated conditional to the root node via the OR and NOT operators: {@code (existing) || !new}. + * @param conditional The conditional to negate and add. + * @return the builder + */ + @Contract("_ -> this") + public DNFConditionalBuilder orNot(Conditional conditional) { + return or(negate(conditional)); + } + + /** + * Adds conditionals to the root node via the AND or OR operators. + * A helper for dynamically adding conditionals. + * @param or Whether to use OR (true) or AND (false) + * @param conditionals The conditional to add. + * @return the builder + */ + @SafeVarargs + @Contract("_,_ -> this") + public final DNFConditionalBuilder add(boolean or, Conditional... conditionals) { + if (or) + return or(conditionals); + return and(conditionals); + } + + /** + * Unrolls nested conditionals which are superfluous: + * {@code a || (b || c) -> a || b || c} + * @param conditionals A collection of conditionals to unroll. + * @param operator Which operator to unroll. + * @return A new list of conditionals without superfluous nesting. + */ + @Contract("_,_ -> new") + private static List> unroll(Collection> conditionals, Operator operator) { + List> newConditionals = new ArrayList<>(); + for (Conditional conditional : conditionals) { + if (conditional instanceof CompoundConditional compound && compound.getOperator() == operator) { + newConditionals.addAll(unroll(compound.getConditionals(), operator)); + } else { + newConditionals.add(conditional); + } + } + return newConditionals; + } + +} diff --git a/src/main/java/org/skriptlang/skript/util/event/EventRegistry.java b/src/main/java/org/skriptlang/skript/util/event/EventRegistry.java index ba7d01c8c28..6110f4d23b6 100644 --- a/src/main/java/org/skriptlang/skript/util/event/EventRegistry.java +++ b/src/main/java/org/skriptlang/skript/util/event/EventRegistry.java @@ -3,8 +3,8 @@ import com.google.common.collect.ImmutableSet; import org.jetbrains.annotations.Unmodifiable; -import java.util.HashSet; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** * An EventRegistry is a generic container for events. @@ -13,7 +13,7 @@ */ public class EventRegistry { - private final Set events = new HashSet<>(); + private final Set events = ConcurrentHashMap.newKeySet(); /** * Registers the provided event with this register. diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index fd9a449be22..7bdb52dbcd0 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -2388,6 +2388,19 @@ transform reasons: unknown: unknown infection: infection, villager infection +# -- Item Flags -- +item flags: + hide_additional_tooltip: hide additional tooltip, additional tooltip hidden, hidden additional tooltip + hide_armor_trim: hide armor trim, armor trim hidden, hidden armor trim + hide_attributes: hide attributes, attributes hidden, hidden attributes + hide_destroys: hide destroys, hide destroyable blocks, hide breakable blocks, destroys hidden, hidden destroys + hide_dye: hide dye, hide dye color, dye hidden, dye color hidden, hidden dye, hidden dye color + hide_enchants: hide enchants, hide enchantments, enchants hidden, enchantments hidden, hidden enchants + hide_placed_on: hide placed on, placed on hidden, hidden placed on + hide_stored_enchants: hide stored enchants, hide stored enchantments, stored enchants hidden, stored enchantments hidden, hidden stored enchants, hidden stored enchantments + hide_unbreakable: hide unbreakable, hide unbreakable status, unbreakable hidden, unbreakable status hidden, hidden unbreakable, hidden unbreakable status + hide_potion_effects: hide potion effects, hide potions, potion effects hidden, potions hidden, hidden potion effects, hidden potions + # -- Display Billboards -- billboards: center: center, middle, center pivot @@ -2476,6 +2489,7 @@ types: material: material¦s @a itemstack: item stack¦s @an itementity: dropped item¦s @a # same as entities.dropped item.name + itemflag: item flag¦s @an biome: biome¦s @a potioneffecttype: potion effect type¦s @a potioneffect: potion effect¦s @a diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 344b536ab87..e9ceb128e9d 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -29,7 +29,7 @@ website: https://skriptlang.org main: ch.njol.skript.Skript version: @version@ -api-version: 1.13 +api-version: 1.19 commands: skript: @@ -45,14 +45,15 @@ permissions: default: false children: skript.effectcommands: true + skript.reloadnotify: true skript.admin: true #skript.config: true skript.effectcommands: default: false description: Allows to use effects as commands, e.g. '!set health to 10' + skript.reloadnotify: + default: false + description: Notifies when others are reloading script files. skript.admin: default: op - description: Allows to use administrative commands and to recieve notifications of new versions. -# skript.config: -# description: allows to modify the configuration files via commands -# default: false + description: Allows use of administrative commands and receiving notifications of new versions. diff --git a/src/test/java/org/skriptlang/skript/test/tests/lang/ConditionalTest.java b/src/test/java/org/skriptlang/skript/test/tests/lang/ConditionalTest.java new file mode 100644 index 00000000000..a61fef5c433 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/lang/ConditionalTest.java @@ -0,0 +1,372 @@ +package org.skriptlang.skript.test.tests.lang; + +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.skriptlang.skript.lang.condition.Conditional; + +public class ConditionalTest { + + TestConditional condTrue; + TestConditional condFalse; + TestConditional condUnknown; + TestContext context; + + @Before + public void setup() { + condTrue = new TestConditional(Kleenean.TRUE); + condFalse = new TestConditional(Kleenean.FALSE); + condUnknown = new TestConditional(Kleenean.UNKNOWN); + context = new TestContext(); + } + + @Test + public void testBasicConditionals() { + Assert.assertEquals(Kleenean.TRUE, condTrue.evaluate(context)); + Assert.assertEquals(1, condTrue.timesEvaluated); + condTrue.reset(); + + Assert.assertEquals(Kleenean.FALSE, condFalse.evaluate(context)); + Assert.assertEquals(1, condFalse.timesEvaluated); + condFalse.reset(); + + Assert.assertEquals(Kleenean.UNKNOWN, condUnknown.evaluate(context)); + Assert.assertEquals(1, condUnknown.timesEvaluated); + condUnknown.reset(); + } + + private void assertBasic(TestConditional conditional, Kleenean expected, Kleenean actual, int expectedEvals) { + Assert.assertEquals("Incorrect evaluation!", expected, actual); + assertEvals(conditional, expectedEvals); + } + + private void assertEvals(TestConditional conditional, int expectedEvals) { + Assert.assertEquals("Wrong number of evaluations!", expectedEvals, conditional.timesEvaluated); + conditional.reset(); + } + + @Test + public void testBasicAndKnown() { + // true AND known x + assertBasic(condTrue, Kleenean.TRUE, condTrue.evaluateAnd(Kleenean.TRUE, context), 1); + assertBasic(condTrue, Kleenean.FALSE, condTrue.evaluateAnd(Kleenean.FALSE, context), 0); + assertBasic(condTrue, Kleenean.UNKNOWN, condTrue.evaluateAnd(Kleenean.UNKNOWN, context), 1); + + // false AND known x + assertBasic(condFalse, Kleenean.FALSE, condFalse.evaluateAnd(Kleenean.TRUE, context), 1); + assertBasic(condFalse, Kleenean.FALSE, condFalse.evaluateAnd(Kleenean.FALSE, context), 0); + assertBasic(condFalse, Kleenean.FALSE, condFalse.evaluateAnd(Kleenean.UNKNOWN, context), 1); + + // unknown AND known x + assertBasic(condUnknown, Kleenean.UNKNOWN, condUnknown.evaluateAnd(Kleenean.TRUE, context), 1); + assertBasic(condUnknown, Kleenean.FALSE, condUnknown.evaluateAnd(Kleenean.FALSE, context), 0); + assertBasic(condUnknown, Kleenean.UNKNOWN, condUnknown.evaluateAnd(Kleenean.UNKNOWN, context), 1); + } + + @Test + public void testBasicOrKnown() { + // true OR known x + assertBasic(condTrue, Kleenean.TRUE, condTrue.evaluateOr(Kleenean.TRUE, context), 0); + assertBasic(condTrue, Kleenean.TRUE, condTrue.evaluateOr(Kleenean.FALSE, context), 1); + assertBasic(condTrue, Kleenean.TRUE, condTrue.evaluateOr(Kleenean.UNKNOWN, context), 1); + + // false OR known x + assertBasic(condFalse, Kleenean.TRUE, condFalse.evaluateOr(Kleenean.TRUE, context), 0); + assertBasic(condFalse, Kleenean.FALSE, condFalse.evaluateOr(Kleenean.FALSE, context), 1); + assertBasic(condFalse, Kleenean.UNKNOWN, condFalse.evaluateOr(Kleenean.UNKNOWN, context), 1); + + // unknown OR known x + assertBasic(condUnknown, Kleenean.TRUE, condUnknown.evaluateOr(Kleenean.TRUE, context), 0); + assertBasic(condUnknown, Kleenean.UNKNOWN, condUnknown.evaluateOr(Kleenean.FALSE, context), 1); + assertBasic(condUnknown, Kleenean.UNKNOWN, condUnknown.evaluateOr(Kleenean.UNKNOWN, context), 1); + } + + @Test + public void testBasicNot() { + assertBasic(condTrue, Kleenean.FALSE, condTrue.evaluateNot(context), 1); + assertBasic(condFalse, Kleenean.TRUE, condFalse.evaluateNot(context), 1); + assertBasic(condUnknown, Kleenean.UNKNOWN, condUnknown.evaluateNot(context), 1); + } + + @Test + public void testBasicAndBasic() { + + TestConditional condTrueB = new TestConditional(Kleenean.TRUE); + TestConditional condFalseB = new TestConditional(Kleenean.FALSE); + TestConditional condUnknownB = new TestConditional(Kleenean.UNKNOWN); + + // true AND x + Assert.assertEquals(Kleenean.TRUE, condTrue.evaluateAnd(condTrueB, context)); + assertEvals(condTrue, 1); + assertEvals(condTrueB, 1); + + Assert.assertEquals(Kleenean.FALSE, condTrue.evaluateAnd(condFalse, context)); + assertEvals(condTrue, 0); + assertEvals(condFalse, 1); + + Assert.assertEquals(Kleenean.UNKNOWN, condTrue.evaluateAnd(condUnknown, context)); + assertEvals(condTrue, 1); + assertEvals(condUnknown, 1); + + // false AND x + Assert.assertEquals(Kleenean.FALSE, condFalse.evaluateAnd(condFalseB, context)); + assertEvals(condFalse, 0); + assertEvals(condFalseB, 1); + + Assert.assertEquals(Kleenean.FALSE, condFalse.evaluateAnd(condTrue, context)); + assertEvals(condFalse, 1); + assertEvals(condTrue, 1); + + Assert.assertEquals(Kleenean.FALSE, condFalse.evaluateAnd(condUnknown, context)); + assertEvals(condFalse, 1); + assertEvals(condUnknown, 1); + + // unknown AND x + Assert.assertEquals(Kleenean.UNKNOWN, condUnknown.evaluateAnd(condUnknownB, context)); + assertEvals(condUnknown, 1); + assertEvals(condUnknownB, 1); + + Assert.assertEquals(Kleenean.UNKNOWN, condUnknown.evaluateAnd(condTrue, context)); + assertEvals(condUnknown, 1); + assertEvals(condTrue, 1); + + Assert.assertEquals(Kleenean.FALSE, condUnknown.evaluateAnd(condFalse, context)); + assertEvals(condUnknown, 0); + assertEvals(condFalse, 1); + } + + @Test + public void testCombinedAnd() { + + TestConditional condTrueB = new TestConditional(Kleenean.TRUE); + TestConditional condFalseB = new TestConditional(Kleenean.FALSE); + TestConditional condUnknownB = new TestConditional(Kleenean.UNKNOWN); + + Conditional trueAndTrue = Conditional.builderDNF(TestContext.class) + .and(condTrue, condTrueB) + .build(); + + Assert.assertEquals(Kleenean.TRUE, trueAndTrue.evaluate(context)); + assertEvals(condTrue, 1); + assertEvals(condTrueB, 1); + + + Conditional trueAndFalse = Conditional.builderDNF(TestContext.class) + .and(condTrue, condFalse) + .build(); + + Assert.assertEquals(Kleenean.FALSE, trueAndFalse.evaluate(context)); + assertEvals(condTrue, 1); + assertEvals(condFalse, 1); + + + Conditional falseAndTrueAndFalse = Conditional.builderDNF(TestContext.class) + .and(condFalse, condTrue, condFalse) + .build(); + + Assert.assertEquals(Kleenean.FALSE, falseAndTrueAndFalse.evaluate(context)); + assertEvals(condTrue, 0); + assertEvals(condFalse, 1); + + + Conditional trueAndUnknown = Conditional.builderDNF(TestContext.class) + .and(condTrue, condUnknown) + .build(); + + Assert.assertEquals(Kleenean.UNKNOWN, trueAndUnknown.evaluate(context)); + assertEvals(condTrue, 1); + assertEvals(condUnknown, 1); + + + Conditional falseAndFalse = Conditional.builderDNF(TestContext.class) + .and(condFalse, condFalseB) + .build(); + + Assert.assertEquals(Kleenean.FALSE, falseAndFalse.evaluate(context)); + assertEvals(condFalseB, 0); + assertEvals(condFalse, 1); + + Conditional falseAndUnknown = Conditional.builderDNF(TestContext.class) + .and(condFalse, condUnknown) + .build(); + + Assert.assertEquals(Kleenean.FALSE, falseAndUnknown.evaluate(context)); + assertEvals(condUnknown, 0); + assertEvals(condFalse, 1); + + Conditional unknownAndUnknown = Conditional.builderDNF(TestContext.class) + .and(condUnknown, condUnknownB) + .build(); + + Assert.assertEquals(Kleenean.UNKNOWN, unknownAndUnknown.evaluate(context)); + assertEvals(condUnknown, 1); + assertEvals(condUnknownB, 1); + } + + @Test + public void testCombinedOr() { + + TestConditional condTrueB = new TestConditional(Kleenean.TRUE); + TestConditional condFalseB = new TestConditional(Kleenean.FALSE); + TestConditional condUnknownB = new TestConditional(Kleenean.UNKNOWN); + + Conditional trueOrTrue = Conditional.builderDNF(TestContext.class) + .or(condTrue, condTrueB) + .build(); + + Assert.assertEquals(Kleenean.TRUE, trueOrTrue.evaluate(context)); + assertEvals(condTrue, 1); + assertEvals(condTrueB, 0); + + + Conditional trueOrFalse = Conditional.builderDNF(TestContext.class) + .or(condTrue, condFalse) + .build(); + + Assert.assertEquals(Kleenean.TRUE, trueOrFalse.evaluate(context)); + assertEvals(condTrue, 1); + assertEvals(condFalse, 0); + + + Conditional falseOrTrueOrFalse = Conditional.builderDNF(TestContext.class) + .or(condFalse, condTrue, condFalseB) + .build(); + + Assert.assertEquals(Kleenean.TRUE, falseOrTrueOrFalse.evaluate(context)); + assertEvals(condTrue, 1); + assertEvals(condFalse, 1); + assertEvals(condFalseB, 0); + + + Conditional trueOrUnknown = Conditional.builderDNF(TestContext.class) + .or(condTrue, condUnknown) + .build(); + + Assert.assertEquals(Kleenean.TRUE, trueOrUnknown.evaluate(context)); + assertEvals(condTrue, 1); + assertEvals(condUnknown, 0); + + + Conditional falseOrFalse = Conditional.builderDNF(TestContext.class) + .or(condFalse, condFalseB) + .build(); + + Assert.assertEquals(Kleenean.FALSE, falseOrFalse.evaluate(context)); + assertEvals(condFalseB, 1); + assertEvals(condFalse, 1); + + Conditional falseOrUnknown = Conditional.builderDNF(TestContext.class) + .or(condFalse, condUnknown) + .build(); + + Assert.assertEquals(Kleenean.UNKNOWN, falseOrUnknown.evaluate(context)); + assertEvals(condUnknown, 1); + assertEvals(condFalse, 1); + + Conditional unknownAndUnknown = Conditional.builderDNF(TestContext.class) + .or(condUnknown, condUnknownB) + .build(); + + Assert.assertEquals(Kleenean.UNKNOWN, unknownAndUnknown.evaluate(context)); + assertEvals(condUnknown, 1); + assertEvals(condUnknownB, 1); + } + + @Test + public void testComplexCombined() { + Conditional trueAndFalseOrUnknownOrTrue = Conditional.builderDNF(TestContext.class) + .and(condTrue, condFalse) + .or(condUnknown, condTrue) + .build(); + + Assert.assertEquals(Kleenean.TRUE, trueAndFalseOrUnknownOrTrue.evaluate(context)); + assertEvals(condTrue, 1); + assertEvals(condFalse, 1); + assertEvals(condUnknown, 1); + + Conditional trueOrTrueAndFalseOrUnknown = Conditional.builderDNF(TestContext.class) + .or(condTrue) + .or(Conditional.compound(Conditional.Operator.AND, condTrue, condFalse)) + .or(condUnknown) + .build(); + + Assert.assertEquals(Kleenean.TRUE, trueOrTrueAndFalseOrUnknown.evaluate(context)); + assertEvals(condTrue, 1); + assertEvals(condFalse, 0); + assertEvals(condUnknown, 0); + + // should compose to (U && T && F) || (T && T && F) + Conditional unknownOrTrueAndTrueAndFalse = Conditional.builderDNF(TestContext.class) + .or(condUnknown, condTrue) + .and(condTrue, condFalse) + .build(); + + Assert.assertEquals(Kleenean.FALSE, unknownOrTrueAndTrueAndFalse.evaluate(context)); + assertEvals(condTrue, 1); + assertEvals(condFalse, 1); + assertEvals(condUnknown, 1); + } + + @Test + public void testCombinedAndOrNot() { + TestConditional condFalseB = new TestConditional(Kleenean.FALSE); + + Conditional falseOrNotTrueAndFalse = Conditional.builderDNF(condFalse) + .orNot(Conditional.compound(Conditional.Operator.AND, condTrue, condFalseB)) + .build(); + + Assert.assertEquals(Kleenean.TRUE, falseOrNotTrueAndFalse.evaluate(context)); + assertEvals(condTrue, 1); + assertEvals(condFalse, 1); + assertEvals(condFalseB, 1); + + + Conditional unknownAndNotTrueOrFalseOrNotFalse = Conditional.builderDNF(TestContext.class) + .and(condUnknown) + .andNot(Conditional.compound(Conditional.Operator.OR, condTrue, condFalse)) + .orNot(condFalseB) + .build(); + + Assert.assertEquals(Kleenean.TRUE, unknownAndNotTrueOrFalseOrNotFalse.evaluate(context)); + assertEvals(condUnknown, 1); + assertEvals(condTrue, 1); + assertEvals(condFalse, 0); + assertEvals(condFalseB, 1); + } + + @Ignore + private static class TestContext { + + } + + @Ignore + private static class TestConditional implements Conditional { + + public int timesEvaluated; + public final Kleenean value; + + TestConditional(Kleenean value) { + this.value = value; + } + + @Override + public Kleenean evaluate(TestContext context) { + ++timesEvaluated; + return value; + } + + public void reset() { + timesEvaluated = 0; + } + + @Override + public String toString(@Nullable Event context, boolean debug) { + return value.toString(); + } + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/events/EvtFireworkTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/events/EvtFireworkTest.java new file mode 100644 index 00000000000..75941ff7de6 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/events/EvtFireworkTest.java @@ -0,0 +1,59 @@ +package org.skriptlang.skript.test.tests.syntaxes.events; + +import ch.njol.skript.Skript; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.skript.util.SkriptColor; +import org.bukkit.Bukkit; +import org.bukkit.FireworkEffect; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Firework; +import org.bukkit.event.Event; +import org.bukkit.event.entity.FireworkExplodeEvent; +import org.bukkit.inventory.meta.FireworkMeta; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class EvtFireworkTest extends SkriptJUnitTest { + + private EntityType entityType; + private List fireworkList = new ArrayList<>(); + + @Before + public void getEntity() { + if (Skript.isRunningMinecraft(1, 20, 5)) { + entityType = EntityType.FIREWORK_ROCKET; + } else { + entityType = EntityType.valueOf("FIREWORK"); + } + } + + @Test + public void callEvents() { + List events = new ArrayList<>(); + for (SkriptColor color : SkriptColor.values()) { + Firework firework = (Firework) getTestWorld().spawnEntity(getTestLocation(), entityType); + FireworkEffect fireworkEffect = FireworkEffect.builder().withColor(color.asDyeColor().getFireworkColor()).build(); + FireworkMeta fireworkMeta = firework.getFireworkMeta(); + fireworkMeta.addEffects(fireworkEffect); + firework.setFireworkMeta(fireworkMeta); + fireworkList.add(firework); + events.add(new FireworkExplodeEvent(firework)); + } + + for (Event event : events) { + Bukkit.getPluginManager().callEvent(event); + } + } + + @After + public void cleanUp() { + for (Firework firework : fireworkList) { + firework.remove(); + } + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/ExprAffectedEntitiesTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/ExprAffectedEntitiesTest.java new file mode 100644 index 00000000000..ab7c1f073f9 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/expressions/ExprAffectedEntitiesTest.java @@ -0,0 +1,44 @@ +package org.skriptlang.skript.test.tests.syntaxes.expressions; + +import ch.njol.skript.test.runner.SkriptJUnitTest; +import org.bukkit.Bukkit; +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Pig; +import org.bukkit.event.entity.AreaEffectCloudApplyEvent; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class ExprAffectedEntitiesTest extends SkriptJUnitTest { + + private AreaEffectCloud cloud; + private Pig piggy; + private final List entityList = new ArrayList<>(); + + @Before + public void setUp() { + piggy = spawnTestPig(); + entityList.add(piggy); + cloud = (AreaEffectCloud) getTestWorld().spawnEntity(getTestLocation(), EntityType.AREA_EFFECT_CLOUD); + } + + @Test + public void callEvent() { + AreaEffectCloudApplyEvent event = new AreaEffectCloudApplyEvent(cloud, entityList); + Bukkit.getPluginManager().callEvent(event); + } + + @After + public void cleanUp() { + if (piggy != null) + piggy.remove(); + if (cloud != null) + cloud.remove(); + } + +} diff --git a/src/test/skript/junit/EvtFireworkTest.sk b/src/test/skript/junit/EvtFireworkTest.sk new file mode 100644 index 00000000000..9646a81a82d --- /dev/null +++ b/src/test/skript/junit/EvtFireworkTest.sk @@ -0,0 +1,16 @@ +options: + EvtFireworkTest: "org.skriptlang.skript.test.tests.syntaxes.events.EvtFireworkTest" + +test "EvtFireworkJUnit" when running JUnit: + set {_tests::1} to "any firework" + loop all colors: + set {_tests::%loop-iteration + 1%} to "%loop-color% firework" + + ensure junit test {@EvtFireworkTest} completes {_tests::*} + +on firework explode: + junit test is {@EvtFireworkTest} + complete objective "any firework" for {@EvtFireworkTest} + if event-colors is set: + set {_color} to first element of event-colors + complete objective "%{_color}% firework" for {@EvtFireworkTest} diff --git a/src/test/skript/junit/ExprAffectedEntities.sk b/src/test/skript/junit/ExprAffectedEntities.sk new file mode 100644 index 00000000000..49f29440f88 --- /dev/null +++ b/src/test/skript/junit/ExprAffectedEntities.sk @@ -0,0 +1,27 @@ +on load: + set {_tests::1} to "cleared piggies" + set {_tests::2} to "add 5 pigs" + set {_tests::3} to "remove 2 pigs" + set {_tests::4} to "set 4 pigs" + + ensure junit test "org.skriptlang.skript.test.tests.syntaxes.expressions.ExprAffectedEntitiesTest" completes {_tests::*} + +on area effect: + set {_test} to "org.skriptlang.skript.test.tests.syntaxes.expressions.ExprAffectedEntitiesTest" + junit test is {_test} + clear affected entities + spawn 5 pigs at spawn of world "world": + add entity to {_pigs::*} + add {_pigs::*} to affected entities + if size of affected entities = 5: + complete objective "add 5 pigs" for junit test {_test} + remove (elements from 1 to 2 of {_pigs::*}) from affected entities + if size of affected entities = 3: + complete objective "remove 2 pigs" for junit test {_test} + set affected entities to (elements from 1 to 4 of {_pigs::*}) + if size of affected entities = 4: + complete objective "set 4 pigs" for junit test {_test} + clear affected entities + if size of affected entities = 0: + complete objective "cleared piggies" for junit test {_test} + clear entities within {_pigs::*} diff --git a/src/test/skript/tests/misc/angle.sk b/src/test/skript/tests/misc/angle.sk new file mode 100644 index 00000000000..f6deadfb12a --- /dev/null +++ b/src/test/skript/tests/misc/angle.sk @@ -0,0 +1,33 @@ +test "angle": + assert 90 degrees is 90 with "90 degrees is not 90" + assert -10.5 degrees is -10.5 with "-10.5 degrees is not -10.5" + assert 354698306983560 degrees is 354698306983560 with "354698306983560 degrees is not 354698306983560" + + assert pi radians is 180.0 degrees with "pi radians is not 180 degrees" + assert 2 * pi in radians is 360.0 degrees with "2pi radians is not 360 degrees" + + assert 180.0 degrees is pi radians with "180 degrees is not pi radians" + assert 360.0 in degrees is 2 * pi radians with "360 degrees is not 2pi radians" + + assert infinity value degrees is infinity value with "infinity degrees is not infinity" + assert -infinity value degrees is -infinity value with "-infinity degrees is not -infinity" + assert infinity value radians is infinity value with "infinity radians is not infinity" + assert -infinity value radians is -infinity value with "-infinity radians is not -infinity" + + assert ("90 degrees" parsed as number) is 90 with "90 degrees parsed as number is not 90" + assert ("3.141592653589793 radians" parsed as number) is 180.0 with "pi radians parsed as number is not 180 degrees" + assert ("90 degrees" parsed as number) is 90 degrees with "90 degrees parsed as number is not 90 degrees" + assert ("3.141592653589793 radians" parsed as number) is 180.0 degrees with "pi radians parsed as number is not 180 degrees" + assert ("90 degrees" parsed as number) is 0.5 * pi radians with "90 degrees parsed as number is not 0.5pi radians" + assert ("3.141592653589793 radians" parsed as number) is pi radians with "pi radians parsed as number is not pi radians" + + assert 90, 180 and 270 in degrees is 90, 180 and 270 with "90, 180 and 270 degrees is not 90, 180 and 270" + assert 0.5 * pi, pi and 1.5 * pi in radians is 90.0, 180.0 and 270.0 with "0.5pi, pi and 1.5pi radians is not 90, 180 and 270" + + assert ("n" parsed as number) degrees is not set with "'n' parsed as number degrees is set" + assert ("n" parsed as number) radians is not set with "'n' parsed as number radians is set" + assert ("n degrees" parsed as number) is not set with "'n' parsed as number degrees is set" + assert ("n radians" parsed as number) is not set with "'n' parsed as number radians is set" + + assert isNaN(NaN value degrees) is true with "NaN degrees is not NaN" + assert isNaN(NaN value radians) is true with "NaN radians is not NaN" diff --git a/src/test/skript/tests/misc/displaydata.sk b/src/test/skript/tests/misc/displaydata.sk index fccb62de0ab..cf31458bf2b 100644 --- a/src/test/skript/tests/misc/displaydata.sk +++ b/src/test/skript/tests/misc/displaydata.sk @@ -1,4 +1,4 @@ -test "display entity data" when running minecraft "1.19": +test "display entity data": spawn a text display at spawn of world "world": set {_text} to entity diff --git a/src/test/skript/tests/misc/displays.sk b/src/test/skript/tests/misc/displays.sk index e3d7df93a9e..c4f7427fcaa 100644 --- a/src/test/skript/tests/misc/displays.sk +++ b/src/test/skript/tests/misc/displays.sk @@ -1,4 +1,4 @@ -test "spawn displays" when running minecraft "1.19.4": +test "spawn displays": spawn a text display at spawn of world "world": set {_display} to event-display assert billboard of display within {_display} is "fixed" parsed as billboard with "default billboard was not fixed" diff --git a/src/test/skript/tests/misc/registry.sk b/src/test/skript/tests/misc/registry.sk index 2294472fbae..49a85dbffb9 100644 --- a/src/test/skript/tests/misc/registry.sk +++ b/src/test/skript/tests/misc/registry.sk @@ -1,4 +1,4 @@ -test "registry" when minecraft version is "1.14": +test "registry": # Test namespaced keys assert curse of vanishing = minecraft:vanishing_curse with "'curse of vanishing' enchant should match namespace key" diff --git a/src/test/skript/tests/regressions/2535-foxdata fixes.sk b/src/test/skript/tests/regressions/2535-foxdata fixes.sk index f5eb10a0a39..926e164a002 100644 --- a/src/test/skript/tests/regressions/2535-foxdata fixes.sk +++ b/src/test/skript/tests/regressions/2535-foxdata fixes.sk @@ -1,4 +1,4 @@ -test "fox EntityData fixes" when running minecraft "1.15.2": +test "fox EntityData fixes": spawn red fox at location(0, 64, 0, world "world") spawn 3 snow foxes at location(0, 64, 0, world "world") delete all foxes diff --git a/src/test/skript/tests/regressions/2537-missing mooshroom variations in 1.14+.sk b/src/test/skript/tests/regressions/2537-missing mooshroom variations in 1.14+.sk index 0265e5c3281..bc7ae656d9c 100644 --- a/src/test/skript/tests/regressions/2537-missing mooshroom variations in 1.14+.sk +++ b/src/test/skript/tests/regressions/2537-missing mooshroom variations in 1.14+.sk @@ -1,4 +1,4 @@ -test "mooshroom EntityData fixes" when running minecraft "1.15.2": +test "mooshroom EntityData fixes": spawn brown mooshroom at location(0, 64, 0, world "world") spawn 3 red mooshrooms at location(0, 64, 0, world "world") delete all mooshrooms diff --git a/src/test/skript/tests/regressions/2625-villager comparisons.sk b/src/test/skript/tests/regressions/2625-villager comparisons.sk index 14c30e09572..7b0961b158e 100644 --- a/src/test/skript/tests/regressions/2625-villager comparisons.sk +++ b/src/test/skript/tests/regressions/2625-villager comparisons.sk @@ -1,4 +1,4 @@ -test "villager comparisons" when running minecraft "1.15.2": +test "villager comparisons": spawn cleric at location(0, 64, 0, world "world") assert last spawned entity is a cleric with "comparison failed" delete last spawned entity diff --git a/src/test/skript/tests/regressions/3253-entitydata fixes.sk b/src/test/skript/tests/regressions/3253-entitydata fixes.sk index 67279dd9f13..cd937d01209 100644 --- a/src/test/skript/tests/regressions/3253-entitydata fixes.sk +++ b/src/test/skript/tests/regressions/3253-entitydata fixes.sk @@ -23,7 +23,6 @@ test "entity data fixes": assert last spawned entity is an adult sheep with "last spawned entity should have been an adult sheep" assert last spawned entity is an adult entity with "last spawned entity should have been an adult entity" -test "entity data fixes - 1.16.5" when running minecraft "1.16.5": spawn a baby piglin at test-location assert last spawned entity is a baby piglin with "last spawned entity should have been a baby piglin" assert last spawned entity is a baby entity with "last spawned entity should have been a baby entity" diff --git a/src/test/skript/tests/regressions/3303-item durability.sk b/src/test/skript/tests/regressions/3303-item durability.sk index fbae4975d09..352c927b9c6 100644 --- a/src/test/skript/tests/regressions/3303-item durability.sk +++ b/src/test/skript/tests/regressions/3303-item durability.sk @@ -2,11 +2,3 @@ test "item durability": set {_i} to a diamond sword set durability of {_i} to 500 assert durability of {_i} != damage value of {_i} with "Durability of item should not equal damage value" - -test "block data value" when running below minecraft "1.13.2": - set test-block to farmland - set {_b} to block above test-block - set block at {_b} to fully grown wheat plant - assert data value of block at {_b} = 7 with "Data value of block should have been 7" - set data value of block at {_b} to 1 - assert data value of block at {_b} = 1 with "Data value of block should have been 1" diff --git a/src/test/skript/tests/regressions/4769-fire-visualeffect.sk b/src/test/skript/tests/regressions/4769-fire-visualeffect.sk index c490fa9e9cb..cfe83f94713 100644 --- a/src/test/skript/tests/regressions/4769-fire-visualeffect.sk +++ b/src/test/skript/tests/regressions/4769-fire-visualeffect.sk @@ -2,9 +2,12 @@ test "fire visual effect comparison": assert a block is a block with "failed to compare block classinfo against block classinfo" assert an itemtype is an itemtype with "failed to compare itemtype classinfo against itemtype classinfo" assert a diamond is an itemtype with "failed to compare itemtype 'diamond' against itemtype classinfo" + set {_below} to type of block below block at spawn of world "world" set {_block} to type of block at spawn of world "world" - set block at spawn of world "world" to fire + set block below block at spawn of world "world" to oak planks + set block at spawn of world "world" to fire[] assert block at spawn of world "world" is fire with "failed to compare fire (itemtype) with a block" + set block below block at spawn of world "world" to {_below} set block at spawn of world "world" to {_block} play 5 fire at spawn of world "world" assert "fire" parsed as visual effect is fire with "failed to compare visual effects" diff --git a/src/test/skript/tests/regressions/4773-composter-the-imposter.sk b/src/test/skript/tests/regressions/4773-composter-the-imposter.sk index 89d4fcf7bcf..086df94ca0a 100644 --- a/src/test/skript/tests/regressions/4773-composter-the-imposter.sk +++ b/src/test/skript/tests/regressions/4773-composter-the-imposter.sk @@ -1,4 +1,4 @@ -test "composter the imposter" when running minecraft "1.14.4": +test "composter the imposter": set {_block} to type of test-block set test-block to composter assert test-block is composter with "failed to compare composter (itemtype) with a block" diff --git a/src/test/skript/tests/regressions/5491-xp orb merge overwrite.sk b/src/test/skript/tests/regressions/5491-xp orb merge overwrite.sk index 4fe830d0c60..968cdb5f673 100644 --- a/src/test/skript/tests/regressions/5491-xp orb merge overwrite.sk +++ b/src/test/skript/tests/regressions/5491-xp orb merge overwrite.sk @@ -1,4 +1,4 @@ -test "spawn xp orb overwriting merged value" when running minecraft "1.14.4": +test "spawn xp orb overwriting merged value": # 1.13.2 seems not to merge xp orbs in the same way, so this test is skipped # 1.21 also does not merge orbs, so this test is disabled. diff --git a/src/test/skript/tests/regressions/7140-leather horse armor unequipable.sk b/src/test/skript/tests/regressions/7140-leather horse armor unequipable.sk index fbfae46cc9f..1a23dc53d80 100644 --- a/src/test/skript/tests/regressions/7140-leather horse armor unequipable.sk +++ b/src/test/skript/tests/regressions/7140-leather horse armor unequipable.sk @@ -1,4 +1,4 @@ -test "leather horse armor unequipable" when running minecraft "1.14": +test "leather horse armor unequipable": if minecraft version is "1.20.6": stop # horse armor equipping is broken on Paper 1.20.6. see https://github.com/PaperMC/Paper/pull/11139 diff --git a/src/test/skript/tests/regressions/pull-5566-block iterator being 100 always.sk b/src/test/skript/tests/regressions/pull-5566-block iterator being 100 always.sk index 7d1037642bb..b1a3cd2dd57 100644 --- a/src/test/skript/tests/regressions/pull-5566-block iterator being 100 always.sk +++ b/src/test/skript/tests/regressions/pull-5566-block iterator being 100 always.sk @@ -1,7 +1,8 @@ # Test not related to a made issue. Details at pull request. test "100 blocks fix": - set {_l} to test-location ~ vector(0, -1, 0) + set {_l} to test-location ~ vector(0, -1, 10) set block at {_l} to air + assert blocks 5 below {_l} contains air, grass block, dirt block, dirt block, bedrock block and void air with "Failed to get correct blocks (got '%blocks 5 below test-location%')" assert size of blocks 3 below location below {_l} is 4 with "Failed to match asserted size" diff --git a/src/test/skript/tests/syntaxes/conditions/CondIsCustomNameVisible.sk b/src/test/skript/tests/syntaxes/conditions/CondIsCustomNameVisible.sk new file mode 100644 index 00000000000..9d2990b8647 --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondIsCustomNameVisible.sk @@ -0,0 +1,20 @@ +test "custom name visibility": + spawn an adult zombie at (spawn of world "world"): + set {_z} to entity + + assert {_z}'s custom name isn't visible with "A zombie shouldn't be spawned with a visible display name" + + set {_z}'s display name to "aaa" + + assert {_z}'s custom name is visible with "Setting an entity's display name sets the visibility to true" + + hide {_z}'s custom name + + assert {_z}'s custom name isn't visible with "New effect should hide the custom name" + + show {_z}'s custom name + + assert {_z}'s custom name is visible with "New effect should show the custom name" + + delete entity within {_z} + diff --git a/src/test/skript/tests/syntaxes/conditions/CondIsPreferredTool.sk b/src/test/skript/tests/syntaxes/conditions/CondIsPreferredTool.sk index 585055cdf88..98175af6b0b 100644 --- a/src/test/skript/tests/syntaxes/conditions/CondIsPreferredTool.sk +++ b/src/test/skript/tests/syntaxes/conditions/CondIsPreferredTool.sk @@ -1,4 +1,4 @@ -test "CondIsPreferredTool - BlockData" when running minecraft "1.19.2": +test "CondIsPreferredTool - BlockData": assert wooden pickaxe is preferred tool for minecraft:stone[] with "failed wooden pickaxe for stone blockdata" assert wooden pickaxe is preferred tool for minecraft:dirt[] with "failed wooden pickaxe for dirt blockdata" assert wooden pickaxe is not preferred tool for minecraft:obsidian[] with "failed wooden pickaxe for obsidian blockdata" @@ -8,7 +8,7 @@ test "CondIsPreferredTool - BlockData" when running minecraft "1.19.2": assert wooden axe is not preferred tool for minecraft:stone[] with "failed wooden axe for stone blockdata" assert wooden axe is preferred tool for minecraft:dirt[] with "failed wooden axe for dirt blockdata" -test "CondIsPreferredTool - Block" when running minecraft "1.16.5": +test "CondIsPreferredTool - Block": set {_block} to block at location(0,0,0, "world") set {_temp} to {_block}'s type set block at {_block} to grass diff --git a/src/test/skript/tests/syntaxes/conditions/CondIsSaddled.sk b/src/test/skript/tests/syntaxes/conditions/CondIsSaddled.sk new file mode 100644 index 00000000000..92c3067d5ee --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondIsSaddled.sk @@ -0,0 +1,15 @@ +test "is saddled": + spawn a horse at (spawn of world "world"): + set {_horse} to event-entity + + spawn a pig at (spawn of world "world"): + set {_pig} to event-entity + + equip {_horse} and {_pig} with saddle + assert {_horse} and {_pig} are saddled with "equipping a horse or steerable with a saddle should do exactly that" + + unequip saddle from {_horse} and {_pig} + assert {_horse} and {_pig} aren't saddled with "unequipping a saddle from a horse or steerable should do exactly that" + + delete entity within {_horse} + delete entity within {_pig} diff --git a/src/test/skript/tests/syntaxes/conditions/CondIsTicking.sk b/src/test/skript/tests/syntaxes/conditions/CondIsTicking.sk new file mode 100644 index 00000000000..a4cbab33322 --- /dev/null +++ b/src/test/skript/tests/syntaxes/conditions/CondIsTicking.sk @@ -0,0 +1,14 @@ +test "is entity ticking": + + spawn an adult zombie at location(512, 1, 512): + add entity to {_z::*} + + spawn an adult zombie at (spawn of world "world"): + add entity to {_z::*} + + assert {_z::1} isn't ticking with "Chunk isn't loaded, therefore should not tick" + + assert {_z::2} is ticking with "Chunk is loaded, therefore should tick" + + delete entities within {_z::*} + diff --git a/src/test/skript/tests/syntaxes/conditions/CondIsWithin.sk b/src/test/skript/tests/syntaxes/conditions/CondIsWithin.sk index 028d20fb750..0bc71d177bf 100644 --- a/src/test/skript/tests/syntaxes/conditions/CondIsWithin.sk +++ b/src/test/skript/tests/syntaxes/conditions/CondIsWithin.sk @@ -1,4 +1,4 @@ -test "within condition" when running minecraft "1.17": +test "within condition": # two locations set {_loc1} to location(0, 0, 0, "world") set {_loc2} to location(20, 20, 20, "world") @@ -31,28 +31,3 @@ test "within condition" when running minecraft "1.17": assert {_loc1} is not within {_pig} with "failed within entity" delete random entity of {_pig} - -test "within condition" when running below minecraft "1.17": - # two locations - set {_loc1} to location(0, 0, 0, "world") - set {_loc2} to location(20, 20, 20, "world") - assert location(10, 10, 10, "world") is within {_loc1} and {_loc2} with "failed within two locs" - assert location(10, -10, 10, "world") is not within {_loc1} and {_loc2} with "failed within two locs" - assert location(0, 0, 0, "world") is not within {_none} and {_none} with "failed within two locs" - - # chunks - set {_chunk1} to chunk at {_loc1} - assert location(10, 10, 10, "world") is within {_chunk1} with "failed within chunk" - assert location(-10, 10, -10, "world") is not within {_chunk1} with "failed within chunk" - - # worlds - assert location(10, 10, 10, "world") is within world("world") with "failed within world" - - # entities - set {_loc} to test-location - spawn a pig at {_loc} - set {_pig} to last spawned entity - assert {_loc} is within {_pig} with "failed within entity" - assert {_loc1} is not within {_pig} with "failed within entity" - - delete random entity of {_pig} diff --git a/src/test/skript/tests/syntaxes/effects/EffDetonate.sk b/src/test/skript/tests/syntaxes/effects/EffDetonate.sk index aa88ac973b3..f7f7c803009 100644 --- a/src/test/skript/tests/syntaxes/effects/EffDetonate.sk +++ b/src/test/skript/tests/syntaxes/effects/EffDetonate.sk @@ -1,17 +1,17 @@ test "detonate entity effect": - spawn a creeper at event-location + spawn a creeper at event-location ~ vector(0, 100, 0) assert last spawned entity is valid with "creepers should not detonate when they are first spawned" detonate last spawned entity assert last spawned entity isn't valid with "creepers should instantly detonate when spawned" -test "detonate explosive minecart" when running minecraft "1.19": +test "detonate explosive minecart": spawn a minecart with tnt at event-location assert last spawned entity is valid with "minecarts with tnt should not detonate when they are first spawned" detonate last spawned entity assert last spawned entity isn't valid with "minecarts with tnt should instantly detonate when they are first spawned" test "detonate wind charge" when running minecraft "1.21": - spawn a wind charge at event-location + spawn a wind charge at event-location ~ vector(0, 100, 0) assert last spawned entity is valid with "wind charges should not detonate when they are first spawned" detonate last spawned entity assert last spawned entity isn't valid with "wind charges should instantly detonate when spawned" diff --git a/src/test/skript/tests/syntaxes/effects/EffEquip.sk b/src/test/skript/tests/syntaxes/effects/EffEquip.sk index a4a88a94f28..11b305aa010 100644 --- a/src/test/skript/tests/syntaxes/effects/EffEquip.sk +++ b/src/test/skript/tests/syntaxes/effects/EffEquip.sk @@ -1,12 +1,74 @@ test "equip effect": - spawn a zombie at spawn of "world": - set {_entity} to event-entity - equip {_entity} with a diamond chestplate - assert chestplate of {_entity} is a diamond chestplate with "entity was not wearing a diamond chestplate" - set chestplate of {_entity} to air + # === ARMOUR === - equip {_entity} with a diamond chestplate named "Test" - assert chestplate of {_entity} is a diamond chestplate named "Test" with "entity was not wearing a named diamond chestplate" + spawn a zombie at (spawn of world "world"): + set {_zombie} to entity - delete the entity within {_entity} + clear helmet of {_zombie} + clear chestplate of {_zombie} + clear leggings of {_zombie} + clear boots of {_zombie} + + assert helmet of {_zombie} is air with "the modified zombie shouldn't be wearing a helmet" + assert chestplate of {_zombie} is air with "the modified zombie shouldn't be wearing a chestplate" + assert leggings of {_zombie} is air with "the modified zombie shouldn't be wearing leggings" + assert boots of {_zombie} is air with "the modified zombie shouldn't be wearing boots" + + equip {_zombie} with iron helmet + equip {_zombie} with diamond chestplate + equip {_zombie} with diamond leggings + equip {_zombie} with any boots + assert helmet of {_zombie} is iron helmet with "equipping a zombie with a helmet should do exactly that" + assert chestplate of {_zombie} is diamond chestplate with "equipping a zombie with a chestplate should do exactly that" + assert leggings of {_zombie} is diamond leggings with "equipping a zombie with leggings should do exactly that" + assert boots of {_zombie} is any boots with "equipping a zombie with boots should do exactly that" + + unequip {_zombie}'s armour + assert helmet of {_zombie} is air with "unequipping an entity should remove its armour" + assert chestplate of {_zombie} is air with "unequipping an entity should remove its armour" + assert leggings of {_zombie} is air with "unequipping an entity should remove its armour" + assert boots of {_zombie} is air with "unequipping an entity should remove its armour" + + # === HORSE ARMOUR === + + spawn a horse at (spawn of world "world"): + set {_horse} to entity + + equip {_horse} with diamond horse armour + assert {_horse} is wearing diamond horse armour with "equipping a horse with horse armour should do exactly that" + + unequip diamond horse armour from {_horse} + assert {_horse} is not wearing diamond horse armour with "unequipping horse armour from a horse should do exactly that" + + # === SADDLES === + + spawn a pig at (spawn of world "world"): + set {_pig} to entity + + assert {_horse} and {_pig} don't have saddles with "normally spawned horses and pigs should not have saddles" + + equip {_horse} and {_pig} with saddle + assert {_horse} and {_pig} have saddles with "equipping a horse or pig with a saddle should do exactly that" + + unequip saddle from {_horse} and {_pig} + assert {_horse} and {_pig} don't have saddles with "unequipping a saddle from a horse or pig should do exactly that" + + # === CLEANUP === + + delete entity within {_zombie} + delete entity within {_horse} + delete entity within {_pig} + +test "equip - wolf armor" when running minecraft "1.20.5": + + spawn a wolf at (spawn of world "world"): + set {_wolf} to entity + + equip {_wolf} with wolf armor + assert {_wolf} is wearing wolf armor with "equipping a wolf with wolf armor should do exactly that" + + unequip wolf armor from {_wolf} + assert {_wolf} is not wearing wolf armor with "unequipping wolf armor from a wolf should do exactly that" + + delete entity within {_wolf} diff --git a/src/test/skript/tests/syntaxes/effects/EffExplodeCreeper.sk b/src/test/skript/tests/syntaxes/effects/EffExplodeCreeper.sk index 37b56627662..8cf30bbd0eb 100644 --- a/src/test/skript/tests/syntaxes/effects/EffExplodeCreeper.sk +++ b/src/test/skript/tests/syntaxes/effects/EffExplodeCreeper.sk @@ -1,4 +1,4 @@ -test "explode creeper effect/condition" when running minecraft "1.15.2": +test "explode creeper effect/condition": spawn a creeper at test-location assert last spawned creeper is not going to explode with "creepers should not explode when they are first spawned" start ignition of the last spawned creeper diff --git a/src/test/skript/tests/syntaxes/effects/EffGlowingText.sk b/src/test/skript/tests/syntaxes/effects/EffGlowingText.sk index d39b3e6988a..afc06f15022 100644 --- a/src/test/skript/tests/syntaxes/effects/EffGlowingText.sk +++ b/src/test/skript/tests/syntaxes/effects/EffGlowingText.sk @@ -1,4 +1,4 @@ -test "glowing sign blocks" when running minecraft "1.17.1": +test "glowing sign blocks": set {_loc} to spawn of "world" set {_original block} to type of block at {_loc} set block at {_loc} to sign @@ -9,7 +9,7 @@ test "glowing sign blocks" when running minecraft "1.17.1": assert block at {_loc} doesn't have glowing text with "Sign had glowing text erroneously (2)" set block at {_loc} to {_original block} -test "glowing sign items" when running minecraft "1.17.1": +test "glowing sign items": set {_sign} to floor sign assert {_sign} doesn't have glowing text with "Sign had glowing text erroneously (1)" make {_sign} have glowing text diff --git a/src/test/skript/tests/syntaxes/effects/EffHandedness.sk b/src/test/skript/tests/syntaxes/effects/EffHandedness.sk index 58aff9b4a44..7d55bc1abda 100644 --- a/src/test/skript/tests/syntaxes/effects/EffHandedness.sk +++ b/src/test/skript/tests/syntaxes/effects/EffHandedness.sk @@ -1,4 +1,4 @@ -test "left handedness" when running minecraft "1.17.1": +test "left handedness": spawn skeleton at test-location: make entity right handed assert entity is not left handed with "zombie is left handed after being made right handed" diff --git a/src/test/skript/tests/syntaxes/effects/EffInvisible.sk b/src/test/skript/tests/syntaxes/effects/EffInvisible.sk index 7b591947f41..b0023d7d638 100644 --- a/src/test/skript/tests/syntaxes/effects/EffInvisible.sk +++ b/src/test/skript/tests/syntaxes/effects/EffInvisible.sk @@ -1,4 +1,4 @@ -test "entity invisibility" when running minecraft "1.16.3": +test "entity invisibility": spawn pig at test-location: make entity invisible assert entity is invisible with "failed to make pig invisible" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprArmorSlot.sk b/src/test/skript/tests/syntaxes/expressions/ExprArmorSlot.sk index 8977f92c184..190effca9ab 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprArmorSlot.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprArmorSlot.sk @@ -12,3 +12,21 @@ test "armour slot": clear armour of event-entity assert armour of event-entity does not contain dirt block, diamond chestplate, iron leggings and gold boots with "Failed to clear EquipmentSlots" delete event-entity + +test "body armor wolf" when running minecraft "1.20.5": + spawn wolf at spawn of world "world": + set {_entity} to event-entity + + set body armor of {_entity} to wolf armor + assert body armor of {_entity} is wolf armor with "Body armor of entity is not wolf armor" + clear body armor of {_entity} + assert body armor of {_entity} is air with "Body armor of entity did not get cleared" + clear entity within {_entity} + +test "invalid body armor": + loop a zombie, a skeleton, a zombie horse and a skeleton horse: + spawn loop-value at spawn of world "world": + assert (body armor of event-entity) is not set with "Entity body armor is accessible" + set body armor of event-entity to diamond armor + assert (body armor of event-entity) is not set with "Entity body armor is accessible" + clear event-entity diff --git a/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk b/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk index fb2fb5abbd7..e7de38130a3 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk @@ -1,4 +1,4 @@ -test "block data" when running minecraft "1.14.4": +test "block data": set {_b} to block at test-location set block at {_b} to campfire[lit=false;waterlogged=true] assert block at {_b} is an unlit campfire with "block at spawn should be an unlit campfire" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprColorOf.sk b/src/test/skript/tests/syntaxes/expressions/ExprColorOf.sk index ed68ede4f20..06e9931c958 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprColorOf.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprColorOf.sk @@ -8,7 +8,7 @@ # test "color of fireworks": -test "color of displays" when running minecraft "1.19.4": +test "color of displays": spawn a text display at spawn of world "world": set {_e} to entity diff --git a/src/test/skript/tests/syntaxes/expressions/ExprCustomModelData.sk b/src/test/skript/tests/syntaxes/expressions/ExprCustomModelData.sk index 824f67d1a83..f1e24481f5f 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprCustomModelData.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprCustomModelData.sk @@ -1,4 +1,4 @@ -test "custom model data expressions/condition" when running minecraft "1.15.2": +test "custom model data expressions/condition": set {_item} to a diamond sword with custom model data 456 assert {_item} has custom model data with "{_item} does not have custom model data" if {_item} has custom model data: diff --git a/src/test/skript/tests/syntaxes/expressions/ExprDisplayBillboard.sk b/src/test/skript/tests/syntaxes/expressions/ExprDisplayBillboard.sk index 81550626e4b..9745ca70abd 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprDisplayBillboard.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprDisplayBillboard.sk @@ -1,4 +1,4 @@ -test "display billboard" when running minecraft "1.19.4": +test "display billboard": spawn a text display at spawn of world "world": set {_e} to entity diff --git a/src/test/skript/tests/syntaxes/expressions/ExprDisplayBrightness.sk b/src/test/skript/tests/syntaxes/expressions/ExprDisplayBrightness.sk index 97233552d85..ee291f39cab 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprDisplayBrightness.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprDisplayBrightness.sk @@ -1,4 +1,4 @@ -test "display brightness" when running minecraft "1.19.4": +test "display brightness": spawn block display at spawn of world "world": set {_e::1} to entity spawn item display at spawn of world "world": diff --git a/src/test/skript/tests/syntaxes/expressions/ExprDisplayGlowOverride.sk b/src/test/skript/tests/syntaxes/expressions/ExprDisplayGlowOverride.sk index 2442d6ef49a..90e97f0170a 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprDisplayGlowOverride.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprDisplayGlowOverride.sk @@ -1,4 +1,4 @@ -test "display glow color override" when running minecraft "1.19": +test "display glow color override": spawn block display at spawn of world "world": set {_e::1} to entity diff --git a/src/test/skript/tests/syntaxes/expressions/ExprDisplayHeightWidth.sk b/src/test/skript/tests/syntaxes/expressions/ExprDisplayHeightWidth.sk index 3b0e74e2d23..0ae29375a50 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprDisplayHeightWidth.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprDisplayHeightWidth.sk @@ -1,4 +1,4 @@ -test "display height/width" when running minecraft "1.19": +test "display height/width": spawn block display at spawn of world "world": set {_e::1} to entity diff --git a/src/test/skript/tests/syntaxes/expressions/ExprDisplayInterpolation.sk b/src/test/skript/tests/syntaxes/expressions/ExprDisplayInterpolation.sk index 4f5061baf68..7d2d204c1ed 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprDisplayInterpolation.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprDisplayInterpolation.sk @@ -1,4 +1,4 @@ -test "display interpolation" when running minecraft "1.19.4": +test "display interpolation": spawn block display at spawn of world "world": set {_e::1} to entity diff --git a/src/test/skript/tests/syntaxes/expressions/ExprDisplayShadow.sk b/src/test/skript/tests/syntaxes/expressions/ExprDisplayShadow.sk index ad2d9ef9088..746f2fa2cdc 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprDisplayShadow.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprDisplayShadow.sk @@ -1,4 +1,4 @@ -test "display radius/strength" when running minecraft "1.19": +test "display radius/strength": spawn block display at spawn of world "world": set {_e::1} to entity diff --git a/src/test/skript/tests/syntaxes/expressions/ExprDisplayTransformation.sk b/src/test/skript/tests/syntaxes/expressions/ExprDisplayTransformation.sk index 4eaac92da0b..fc3ac3c5315 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprDisplayTransformation.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprDisplayTransformation.sk @@ -1,4 +1,4 @@ -test "display transformation rotations" when running minecraft "1.19.4": +test "display transformation rotations": spawn block display at spawn of world "world": set {_e::1} to entity @@ -27,7 +27,7 @@ test "display transformation rotations" when running minecraft "1.19.4": delete entities within {_e::*} -test "display transformation translation / scales" when running minecraft "1.19.4": +test "display transformation translation / scales": spawn block display at spawn of world "world": set {_e::1} to entity spawn item display at spawn of world "world": diff --git a/src/test/skript/tests/syntaxes/expressions/ExprDisplayViewRange.sk b/src/test/skript/tests/syntaxes/expressions/ExprDisplayViewRange.sk index 5428dd8b8ba..ba4bfbbb09e 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprDisplayViewRange.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprDisplayViewRange.sk @@ -1,4 +1,4 @@ -test "display view range" when running minecraft "1.19": +test "display view range": spawn block display at spawn of world "world": set {_e::1} to entity diff --git a/src/test/skript/tests/syntaxes/expressions/ExprExplosiveYield.sk b/src/test/skript/tests/syntaxes/expressions/ExprExplosiveYield.sk index c4ccd1dd3f6..7dcb67b6a2d 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprExplosiveYield.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprExplosiveYield.sk @@ -5,7 +5,7 @@ test "explosive yield": assert explosive yield of last spawned tnt is 10 with "an explosive's explosive yield should be 10 if it is set to 10" delete last spawned primed tnt -test "creeper explosive yield" when running minecraft "1.12.2": +test "creeper explosive yield": spawn a creeper at test-location assert explosive yield of last spawned creeper is 3 with "explosive yield of a creeper is 3 by default" set explosive yield of last spawned creeper to 10 diff --git a/src/test/skript/tests/syntaxes/expressions/ExprFreezeTicks.sk b/src/test/skript/tests/syntaxes/expressions/ExprFreezeTicks.sk index 2f59f3ed27c..3a0d47e5137 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprFreezeTicks.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprFreezeTicks.sk @@ -1,4 +1,4 @@ -test "freeze time" when running minecraft "1.18": +test "freeze time": spawn cow at test-location: assert freeze time of entity is set with "freeze time get failed" set freeze time of entity to 3 seconds diff --git a/src/test/skript/tests/syntaxes/expressions/ExprItemDisplayTransform.sk b/src/test/skript/tests/syntaxes/expressions/ExprItemDisplayTransform.sk index 70e676e7b0c..d97f06d5e34 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprItemDisplayTransform.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprItemDisplayTransform.sk @@ -1,4 +1,4 @@ -test "item display transforms" when running minecraft "1.19.4": +test "item display transforms": spawn block display at spawn of world "world": set {_bd} to entity diff --git a/src/test/skript/tests/syntaxes/expressions/ExprItemFlags.sk b/src/test/skript/tests/syntaxes/expressions/ExprItemFlags.sk new file mode 100644 index 00000000000..eb5e7acc058 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprItemFlags.sk @@ -0,0 +1,18 @@ +test "item flags": + set {_i} to diamond sword of unbreaking 2 + + add hide enchants to item flags of {_i} + assert item flags of {_i} is hide enchants with "Hide Enchants did not get added to item." + + remove hide enchants from item flags of {_i} + assert item flags of {_i} isn't set with "Hide Enchants did not get removed from item." + + set item flags of {_i} to hide attributes + assert item flags of {_i} is hide attributes with "Item flags did not get set." + + reset item flags of {_i} + assert item flags of {_i} isn't set with "Item Flags did not reset." + + set {_i} to diamond sword of unbreaking 2 with item flags hide enchants + assert item flags of {_i} is hide enchants with "Item does not have hide enchants item flag" + diff --git a/src/test/skript/tests/syntaxes/expressions/ExprLowestHighestSolidBlock.sk b/src/test/skript/tests/syntaxes/expressions/ExprLowestHighestSolidBlock.sk index b0f90139f50..2c1ab3fa0d9 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprLowestHighestSolidBlock.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprLowestHighestSolidBlock.sk @@ -1,34 +1,4 @@ -test "lowest/highest solid block (old height)" when running below minecraft "1.18": - - # highest solid block - set {_oldBlock::1} to block data of block at location(0.5, 255.5, 0.5, "world") - set {_oldBlock::2} to block data of block at location(0.5, 254.5, 0.5, "world") - set {_oldBlock::3} to block data of block at location(0.5, 253.5, 0.5, "world") - set block at location(0.5, 255.5, 0.5, "world") to air - set block at location(0.5, 254.5, 0.5, "world") to air - set block at location(0.5, 253.5, 0.5, "world") to dirt - set {_highest} to highest solid block at location(0.5, 64, 0.5, "world") - assert type of {_highest} is dirt with "highest block is not dirt (got '%type of {_highest}%')" - assert location of {_highest} is location(0.5, 253.5, 0.5, "world") with "highest block is not at 0.5,253.5,0.5 (got '%location of {_highest}%')" - set block at location(0.5, 255.5, 0.5, "world") to {_oldBlock::1} - set block at location(0.5, 254.5, 0.5, "world") to {_oldBlock::2} - set block at location(0.5, 253.5, 0.5, "world") to {_oldBlock::3} - - # lowest solid block - set {_oldBlock::1} to block data of block at location(0.5, 0.5, 0.5, "world") - set {_oldBlock::2} to block data of block at location(0.5, 1.5, 0.5, "world") - set {_oldBlock::3} to block data of block at location(0.5, 2.5, 0.5, "world") - set block at location(0.5, 0.5, 0.5, "world") to air - set block at location(0.5, 1.5, 0.5, "world") to air - set block at location(0.5, 2.5, 0.5, "world") to dirt - set {_lowest} to lowest solid block at location(0.5, 64, 0.5, "world") - assert type of {_lowest} is dirt with "lowest block is not dirt (got '%type of {_lowest}%')" - assert location of {_lowest} is location(0.5, 2.5, 0.5, "world") with "lowest block is not at 0.5,2.5,0.5 (got '%location of {_lowest}%')" - set block at location(0.5, 0.5, 0.5, "world") to {_oldBlock::1} - set block at location(0.5, 1.5, 0.5, "world") to {_oldBlock::2} - set block at location(0.5, 2.5, 0.5, "world") to {_oldBlock::3} - -test "lowest/highest solid block (new height)" when running minecraft "1.18": +test "lowest/highest solid block": # highest solid block set {_oldBlock::1} to block data of block at location(0.5, 319.5, 0.5, "world") diff --git a/src/test/skript/tests/syntaxes/expressions/ExprMaxPlayers.sk b/src/test/skript/tests/syntaxes/expressions/ExprMaxPlayers.sk index d019eeccdd9..3f5c3c3d801 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprMaxPlayers.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprMaxPlayers.sk @@ -1,4 +1,4 @@ -test "max players" when running minecraft "1.16.5": +test "max players": set real max players count to 5 assert real max players count is 5 with "setting max players failed" add 3 to real max players diff --git a/src/test/skript/tests/syntaxes/expressions/ExprQuaternionAxisAngle.sk b/src/test/skript/tests/syntaxes/expressions/ExprQuaternionAxisAngle.sk index 1f4d69fe321..4696a8ddedb 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprQuaternionAxisAngle.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprQuaternionAxisAngle.sk @@ -1,4 +1,4 @@ -test "quaternion axis angle" when running minecraft "1.19": +test "quaternion axis angle": set {_v} to vector(1, 2, 3) set {_q} to axisAngle(45, {_v}) assert rotation angle of {_q} is 45 with "angle of quaternion failed" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprSkullOwner.sk b/src/test/skript/tests/syntaxes/expressions/ExprSkullOwner.sk new file mode 100644 index 00000000000..4369944c01e --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprSkullOwner.sk @@ -0,0 +1,16 @@ +test "skull owner - block": + set {_loc} to test-location + set {_old} to block at {_loc} + set block at {_loc} to player head + assert skull owner of (block at {_loc}) is not set with "Block Skull Owner should not be set" + set {_player} to "Sovde" parsed as offline player + set skull owner of (block at {_loc}) to {_player} + assert skull owner of (block at {_loc}) is {_player} with "Block Skull Owner did not change" + set block at {_loc} to {_old} + +test "skull owner - item": + set {_skull} to a player head + assert skull owner of {_skull} is not set with "Item Skull Owner should not be set" + set {_player} to "Sovde" parsed as offline player + set skull owner of {_skull} to {_player} + assert skull owner of {_skull} is {_player} with "Item Skull Owner did not change" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprTextDisplayAlignment.sk b/src/test/skript/tests/syntaxes/expressions/ExprTextDisplayAlignment.sk index e62d9f7172c..18c45683677 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprTextDisplayAlignment.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprTextDisplayAlignment.sk @@ -1,4 +1,4 @@ -test "text alignment" when running minecraft "1.19.4": +test "text alignment": spawn item display at spawn of world "world": set {_id} to entity diff --git a/src/test/skript/tests/syntaxes/expressions/ExprTextDisplayLineWidth.sk b/src/test/skript/tests/syntaxes/expressions/ExprTextDisplayLineWidth.sk index 9301419b8cd..9ccbcb296ec 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprTextDisplayLineWidth.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprTextDisplayLineWidth.sk @@ -1,4 +1,4 @@ -test "line width" when running minecraft "1.19.4": +test "line width": spawn item display at spawn of world "world": set {_id} to entity diff --git a/src/test/skript/tests/syntaxes/expressions/ExprTextDisplayOpacity.sk b/src/test/skript/tests/syntaxes/expressions/ExprTextDisplayOpacity.sk index 521c659f931..7d232791533 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprTextDisplayOpacity.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprTextDisplayOpacity.sk @@ -1,4 +1,4 @@ -test "text opacity" when running minecraft "1.19.4": +test "text opacity": spawn item display at spawn of world "world": set {_id} to entity diff --git a/src/test/skript/tests/syntaxes/expressions/ExprTimes.sk b/src/test/skript/tests/syntaxes/expressions/ExprTimes.sk index 5001a78ade0..edb09f9080a 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprTimes.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprTimes.sk @@ -6,3 +6,7 @@ test "times": add 1 to {_count} assert {_count} is 3 with "count was %{_count}% instead of 3" assert {_three} is 3 with "original number wasn't 3" + set {_count} to 0 + loop thrice: + add 1 to {_count} + assert {_count} is 3 with "thrice count was %{_count}% instead of 3" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprWhitelist.sk b/src/test/skript/tests/syntaxes/expressions/ExprWhitelist.sk index 32aa528320e..9eab2e0e042 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprWhitelist.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprWhitelist.sk @@ -11,7 +11,7 @@ test "whitelist": reset whitelist assert whitelist is not set with "Failed to empty whitelist" -test "enforce whitelist" when running minecraft "1.17": +test "enforce whitelist": enforce whitelist assert server whitelist is enforced with "Failed to enforce server whitelist" unenforce whitelist diff --git a/src/test/skript/tests/syntaxes/expressions/ExprXYZComponent.sk b/src/test/skript/tests/syntaxes/expressions/ExprXYZComponent.sk index bb0666c322d..519226814dd 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprXYZComponent.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprXYZComponent.sk @@ -1,14 +1,12 @@ test "vector xyz": - if running minecraft "1.19.4": - assert the w component of vector(0, 0, 0) is not set with "w vector component return value for vector unexpectedly" + assert the w component of vector(0, 0, 0) is not set with "w vector component return value for vector unexpectedly" assert the x component of vector(1, 0, 0) is 1 with "x vector component failed" assert the y component of vector(0, 1, 0) is 1 with "y vector component failed" assert the z component of vector(0, 0, 1) is 1 with "z vector component failed" set {_v} to vector(0,0,0) set x of {_v} to infinity value - if running minecraft "1.19.4": - assert the w component of {_v} is not set with "set x of vector created w component somehow" + assert the w component of {_v} is not set with "set x of vector created w component somehow" assert the x component of {_v} is infinity value with "set x of vector failed" assert the y component of {_v} is 0 with "set x of vector modified other components" assert the z component of {_v} is 0 with "set x of vector modified other components" @@ -30,7 +28,7 @@ test "vector xyz": assert the x component of {_v::*} is (-1.5, -1.5, and -1.5) with "x component of multiple vectors failed" assert the y component of {_v::*} is (2, 3, and 4) with "changing x component of multiple vectors changed y components too" -test "quaternion wxyz" when running minecraft "1.19.4": +test "quaternion wxyz": assert the w component of quaternion(1, 0, 0, 0) is 1 with "w vector component failed" assert the x component of quaternion(1, 0, 0, 0) is 0 with "x vector component failed" assert the y component of quaternion(1, 0, 0, 0) is 0 with "y vector component failed" diff --git a/src/test/skript/tests/syntaxes/functions/offlinePlayer.sk b/src/test/skript/tests/syntaxes/functions/offlinePlayer.sk index b96d67a7487..389ecea7308 100644 --- a/src/test/skript/tests/syntaxes/functions/offlinePlayer.sk +++ b/src/test/skript/tests/syntaxes/functions/offlinePlayer.sk @@ -2,6 +2,6 @@ test "offline player function": set {_lookup} to offlineplayer("Notch") assert {_lookup} is set with "Failed to look up offline player" -test "offline player function no lookup" when running minecraft "1.16": +test "offline player function no lookup": set {_non-lookup} to offlineplayer("Dinnerbone", false) assert {_non-lookup} is not set with "Looked up offline player when told not to" diff --git a/src/test/skript/tests/syntaxes/functions/quaternions.sk b/src/test/skript/tests/syntaxes/functions/quaternions.sk index 892d24006aa..24e2d724d8f 100644 --- a/src/test/skript/tests/syntaxes/functions/quaternions.sk +++ b/src/test/skript/tests/syntaxes/functions/quaternions.sk @@ -1,4 +1,4 @@ -test "quaternions" when running minecraft "1.19": +test "quaternions": set {_a} to quaternion(1, 2, 3, 4) assert w of {_a} is 1 with error "failed to get w component of quaternion" assert x of {_a} is 2 with error "failed to get x component of quaternion" @@ -12,7 +12,7 @@ test "quaternions" when running minecraft "1.19": assert y of {_a} is -infinity value with error "failed to get y component of quaternion" assert isNaN(z of {_a}) is true with error "failed to get z component of quaternion" -test "axis angle" when running minecraft "1.19": +test "axis angle": set {_a} to axisAngle(100, vector(2, 3, 4)) assert w of {_a} is 0.642787516117096 with error "failed to get w component of axis angle" assert x of {_a} is 1.5320889949798584 with error "failed to get x component of axis angle" diff --git a/src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk b/src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk index e829053ea07..48ceb74d3c5 100644 --- a/src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk +++ b/src/test/skript/tests/syntaxes/sections/EffSecSpawn.sk @@ -21,7 +21,7 @@ test "spawn section": assert {_before} is 10 with "value of {_before} should be 10 (got '%{_before}%')" assert {_new var} is 5 with "value of {_new var} should be 5 (got '%{_new var}%')" -test "spawn cats by type" when running minecraft "1.15.2": +test "spawn cats by type": delete all cats set {_l} to location of spawn of world "world" spawn 5 all black cats at {_l}