From 0cc9f980737488894657b00fe65a23226cf505ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Mar 2024 15:29:18 -0500 Subject: [PATCH 01/17] Bump org.gradle.toolchains.foojay-resolver-convention from 0.7.0 to 0.8.0 (#6332) --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index e5432e8214f..78b185aca4c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' } rootProject.name = 'Skript' From d864baa7c979e5499c745bd89684de0e99979117 Mon Sep 17 00:00:00 2001 From: Shane Bee Date: Sat, 9 Mar 2024 12:37:53 -0800 Subject: [PATCH 02/17] EntityData - check if entity can spawn (#6484) --- .../ch/njol/skript/entity/EntityData.java | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/entity/EntityData.java b/src/main/java/ch/njol/skript/entity/EntityData.java index 435415b8c91..2b4fbd1947e 100644 --- a/src/main/java/ch/njol/skript/entity/EntityData.java +++ b/src/main/java/ch/njol/skript/entity/EntityData.java @@ -37,6 +37,7 @@ import org.bukkit.RegionAccessor; import org.bukkit.World; import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.eclipse.jdt.annotation.Nullable; @@ -94,6 +95,7 @@ public abstract class EntityData implements SyntaxElement, Ygg } catch (NoSuchMethodException | SecurityException ignored) { /* We already checked if the method exists */ } } + private static final boolean HAS_ENABLED_BY_FEATURE = Skript.methodExists(EntityType.class, "isEnabledByFeature", World.class); public final static String LANGUAGE_NODE = "entities"; public final static Message m_age_pattern = new Message(LANGUAGE_NODE + ".age pattern"); @@ -463,6 +465,25 @@ private E apply(E entity) { return entity; } + /** + * Check if this entity type can spawn. + *

Some entity types may be restricted by experimental datapacks.

+ * + * @param world World to check if entity can spawn in + * @return True if entity can spawn else false + */ + public boolean canSpawn(@Nullable World world) { + if (world == null) + return false; + if (HAS_ENABLED_BY_FEATURE) { + // Check if the entity can actually be spawned + // Some entity types may be restricted by experimental datapacks + EntityType bukkitEntityType = EntityUtils.toBukkitEntityType(this); + return bukkitEntityType.isEnabledByFeature(world); + } + return true; + } + /** * Spawn this entity data at a location. * @@ -476,7 +497,7 @@ public final E spawn(Location location) { /** * Spawn this entity data at a location. - * The consumer allows for modiciation to the entity before it actually gets spawned. + * The consumer allows for modification to the entity before it actually gets spawned. *

* Bukkit's own {@link org.bukkit.util.Consumer} is deprecated. * Use {@link #spawn(Location, Consumer)} @@ -494,7 +515,7 @@ public E spawn(Location location, org.bukkit.util.@Nullable Consumer consumer /** * Spawn this entity data at a location. - * The consumer allows for modiciation to the entity before it actually gets spawned. + * The consumer allows for modification to the entity before it actually gets spawned. * * @param location The {@link Location} to spawn the entity at. * @param consumer A {@link Consumer} to apply the entity changes to. @@ -503,10 +524,13 @@ public E spawn(Location location, org.bukkit.util.@Nullable Consumer consumer @Nullable public E spawn(Location location, @Nullable Consumer consumer) { assert location != null; + World world = location.getWorld(); + if (!canSpawn(world)) + return null; if (consumer != null) { return EntityData.spawn(location, getType(), e -> consumer.accept(this.apply(e))); } else { - return apply(location.getWorld().spawn(location, getType())); + return apply(world.spawn(location, getType())); } } From c59ff5b9d72fc812bc76c2dcc401c1b1cd219647 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Tue, 19 Mar 2024 19:10:58 +0300 Subject: [PATCH 03/17] =?UTF-8?q?=F0=9F=9A=80=20Add=20timespan=20details?= =?UTF-8?q?=20expression=20&=20Improvements=20(#4661)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🚀 Add timespan details expression * Changes * Improvements * Changes * Add DD:HH:MM:SS.ms + improve toString * Update src/main/java/ch/njol/skript/util/Timespan.java Co-authored-by: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> * Update src/main/java/ch/njol/skript/util/Timespan.java Co-authored-by: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> * Update src/main/java/ch/njol/skript/util/Timespan.java Co-authored-by: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> * Update src/main/java/ch/njol/skript/util/Timespan.java Co-authored-by: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> * Testing * Improvements - Unfinished code * Update enums * Fix building * Test build * Test 2 * Fix code * Requested Changes & Improvements * Requested Changes * Pikachu's quality requested changes 🚀 * Apply suggestions * typo * Update src/main/java/ch/njol/skript/expressions/ExprTimespanDetails.java Co-authored-by: Patrick Miller * Add Timespan(TimePeriod, long) and deprecate fromTicks * Add getAs(TimePeriod) --------- Co-authored-by: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com> Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Co-authored-by: Patrick Miller Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../expressions/ExprTimespanDetails.java | 73 ++++++ .../java/ch/njol/skript/util/Timespan.java | 230 ++++++++++++------ 2 files changed, 229 insertions(+), 74 deletions(-) create mode 100644 src/main/java/ch/njol/skript/expressions/ExprTimespanDetails.java diff --git a/src/main/java/ch/njol/skript/expressions/ExprTimespanDetails.java b/src/main/java/ch/njol/skript/expressions/ExprTimespanDetails.java new file mode 100644 index 00000000000..a699d6e6e95 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprTimespanDetails.java @@ -0,0 +1,73 @@ +/** + * 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.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.util.Timespan; +import ch.njol.skript.util.Timespan.TimePeriod; +import ch.njol.util.Kleenean; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Locale; + +@Name("Timespan Details") +@Description("Retrieve specific information of a timespan such as hours/minutes/etc.") +@Examples({ + "set {_t} to difference between now and {Payouts::players::%uuid of player%::last-date}", + "send \"It has been %days of {_t}% day(s) since last payout.\"" +}) +@Since("INSERT VERSION") +public class ExprTimespanDetails extends SimplePropertyExpression { + + static { + register(ExprTimespanDetails.class, Long.class, "(:(tick|second|minute|hour|day|week|month|year))s", "timespans"); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private TimePeriod type; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + type = TimePeriod.valueOf(parseResult.tags.get(0).toUpperCase(Locale.ENGLISH)); + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Override + @Nullable + public Long convert(Timespan time) { + return time.getMilliSeconds() / type.getTime(); + } + + @Override + public Class getReturnType() { + return Long.class; + } + + @Override + protected String getPropertyName() { + return type.name().toLowerCase(Locale.ENGLISH); + } + +} diff --git a/src/main/java/ch/njol/skript/util/Timespan.java b/src/main/java/ch/njol/skript/util/Timespan.java index 3723fdb192f..a73f1ce6aaf 100644 --- a/src/main/java/ch/njol/skript/util/Timespan.java +++ b/src/main/java/ch/njol/skript/util/Timespan.java @@ -18,10 +18,16 @@ */ package ch.njol.skript.util; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.Locale; import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval; import ch.njol.skript.Skript; @@ -35,91 +41,133 @@ public class Timespan implements YggdrasilSerializable, Comparable { // REMIND unit - private static final Noun m_tick = new Noun("time.tick"); - private static final Noun m_second = new Noun("time.second"); - private static final Noun m_minute = new Noun("time.minute"); - private static final Noun m_hour = new Noun("time.hour"); - private static final Noun m_day = new Noun("time.day"); - private static final Noun m_week = new Noun("time.week"); - private static final Noun m_month = new Noun("time.month"); - private static final Noun m_year = new Noun("time.year"); - static final Noun[] names = {m_tick, m_second, m_minute, m_hour, m_day, m_week, m_month, m_year}; - static final long[] times = {50L, 1000L, 1000L * 60L, 1000L * 60L * 60L, 1000L * 60L * 60L * 24L, 1000L * 60L * 60L * 24L * 7L, 1000L * 60L * 60L * 24L * 30L, 1000L * 60L * 60L * 24L * 365L}; - static final HashMap parseValues = new HashMap<>(); + + public enum TimePeriod { + + 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 long time; + + TimePeriod(long time) { + this.name = new Noun("time." + this.name().toLowerCase(Locale.ENGLISH)); + this.time = time; + } + + public long getTime() { + return time; + } + + } + + private static final List> SIMPLE_VALUES = Arrays.asList( + new NonNullPair<>(TimePeriod.YEAR.name, TimePeriod.YEAR.time), + new NonNullPair<>(TimePeriod.MONTH.name, TimePeriod.MONTH.time), + new NonNullPair<>(TimePeriod.WEEK.name, TimePeriod.WEEK.time), + new NonNullPair<>(TimePeriod.DAY.name, TimePeriod.DAY.time), + new NonNullPair<>(TimePeriod.HOUR.name, TimePeriod.HOUR.time), + new NonNullPair<>(TimePeriod.MINUTE.name, TimePeriod.MINUTE.time), + new NonNullPair<>(TimePeriod.SECOND.name, TimePeriod.SECOND.time) + ); + + private static final Map PARSE_VALUES = new HashMap<>(); + static { Language.addListener(new LanguageChangeListener() { @Override public void onLanguageChange() { - for (int i = 0; i < names.length; i++) { - parseValues.put(names[i].getSingular().toLowerCase(Locale.ENGLISH), times[i]); - parseValues.put(names[i].getPlural().toLowerCase(Locale.ENGLISH), times[i]); + 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()); } } }); } + + 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 final long millis; @Nullable - public static Timespan parse(final String s) { - if (s.isEmpty()) + public static Timespan parse(String value) { + if (value.isEmpty()) return null; + long t = 0; boolean minecraftTime = false; boolean isMinecraftTimeSet = false; - if (s.matches("^\\d+:\\d\\d(:\\d\\d)?(\\.\\d{1,4})?$")) { // MM:SS[.ms] or HH:MM:SS[.ms] - final String[] ss = s.split("[:.]"); - final long[] times = {1000L * 60L * 60L, 1000L * 60L, 1000L, 1L}; // h, m, s, ms - - final int offset = ss.length == 3 && !s.contains(".") || ss.length == 4 ? 0 : 1; - for (int i = 0; i < ss.length; i++) { - t += times[offset + i] * Utils.parseLong("" + ss[i]); + + Matcher matcher = TIMESPAN_PATTERN.matcher(value); + if (matcher.matches()) { // MM:SS[.ms] or HH:MM:SS[.ms] or DD:HH:MM:SS[.ms] + String[] substring = TIMESPAN_SPLIT_PATTERN.split(value); + long[] times = {1L, TimePeriod.SECOND.time, TimePeriod.MINUTE.time, TimePeriod.HOUR.time, TimePeriod.DAY.time}; // ms, s, m, h, d + boolean hasMs = value.contains("."); + int length = substring.length; + int offset = 2; // MM:SS[.ms] + + if (length == 4 && !hasMs || length == 5) // DD:HH:MM:SS[.ms] + offset = 0; + else if (length == 3 && !hasMs || length == 4) // HH:MM:SS[.ms] + offset = 1; + + for (int i = 0; i < substring.length; i++) { + t += times[offset + i] * Utils.parseLong("" + substring[i]); } } else { // minutes/seconds/.. etc - final String[] subs = s.toLowerCase(Locale.ENGLISH).split("\\s+"); - for (int i = 0; i < subs.length; i++) { - String sub = subs[i]; + String[] substring = value.toLowerCase(Locale.ENGLISH).split("\\s+"); + for (int i = 0; i < substring.length; i++) { + String sub = substring[i]; if (sub.equals(GeneralWords.and.toString())) { - if (i == 0 || i == subs.length - 1) + if (i == 0 || i == substring.length - 1) return null; continue; } double amount = 1; if (Noun.isIndefiniteArticle(sub)) { - if (i == subs.length - 1) + if (i == substring.length - 1) return null; - amount = 1; - sub = subs[++i]; - } else if (sub.matches("^\\d+(\\.\\d+)?$")) { - if (i == subs.length - 1) + sub = substring[++i]; + } else if (TIMESPAN_NUMBER_PATTERN.matcher(sub).matches()) { + if (i == substring.length - 1) return null; try { amount = Double.parseDouble(sub); } catch (NumberFormatException e) { - throw new IllegalArgumentException("invalid timespan: " + s); + throw new IllegalArgumentException("Invalid timespan: " + value); } - sub = subs[++i]; + sub = substring[++i]; } if (CollectionUtils.contains(Language.getList("time.real"), sub)) { - if (i == subs.length - 1 || isMinecraftTimeSet && minecraftTime) + if (i == substring.length - 1 || isMinecraftTimeSet && minecraftTime) return null; - sub = subs[++i]; + sub = substring[++i]; } else if (CollectionUtils.contains(Language.getList("time.minecraft"), sub)) { - if (i == subs.length - 1 || isMinecraftTimeSet && !minecraftTime) + if (i == substring.length - 1 || isMinecraftTimeSet && !minecraftTime) return null; minecraftTime = true; - sub = subs[++i]; + sub = substring[++i]; } if (sub.endsWith(",")) sub = sub.substring(0, sub.length() - 1); - final Long d = parseValues.get(sub.toLowerCase(Locale.ENGLISH)); + Long d = PARSE_VALUES.get(sub.toLowerCase(Locale.ENGLISH)); if (d == null) return null; - if (minecraftTime && d != times[0]) // times[0] == tick + if (minecraftTime && d != TimePeriod.TICK.time) amount /= 72f; t += Math.round(amount * d); @@ -128,27 +176,47 @@ public static Timespan parse(final String s) { } } + return new Timespan(t); } - private final long millis; - public Timespan() { millis = 0; } - - public Timespan(final long millis) { + + /** + * Builds a Timespan from the given milliseconds. + * + * @param millis The milliseconds of Timespan + */ + public Timespan(long millis) { if (millis < 0) throw new IllegalArgumentException("millis must be >= 0"); this.millis = 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 + */ + public Timespan(TimePeriod timePeriod, long time) { + if (time < 0) + throw new IllegalArgumentException("time must be >= 0"); + this.millis = time * timePeriod.getTime(); + } + /** * Builds a Timespan from the given long parameter. + * + * @deprecated Use {@link Timespan#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 public static Timespan fromTicks(long ticks) { return new Timespan(ticks * 50L); } @@ -162,17 +230,35 @@ 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 public long getMilliSeconds() { return millis; } /** + * @deprecated Use {@link Timespan#getAs(TimePeriod)} + * * @return the amount of Minecraft ticks this timespan represents. */ + @Deprecated + @ScheduledForRemoval public long getTicks() { return Math.round((millis / 50.0)); } + /** + * @return the amount of TimePeriod this timespan represents. + */ + public long getAs(TimePeriod timePeriod) { + return Math.round(millis * timePeriod.getTime()); + } + /** * @deprecated Use getTicks() instead. Old API naming changes. */ @@ -187,66 +273,62 @@ public String toString() { return toString(millis); } - public String toString(final int flags) { + public String toString(int flags) { return toString(millis, flags); } - @SuppressWarnings("unchecked") - static final NonNullPair[] simpleValues = new NonNullPair[] { - new NonNullPair<>(m_day, 1000L * 60 * 60 * 24), - new NonNullPair<>(m_hour, 1000L * 60 * 60), - new NonNullPair<>(m_minute, 1000L * 60), - new NonNullPair<>(m_second, 1000L) - }; - - public static String toString(final long millis) { + public static String toString(long millis) { return toString(millis, 0); } - public static String toString(final long millis, final int flags) { - for (int i = 0; i < simpleValues.length - 1; i++) { - if (millis >= simpleValues[i].getSecond()) { - final double second = 1. * (millis % simpleValues[i].getSecond()) / simpleValues[i + 1].getSecond(); + @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 / simpleValues[i].getSecond()), simpleValues[i], flags) + " " + GeneralWords.and + " " + toString(second, simpleValues[i + 1], flags); + 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 / simpleValues[i].getSecond(), simpleValues[i], flags); + return toString(1. * millis / SIMPLE_VALUES.get(i).getSecond(), SIMPLE_VALUES.get(i), flags); } } } - return toString(1. * millis / simpleValues[simpleValues.length - 1].getSecond(), simpleValues[simpleValues.length - 1], 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(final double amount, final NonNullPair p, final int flags) { - return p.getFirst().withAmount(amount, flags); + private static String toString(double amount, NonNullPair pair, int flags) { + return pair.getFirst().withAmount(amount, flags); } - + + /** + * 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(final @Nullable Timespan o) { - final long d = o == null ? millis : millis - o.millis; - return d > 0 ? 1 : d < 0 ? -1 : 0; + public int compareTo(@Nullable Timespan time) { + return Long.compare(millis, time == null ? millis : time.millis); } @Override public int hashCode() { - final int prime = 31; + int prime = 31; int result = 1; - result = prime * result + (int) (millis/Integer.MAX_VALUE); + result = prime * result + (int) (millis / Integer.MAX_VALUE); return result; } @Override - public boolean equals(final @Nullable Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof Timespan)) return false; - final Timespan other = (Timespan) obj; - if (millis != other.millis) - return false; - return true; + + return millis == ((Timespan) obj).millis; } } From a44ed2fad68bd2618be040642e8a2273a3514cc5 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Tue, 19 Mar 2024 10:21:24 -0600 Subject: [PATCH 04/17] Fixes entitydata consumer on versions 1.20.1 and below. (#6475) * Fix entitydata consumer on 1.20.1 and older * Update aliases to master --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- src/main/java/ch/njol/skript/entity/EntityData.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/njol/skript/entity/EntityData.java b/src/main/java/ch/njol/skript/entity/EntityData.java index 2b4fbd1947e..9cf1f427fd7 100644 --- a/src/main/java/ch/njol/skript/entity/EntityData.java +++ b/src/main/java/ch/njol/skript/entity/EntityData.java @@ -670,15 +670,18 @@ public void deserialize(final Fields fields) throws StreamCorruptedException, No protected boolean deserialize(final String s) { return false; } - + @SuppressWarnings({"unchecked", "deprecation"}) protected static @Nullable E spawn(Location location, Class type, Consumer consumer) { + World world = location.getWorld(); + if (world == null) + return null; try { if (WORLD_1_17_CONSUMER) { - return (@Nullable E) WORLD_1_17_CONSUMER_METHOD.invoke(location.getWorld(), location, type, + return (@Nullable E) WORLD_1_17_CONSUMER_METHOD.invoke(world, location, type, (org.bukkit.util.Consumer) consumer::accept); } else if (WORLD_1_13_CONSUMER) { - return (@Nullable E) WORLD_1_13_CONSUMER_METHOD.invoke(location.getWorld(), location, type, + return (@Nullable E) WORLD_1_13_CONSUMER_METHOD.invoke(world, location, type, (org.bukkit.util.Consumer) consumer::accept); } } catch (InvocationTargetException | IllegalAccessException e) { @@ -686,7 +689,7 @@ protected boolean deserialize(final String s) { Skript.exception(e, "Can't spawn " + type.getName()); return null; } - return location.getWorld().spawn(location, type, consumer); + return world.spawn(location, type, consumer); } - + } From c6b344cc8949bbfa796d1b7f99f8c209cd1ca297 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:39:08 -0600 Subject: [PATCH 05/17] Change uuid default (#5676) Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- src/main/resources/config.sk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/config.sk b/src/main/resources/config.sk index d15284a06eb..7d7979e2d45 100644 --- a/src/main/resources/config.sk +++ b/src/main/resources/config.sk @@ -88,7 +88,7 @@ player variable fix: true # This fix should work around that and whenever a invalid(old) player object is attempted to be get through a variable # It will check if the player is online and then get the valid(new) player object and update the variable object to that one. -use player UUIDs in variable names: false +use player UUIDs in variable names: true # Whether to use a player's UUID instead of their name in variables, e.g. {home::%player%} will look like # {home::e5240337-a4a2-39dd-8ed9-e5ce729a8522} instead of {home::njol}. # Please note that if this setting is changed old variables WILL NOT be renamed automatically. From 7a79d8733857c6dc66a7c3c3065fdc1622b8cf70 Mon Sep 17 00:00:00 2001 From: Shane Bee Date: Tue, 26 Mar 2024 14:54:37 -0700 Subject: [PATCH 06/17] Examples Update (#6510) * ExprAnvilText - fix docs * DefaultFunctions - better location examples --------- Co-authored-by: Moderocky --- .../skript/classes/data/DefaultFunctions.java | 26 ++++++++++++++++--- .../skript/expressions/ExprAnvilText.java | 2 +- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java index 556f396b80b..e2c5392dd39 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultFunctions.java @@ -376,10 +376,28 @@ public Location[] execute(FunctionEvent e, Object[][] params) { } }.description("Creates a location from a world and 3 coordinates, with an optional yaw and pitch.", "If for whatever reason the world is not found, it will fallback to the server's main world.") - .examples("location(0, 128, 0)", - "location(player's x-coordinate, player's y-coordinate + 5, player's z-coordinate, player's world, 0, 90)", - "location(0, 64, 0, world \"world_nether\")", - "location(100, 110, -145, world(\"my_custom_world\"))") + .examples("# TELEPORTING", + "teleport player to location(1,1,1, world \"world\")", + "teleport player to location(1,1,1, world \"world\", 100, 0)", + "teleport player to location(1,1,1, world \"world\", yaw of player, pitch of player)", + "teleport player to location(1,1,1, world of player)", + "teleport player to location(1,1,1, world(\"world\"))", + "teleport player to location({_x}, {_y}, {_z}, {_w}, {_yaw}, {_pitch})", + "# SETTING BLOCKS", + "set block at location(1,1,1, world \"world\") to stone", + "set block at location(1,1,1, world \"world\", 100, 0) to stone", + "set block at location(1,1,1, world of player) to stone", + "set block at location(1,1,1, world(\"world\")) to stone", + "set block at location({_x}, {_y}, {_z}, {_w}) to stone", + "# USING VARIABLES", + "set {_l1} to location(1,1,1)", + "set {_l2} to location(10,10,10)", + "set blocks within {_l1} and {_l2} to stone", + "if player is within {_l1} and {_l2}:", + "# OTHER", + "kill all entities in radius 50 around location(1,65,1, world \"world\")", + "delete all entities in radius 25 around location(50,50,50, world \"world_nether\")", + "ignite all entities in radius 25 around location(1,1,1, world of player)") .since("2.2")); Functions.registerFunction(new SimpleJavaFunction("date", new Parameter[] { diff --git a/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java b/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java index acc05ae124c..15490749007 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java +++ b/src/main/java/ch/njol/skript/expressions/ExprAnvilText.java @@ -35,7 +35,7 @@ @Examples({ "on inventory click:", "\ttype of event-inventory is anvil inventory", - "\tif the anvil input text of the event-inventory is \"FREE OP\":", + "\tif the anvil text input of the event-inventory is \"FREE OP\":", "\t\tban player" }) @Since("2.7") From 86658cf4712edc2af698057a2e32ea2c4677da0a Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Wed, 27 Mar 2024 19:04:28 +0100 Subject: [PATCH 07/17] Handle invalid amounts in ExprRandomCharacter (#6502) * protect against bad amounts * Update ExprCharacters.sk .% * switch to Integer instead of Number and let chaos reign --------- Co-authored-by: Moderocky --- .../skript/expressions/ExprRandomCharacter.java | 13 ++++++++----- .../tests/syntaxes/expressions/ExprCharacters.sk | 8 ++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java b/src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java index 32afa678c42..13660c7ccd8 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRandomCharacter.java @@ -50,17 +50,17 @@ public class ExprRandomCharacter extends SimpleExpression { static { Skript.registerExpression(ExprRandomCharacter.class, String.class, ExpressionType.COMBINED, - "[a|%-number%] random [:alphanumeric] character[s] (from|between) %string% (to|and) %string%"); + "[a|%-integer%] random [:alphanumeric] character[s] (from|between) %string% (to|and) %string%"); } @Nullable - private Expression amount; + private Expression amount; private Expression from, to; private boolean isAlphanumeric; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - amount = (Expression) exprs[0]; + amount = (Expression) exprs[0]; from = (Expression) exprs[1]; to = (Expression) exprs[2]; isAlphanumeric = parseResult.hasTag("alphanumeric"); @@ -70,7 +70,10 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override @Nullable protected String[] get(Event event) { - int amount = this.amount == null ? 1 : this.amount.getOptionalSingle(event).orElse(1).intValue(); + Integer amount = this.amount == null ? Integer.valueOf(1) : this.amount.getSingle(event); + if (amount == null || amount <= 0) + return new String[0]; + String from = this.from.getSingle(event); String to = this.to.getSingle(event); if (from == null || to == null) @@ -117,7 +120,7 @@ protected String[] get(Event event) { @Override public boolean isSingle() { if (amount instanceof Literal) - return ((Literal) amount).getSingle().intValue() == 1; + return ((Literal) amount).getSingle() == 1; return amount == null; } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprCharacters.sk b/src/test/skript/tests/syntaxes/expressions/ExprCharacters.sk index 5c57a474378..0805fb13d85 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprCharacters.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprCharacters.sk @@ -1,5 +1,13 @@ test "characters between": set {_a} to 1 random character between "a" and "z" + assert {_a} is set with "failed to generate random character" + + set {_a::*} to -10 random character between "a" and "z" + assert {_a::*} is not set with "-10 random characters returned non-null value: %{_a::*}%" + + set {_a::*} to {_null} random character between "a" and "z" + assert {_a::*} is not set with "null random characters returned non-null value: %{_a::*}%" + assert join characters between "a" and "d" is "abcd" with "Invalid characters between a and d" assert join characters between "d" and "a" is "dcba" with "Invalid characters between d and a" assert join characters between "Z" and "a" is "Z[\]^_`a" with "Invalid characters between Z and a" From 9b96c8e8dcf34f44c7e3e1e35db1d1edadf02375 Mon Sep 17 00:00:00 2001 From: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Date: Wed, 27 Mar 2024 21:14:06 +0300 Subject: [PATCH 08/17] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Fix=20ExprRandom?= =?UTF-8?q?=20missing=20canInitSafely=20(#6512)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Moderocky --- src/main/java/ch/njol/skript/expressions/ExprRandom.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprRandom.java b/src/main/java/ch/njol/skript/expressions/ExprRandom.java index c3a87f7e957..5ab7e45996a 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprRandom.java +++ b/src/main/java/ch/njol/skript/expressions/ExprRandom.java @@ -78,9 +78,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye } else { expr = exprs[1].getConvertedExpression((((Literal>) exprs[0]).getSingle()).getC()); } - if (expr == null) - return false; - return true; + return expr != null && LiteralUtils.canInitSafely(expr); } @Override From f691fff73885b161fd5732eb2e67bd086a762216 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Fri, 29 Mar 2024 01:36:02 -0600 Subject: [PATCH 09/17] Adds pathfinding condition (#6506) * Add pathfinding condition * setNegated fixes --- .../skript/conditions/CondIsPathfinding.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/main/java/ch/njol/skript/conditions/CondIsPathfinding.java diff --git a/src/main/java/ch/njol/skript/conditions/CondIsPathfinding.java b/src/main/java/ch/njol/skript/conditions/CondIsPathfinding.java new file mode 100644 index 00000000000..3fb40549421 --- /dev/null +++ b/src/main/java/ch/njol/skript/conditions/CondIsPathfinding.java @@ -0,0 +1,105 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.conditions; + +import ch.njol.skript.Skript; +import ch.njol.skript.conditions.base.PropertyCondition; +import ch.njol.skript.conditions.base.PropertyCondition.PropertyType; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.RequiredPlugins; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; + +import com.destroystokyo.paper.entity.Pathfinder; +import com.destroystokyo.paper.entity.Pathfinder.PathResult; + +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Is Pathfinding") +@Description({ + "Checks whether living entities are pathfinding.", + "Can only be a living entity that is a Mob." +}) +@Examples({ + "make {_entity} pathfind to {_location} at speed 2", + "while {_entity} is pathfinding", + "\twait a second", + "launch flickering trailing burst firework colored red at location of {_entity}", + "subtract 10 from {defence::tower::health}", + "clear entity within {_entity}" +}) +@RequiredPlugins("Paper") +@Since("INSERT VERSION") +public class CondIsPathfinding extends Condition { + + static { + if (Skript.classExists("org.bukkit.entity.Mob") && Skript.methodExists(Mob.class, "getPathfinder")) + PropertyCondition.register(CondIsPathfinding.class, "pathfinding [to[wards] %-livingentity/location%]", "livingentities"); + } + + private Expression entities; + private Expression target; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + entities = (Expression) expressions[0]; + target = expressions[1]; + setNegated(matchedPattern == 1); + return true; + } + + @Override + public boolean check(Event event) { + return entities.check(event, entity -> { + if (!(entity instanceof Mob)) + return false; + Pathfinder pathfind = ((Mob) entity).getPathfinder(); + if (target == null) + return pathfind.hasPath(); + + PathResult current = pathfind.getCurrentPath(); + Object target = this.target.getSingle(event); + if (target == null || current == null) + return false; + Location location = current.getFinalPoint(); + if (target instanceof Location) + return location.equals(target); + assert target instanceof LivingEntity; + LivingEntity entityTarget = (LivingEntity) target; + return location.distance(((Mob) entityTarget).getLocation()) < 1; + }, isNegated()); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return PropertyCondition.toString(this, PropertyType.BE, event, debug, entities, "pathfinding" + + target == null ? "" : " to " + target.toString(event, debug)); + } + +} From 8f503c563d29d39b70e456c2139dbc9573a740ff Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Fri, 29 Mar 2024 08:41:33 +0100 Subject: [PATCH 10/17] Remove Player name/UUID in variables warning. (#6505) Update VariableString.java Co-authored-by: Moderocky --- src/main/java/ch/njol/skript/lang/VariableString.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/VariableString.java b/src/main/java/ch/njol/skript/lang/VariableString.java index 6f3c11e492a..a81cd600a4e 100644 --- a/src/main/java/ch/njol/skript/lang/VariableString.java +++ b/src/main/java/ch/njol/skript/lang/VariableString.java @@ -224,16 +224,6 @@ public static VariableString newInstance(String original, StringMode mode) { log.printErrors("Can't understand this expression: " + original.substring(exprStart + 1, exprEnd)); return null; } else { - if ( - mode == StringMode.VARIABLE_NAME && - !SkriptConfig.usePlayerUUIDsInVariableNames.value() && - OfflinePlayer.class.isAssignableFrom(expr.getReturnType()) - ) { - Skript.warning( - "In the future, players in variable names will use the player's UUID instead of their name. " + - "For information on how to make sure your scripts won't be impacted by this change, see https://github.com/SkriptLang/Skript/discussions/6270." - ); - } strings.add(expr); } log.printLog(); From 6fe77c8f5118704d26df0791ee124bd3ce59f6bd Mon Sep 17 00:00:00 2001 From: Shane Bee Date: Fri, 29 Mar 2024 02:35:54 -0700 Subject: [PATCH 11/17] Add MC 1.20.5 Attributes (#6413) default.lang - add upcoming 1.20.5 attributes Co-authored-by: Moderocky --- src/main/resources/lang/default.lang | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/resources/lang/default.lang b/src/main/resources/lang/default.lang index efd0651babf..2ac67709dc4 100644 --- a/src/main/resources/lang/default.lang +++ b/src/main/resources/lang/default.lang @@ -1934,12 +1934,21 @@ attribute types: generic_attack_speed: generic attack speed, attack speed generic_flying_speed: generic flying speed, flying speed generic_follow_range: generic follow range, follow range + generic_gravity: generic gravity, gravity + generic_jump_strength: generic jump strength, jump strength generic_knockback_resistance: generic knockback resistance, knockback resistance generic_luck: generic luck, luck generic_max_absorption: generic max absorption, max absorption generic_max_health: generic max health, max health generic_movement_speed: generic movement speed, movement speed + generic_safe_fall_distance: generic safe fall distance, safe fall distance + generic_fall_damage_multiplier: generic fall damage multiplier, fall damage multiplier + generic_scale: generic scale, scale + generic_step_height: generic step height, step height horse_jump_strength: horse jump strength + player_block_break_speed: player block break speed, block break speed + player_block_interaction_range: player block interaction range, block interaction range + player_entity_interaction_range: player entity interaction range, entity interaction range zombie_spawn_reinforcements: zombie spawn reinforcements # -- Environments -- From e3d357b8fb302033b9a83c469eb2c4982a22fac6 Mon Sep 17 00:00:00 2001 From: Shane Bee Date: Sat, 30 Mar 2024 10:38:56 -0700 Subject: [PATCH 12/17] ItemData/BlockCompat - strip out BlockState middleman (#6473) * ItemData/BlockCompat - strip out BlockState middleman * ItemData/BlockCompat - add stuff back, but deprecate * ItemData/BlockCompat - Change a few things around * NewBlockCompat - getBlock at entity instead of forcing a chunk to load * Update src/main/java/ch/njol/skript/aliases/ItemType.java Co-authored-by: Patrick Miller --------- Co-authored-by: Patrick Miller Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> Co-authored-by: Moderocky --- .../java/ch/njol/skript/aliases/ItemData.java | 19 ++++++++--- .../java/ch/njol/skript/aliases/ItemType.java | 33 ++++++++++++++----- .../ch/njol/skript/bukkitutil/ItemUtils.java | 4 ++- .../skript/bukkitutil/block/BlockCompat.java | 19 +++++++---- .../bukkitutil/block/NewBlockCompat.java | 26 ++++++++++----- .../njol/skript/entity/FallingBlockData.java | 4 +-- .../java/ch/njol/skript/events/EvtBlock.java | 2 +- .../java/ch/njol/skript/events/EvtGrow.java | 4 +-- 8 files changed, 76 insertions(+), 35 deletions(-) diff --git a/src/main/java/ch/njol/skript/aliases/ItemData.java b/src/main/java/ch/njol/skript/aliases/ItemData.java index b6f90b30f42..32323c57e6f 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemData.java +++ b/src/main/java/ch/njol/skript/aliases/ItemData.java @@ -31,6 +31,7 @@ import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemFactory; import org.bukkit.inventory.ItemFlag; @@ -188,15 +189,23 @@ public ItemData(ItemStack stack) { this(stack, BlockCompat.INSTANCE.getBlockValues(stack)); this.itemForm = true; } - - public ItemData(BlockState block) { - this.type = ItemUtils.asItem(block.getType()); + + /** + * @deprecated Use {@link ItemData#ItemData(BlockData)} instead + */ + @Deprecated + public ItemData(BlockState blockState) { + this(blockState.getBlockData()); + } + + public ItemData(BlockData blockData) { + this.type = blockData.getMaterial(); this.stack = new ItemStack(type); - this.blockValues = BlockCompat.INSTANCE.getBlockValues(block); + this.blockValues = BlockCompat.INSTANCE.getBlockValues(blockData); } public ItemData(Block block) { - this(block.getState()); + this(block.getBlockData()); } /** diff --git a/src/main/java/ch/njol/skript/aliases/ItemType.java b/src/main/java/ch/njol/skript/aliases/ItemType.java index 6b56e9cee17..7341667fa91 100644 --- a/src/main/java/ch/njol/skript/aliases/ItemType.java +++ b/src/main/java/ch/njol/skript/aliases/ItemType.java @@ -43,6 +43,7 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.block.Skull; +import org.bukkit.block.data.BlockData; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; @@ -184,10 +185,16 @@ public ItemType(ItemStack i) { add_(new ItemData(i)); } - public ItemType(BlockState b) { -// amount = 1; - add_(new ItemData(b)); - // TODO metadata - spawners, skulls, etc. + /** + * @deprecated Use {@link #ItemType(BlockData)} instead + */ + @Deprecated + public ItemType(BlockState blockState) { + this(blockState.getBlockData()); + } + + public ItemType(BlockData blockData) { + add_(new ItemData(blockData)); } /** @@ -211,7 +218,7 @@ public void setTo(ItemType i) { } public ItemType(Block block) { - this(block.getState()); + this(block.getBlockData()); } /** @@ -272,17 +279,25 @@ public boolean isOfType(@Nullable ItemStack item) { return isOfType(new ItemData(item)); } - public boolean isOfType(@Nullable BlockState block) { - if (block == null) + /** + * @deprecated Use {@link #isOfType(BlockData)} instead + */ + @Deprecated + public boolean isOfType(@Nullable BlockState blockState) { + return blockState != null && isOfType(blockState.getBlockData()); + } + + public boolean isOfType(@Nullable BlockData blockData) { + if (blockData == null) return isOfType(Material.AIR, null); - return isOfType(new ItemData(block)); + return isOfType(new ItemData(blockData)); } public boolean isOfType(@Nullable Block block) { if (block == null) return isOfType(Material.AIR, null); - return isOfType(block.getState()); + return isOfType(block.getBlockData()); } public boolean isOfType(ItemData type) { diff --git a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java index ca373598eff..c6d5f25870e 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java +++ b/src/main/java/ch/njol/skript/bukkitutil/ItemUtils.java @@ -74,13 +74,15 @@ public static Material asBlock(Material type) { return null; } } - + /** * Gets an item material corresponding to given block material, which might * be the given material. * @param type Material. * @return Item version of material or null. + * @deprecated This just returns itself and has no use */ + @Deprecated public static Material asItem(Material type) { // Assume (naively) that all types are valid items return type; diff --git a/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java b/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java index 5ffb1063284..eef0866a01b 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java +++ b/src/main/java/ch/njol/skript/bukkitutil/block/BlockCompat.java @@ -23,11 +23,11 @@ import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.FallingBlock; import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.Nullable; -import ch.njol.skript.Skript; import ch.njol.skript.aliases.ItemFlags; /** @@ -42,13 +42,15 @@ public interface BlockCompat { BlockCompat INSTANCE = new NewBlockCompat(); static final BlockSetter SETTER = INSTANCE.getSetter(); - + /** * Gets block values from a block state. They can be compared to other * values if needed, but cannot be used to retrieve any other data. * @param block Block state to retrieve value from. * @return Block values. + * @deprecated Use {@link #getBlockValues(BlockData)} instead */ + @Deprecated @Nullable BlockValues getBlockValues(BlockState block); @@ -60,8 +62,11 @@ public interface BlockCompat { */ @Nullable default BlockValues getBlockValues(Block block) { - return getBlockValues(block.getState()); + return getBlockValues(block.getBlockData()); } + + @Nullable + BlockValues getBlockValues(BlockData blockData); /** * Gets block values from a item stack. They can be compared to other values @@ -71,17 +76,19 @@ default BlockValues getBlockValues(Block block) { */ @Nullable BlockValues getBlockValues(ItemStack stack); - + /** * Creates a block state from a falling block. * @param entity Falling block entity * @return Block state. + * @deprecated This shouldn't be used */ + @Deprecated BlockState fallingBlockToState(FallingBlock entity); - + @Nullable default BlockValues getBlockValues(FallingBlock entity) { - return getBlockValues(fallingBlockToState(entity)); + return getBlockValues(entity.getBlockData()); } /** diff --git a/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java b/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java index 0610022d1c9..2a0ecd36da8 100644 --- a/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java +++ b/src/main/java/ch/njol/skript/bukkitutil/block/NewBlockCompat.java @@ -325,16 +325,23 @@ public void sendBlockChange(Player player, Location location, Material type, @Nu } private NewBlockSetter setter = new NewBlockSetter(); - + + /** + * @deprecated Use {@link #getBlockValues(BlockData)} instead + */ + @Deprecated @Nullable @Override - public BlockValues getBlockValues(BlockState block) { - // If block doesn't have useful data, data field of type is MaterialData - if (block.getType().isBlock()) - return new NewBlockValues(block.getType(), block.getBlockData(), false); - return null; + public BlockValues getBlockValues(BlockState blockState) { + return getBlockValues(blockState.getBlockData()); } - + + @Nullable + @Override + public BlockValues getBlockValues(BlockData blockData) { + return new NewBlockValues(blockData.getMaterial(), blockData, false); + } + @Override @Nullable public BlockValues getBlockValues(ItemStack stack) { @@ -345,15 +352,16 @@ public BlockValues getBlockValues(ItemStack stack) { } return null; } - + @Override public BlockSetter getSetter() { return setter; } + @Deprecated @Override public BlockState fallingBlockToState(FallingBlock entity) { - BlockState state = entity.getWorld().getBlockAt(0, 0, 0).getState(); + BlockState state = entity.getLocation().getBlock().getState(); state.setBlockData(entity.getBlockData()); return state; } diff --git a/src/main/java/ch/njol/skript/entity/FallingBlockData.java b/src/main/java/ch/njol/skript/entity/FallingBlockData.java index 2f937148fe9..262b2b564d8 100644 --- a/src/main/java/ch/njol/skript/entity/FallingBlockData.java +++ b/src/main/java/ch/njol/skript/entity/FallingBlockData.java @@ -93,7 +93,7 @@ public ItemType convert(ItemType t) { @Override protected boolean init(final @Nullable Class c, final @Nullable FallingBlock e) { if (e != null) // TODO material data support - types = new ItemType[] {new ItemType(BlockCompat.INSTANCE.fallingBlockToState(e))}; + types = new ItemType[] {new ItemType(e.getBlockData())}; return true; } @@ -101,7 +101,7 @@ protected boolean init(final @Nullable Class c, final @N protected boolean match(final FallingBlock entity) { if (types != null) { for (final ItemType t : types) { - if (t.isOfType(BlockCompat.INSTANCE.fallingBlockToState(entity))) + if (t.isOfType(entity.getBlockData())) return true; } return false; diff --git a/src/main/java/ch/njol/skript/events/EvtBlock.java b/src/main/java/ch/njol/skript/events/EvtBlock.java index 4a393146600..78d5fc1dc59 100644 --- a/src/main/java/ch/njol/skript/events/EvtBlock.java +++ b/src/main/java/ch/njol/skript/events/EvtBlock.java @@ -104,7 +104,7 @@ public boolean check(final Event event) { if (event instanceof BlockFormEvent) { BlockFormEvent blockFormEvent = (BlockFormEvent) event; BlockState newState = blockFormEvent.getNewState(); - item = new ItemType(newState); + item = new ItemType(newState.getBlockData()); blockData = newState.getBlockData(); } else if (event instanceof BlockEvent) { BlockEvent blockEvent = (BlockEvent) event; diff --git a/src/main/java/ch/njol/skript/events/EvtGrow.java b/src/main/java/ch/njol/skript/events/EvtGrow.java index 98f5f281728..ff2deb11a8d 100644 --- a/src/main/java/ch/njol/skript/events/EvtGrow.java +++ b/src/main/java/ch/njol/skript/events/EvtGrow.java @@ -174,7 +174,7 @@ private static boolean checkFrom(Event event, Literal types) { BlockState oldState = ((BlockGrowEvent) event).getBlock().getState(); return types.check(event, type -> { if (type instanceof ItemType) { - return ((ItemType) type).isOfType(oldState); + return ((ItemType) type).isOfType(oldState.getBlockData()); } else if (type instanceof BlockData) { return ((BlockData) type).matches(oldState.getBlockData()); } @@ -201,7 +201,7 @@ private static boolean checkTo(Event event, Literal types) { BlockState newState = ((BlockGrowEvent) event).getNewState(); return types.check(event, type -> { if (type instanceof ItemType) { - return ((ItemType) type).isOfType(newState); + return ((ItemType) type).isOfType(newState.getBlockData()); } else if (type instanceof BlockData) { return ((BlockData) type).matches(newState.getBlockData()); } From bfdadaa59813b14c113b1fb58b953fb0660610be Mon Sep 17 00:00:00 2001 From: Shane Bee Date: Sat, 30 Mar 2024 10:43:50 -0700 Subject: [PATCH 13/17] ExprIndices - fix strings not sorting alphabetically (#6495) * ExprIndices - fix strings not sorting alphabetically * ExprIndices - update method in ExprSortedList and use in this class * ExprIndices - forgot to get values * ExprSortedList - add a message to the error for better understanding --------- Co-authored-by: Moderocky --- .../ch/njol/skript/expressions/ExprIndices.java | 7 +------ .../ch/njol/skript/expressions/ExprSortedList.java | 7 +++++-- .../tests/syntaxes/expressions/ExprIndices.sk | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/njol/skript/expressions/ExprIndices.java b/src/main/java/ch/njol/skript/expressions/ExprIndices.java index 1f4439273b6..c09bfe81964 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprIndices.java +++ b/src/main/java/ch/njol/skript/expressions/ExprIndices.java @@ -28,7 +28,6 @@ import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.Variable; import ch.njol.skript.lang.util.SimpleExpression; -import org.skriptlang.skript.lang.comparator.Comparators; import ch.njol.skript.util.LiteralUtils; import ch.njol.util.Kleenean; import org.bukkit.event.Event; @@ -102,7 +101,7 @@ protected String[] get(Event e) { if (sort) { int direction = descending ? -1 : 1; return variable.entrySet().stream() - .sorted((a, b) -> compare(a, b, direction)) + .sorted((a, b) -> ExprSortedList.compare(a.getValue(), b.getValue()) * direction) .map(Entry::getKey) .toArray(String[]::new); } @@ -130,8 +129,4 @@ public String toString(@Nullable Event e, boolean debug) { return text; } - // Extracted method for better readability - private int compare(Entry a, Entry b, int direction) { - return Comparators.compare(a.getValue(), b.getValue()).getRelation() * direction; - } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprSortedList.java b/src/main/java/ch/njol/skript/expressions/ExprSortedList.java index 3cfed13b0a6..0a4af988476 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprSortedList.java +++ b/src/main/java/ch/njol/skript/expressions/ExprSortedList.java @@ -34,6 +34,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.skriptlang.skript.lang.comparator.Comparator; import org.skriptlang.skript.lang.comparator.Comparators; +import org.skriptlang.skript.lang.comparator.Relation; import java.lang.reflect.Array; @@ -77,12 +78,14 @@ protected Object[] get(Event event) { } @SuppressWarnings("unchecked") - private static int compare(A a, B b) throws IllegalArgumentException, ClassCastException { + public static int compare(A a, B b) throws IllegalArgumentException, ClassCastException { + if (a instanceof String && b instanceof String) + return Relation.get(((String) a).compareToIgnoreCase((String) b)).getRelation(); Comparator comparator = Comparators.getComparator((Class) a.getClass(), (Class) b.getClass()); if (comparator != null && comparator.supportsOrdering()) return comparator.compare(a, b).getRelation(); if (!(a instanceof Comparable)) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Cannot compare " + a.getClass()); return ((Comparable) a).compareTo(b); } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprIndices.sk b/src/test/skript/tests/syntaxes/expressions/ExprIndices.sk index fccca1d6a6d..855e4129b78 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprIndices.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprIndices.sk @@ -23,3 +23,17 @@ test "sorted indices": set {_a} to {_leader-board::%loop-value%} set {_b} to {_descending-values::%loop-index%} assert {_a} is equal to {_b} with "Sorting indices in descending order is incorrect" + +test "alphabetically sorted indices": + set {_test::a} to "Bob" + set {_test::b} to "Donald" + set {_test::c} to "Zeffer" + set {_test::d} to "Angus" + set {_test::e} to "Kevin" + set {_test::f} to "anderson" + set {_test::g} to "robertson" + + set {_indexes::*} to sorted indices of {_test::*} in ascending order + + assert {_test::%{_indexes::1}%} = "anderson" with "First element of sorted strings should be 'anderson'" + assert {_test::%{_indexes::7}%} = "Zeffer" with "Last element of sorted strings should be 'Zeffer'" From dfaa66fc3e8f5a177e99bb8cc113bcd9ad845467 Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Mon, 1 Apr 2024 03:51:28 -0600 Subject: [PATCH 14/17] Fix expression null pointer exception on expressions with null return type (#6497) * Fix null pointer exception and add proper error message * Update src/main/java/ch/njol/skript/lang/SkriptParser.java Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> * change error message --------- Co-authored-by: Ayham Al Ali <20037329+AyhamAl-Ali@users.noreply.github.com> Co-authored-by: Moderocky --- src/main/java/ch/njol/skript/lang/SkriptParser.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index 3d6a16b4eba..930c8e0639a 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -531,6 +531,9 @@ private Expression parseSingleExpr(boolean allowUnparsedLiteral, @Nullable Lo Expression parsedExpression = parseExpression(types, expr); if (parsedExpression != null) { // Expression/VariableString parsing success Class returnType = parsedExpression.getReturnType(); // Sometimes getReturnType does non-trivial costly operations + if (returnType == null) + throw new SkriptAPIException("Expression '" + expr + "' returned null for method Expression#getReturnType. Null is not a valid return."); + for (int i = 0; i < types.length; i++) { Class type = types[i]; if (type == null) // Ignore invalid (null) types From cabfad7277270c7688a1465a667e2c688f0b4a90 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Mon, 1 Apr 2024 12:57:21 +0200 Subject: [PATCH 15/17] Prepare for Release (2.8.4) (#6516) Bump version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e31c5a65002..3271da70815 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.8.3 +version=2.8.4 jarName=Skript.jar testEnv=java17/paper-1.20.4 testEnvJavaVersion=17 From e454a1059862cd34bc922d3125c9c2da4d222f06 Mon Sep 17 00:00:00 2001 From: Pikachu920 <28607612+Pikachu920@users.noreply.github.com> Date: Tue, 2 Apr 2024 21:09:49 -0500 Subject: [PATCH 16/17] Add more flexible static parse method (#6306) * Add more flexible static parse method * Remove extraneous new line * Add SkriptPattern overload --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../ch/njol/skript/lang/SkriptParser.java | 20 +++++ .../test/tests/parsing/StaticParseTest.java | 90 +++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 src/test/java/org/skriptlang/skript/test/tests/parsing/StaticParseTest.java diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index 930c8e0639a..d67318f5174 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -983,6 +983,26 @@ public static ParseResult parse(String text, String pattern) { return new SkriptParser(text, PARSE_LITERALS, ParseContext.COMMAND).parse_i(pattern); } + /** + * Parses the text as the given pattern with the given parse context and parse flags. + *

+ * Prints parse errors (i.e. must start a ParseLog before calling this method) + */ + @Nullable + public static ParseResult parse(String text, String pattern, int parseFlags, ParseContext parseContext) { + return new SkriptParser(text, parseFlags, parseContext).parse_i(pattern); + } + + /** + * Parses the text as the given pattern with the given parse context and parse flags. + *

+ * Prints parse errors (i.e. must start a ParseLog before calling this method) + */ + @Nullable + public static ParseResult parse(String text, SkriptPattern pattern, int parseFlags, ParseContext parseContext) { + return parse(text, pattern.toString(), parseFlags, parseContext); + } + /** * Finds the closing bracket of the group at start (i.e. start has to be in a group). * diff --git a/src/test/java/org/skriptlang/skript/test/tests/parsing/StaticParseTest.java b/src/test/java/org/skriptlang/skript/test/tests/parsing/StaticParseTest.java new file mode 100644 index 00000000000..b4589f63ccf --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/parsing/StaticParseTest.java @@ -0,0 +1,90 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.parsing; + +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.patterns.PatternCompiler; +import ch.njol.skript.patterns.SkriptPattern; +import org.junit.Assert; +import org.junit.Test; + +import java.util.stream.Stream; + +public class StaticParseTest { + + private Stream computeParseResults(String text, String pattern) { + ParseResult stringParseResult = SkriptParser.parse(text, pattern, SkriptParser.ALL_FLAGS, ParseContext.COMMAND); + SkriptPattern compiledPattern = PatternCompiler.compile(pattern); + ParseResult compiledParseResult = SkriptParser.parse(text, compiledPattern, SkriptParser.ALL_FLAGS, ParseContext.COMMAND); + return Stream.of(stringParseResult, compiledParseResult); + } + + @Test + public void testMultipleSingleExpressions() { + computeParseResults("1 test 2 test 3", "%number% test %number% test %number%").forEach(result -> { + Assert.assertNotNull("parse method returned null", result); + Assert.assertEquals("parse method returned wrong number of expressions", 3, result.exprs.length); + Assert.assertEquals("parse method returned wrong first expression", 1L, result.exprs[0].getSingle(null)); + Assert.assertEquals("parse method returned wrong second expression", 2L, result.exprs[1].getSingle(null)); + Assert.assertEquals("parse method returned wrong first expression", 3L, result.exprs[2].getSingle(null)); + }); + } + + @Test + public void testMultipleMultipleExpressions() { + computeParseResults("1, 2 and 3 test 4, 5 and 6", "%numbers% test %numbers%").forEach(result -> { + Assert.assertNotNull("parse method returned null", result); + Assert.assertEquals("parse method returned wrong number of expressions", 2, result.exprs.length); + Assert.assertArrayEquals("parse method returned wrong first expression", new Long[]{1L,2L,3L}, result.exprs[0].getArray(null)); + Assert.assertArrayEquals("parse method returned wrong second expression", new Long[]{4L,5L,6L}, result.exprs[1].getArray(null)); + }); + } + + @Test + public void testMultipleMixedExpressions() { + computeParseResults("1 test 2, 3 and 4", "%number% test %numbers%").forEach(result -> { + Assert.assertNotNull("parse method returned null", result); + Assert.assertEquals("parse method returned wrong number of expressions", 2, result.exprs.length); + Assert.assertEquals("parse method returned wrong first expression", 1L, result.exprs[0].getSingle(null)); + Assert.assertArrayEquals("parse method returned wrong second expression", new Long[]{2L,3L,4L}, result.exprs[1].getArray(null)); + }); + } + + @Test + public void testMultipleExpression() { + computeParseResults("1, 2 and 3", "%numbers%").forEach(result -> { + Assert.assertNotNull("parse method returned null", result); + Assert.assertEquals("parse method returned wrong number of expressions", 1, result.exprs.length); + Assert.assertArrayEquals("parse method returned wrong first expression", new Long[]{1L,2L,3L}, result.exprs[0].getArray(null)); + }); + + } + + @Test + public void testSingleExpression() { + computeParseResults("1", "%number%").forEach(result -> { + Assert.assertNotNull("parse method returned null", result); + Assert.assertEquals("parse method returned wrong number of expressions", 1, result.exprs.length); + Assert.assertEquals("parse method returned wrong first expression", 1L, result.exprs[0].getSingle(null)); + }); + } + +} From 33d0fb12f822ce118942d8909c92f62acd9d13b0 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sun, 7 Apr 2024 12:19:34 +0100 Subject: [PATCH 17/17] Adds ability to listen to cancelled events (#6207) * Hey! Listen! Adds functionality to allow events to listen to cancelled events only, uncancelled only (previous behavior), and both. Deprecates listenCancelled (breaking change for EvtResurrect) * Apply suggestions from code review Co-authored-by: _tud <98935832+UnderscoreTud@users.noreply.github.com> * Allow events to have specific default listening behavior Removes breaking change from EvtResurrect. Refactors some of SkriptEventHandler Errors for events that don't support cancelling Bug fix for data leak via EventData * revert incorrect java doc change * Streamline setting default behavior * indentation * Prevent any sort of listening behavior on events that can't be cancelled. Reduce local complexity in SkriptEventHandler * Fix merge mistake * Apply suggestions from code review Co-authored-by: Patrick Miller * Requested Changes * Cancelled Event Test * Fix merge mistakes I need to stop using the browser for merge conflicts * Update SkriptEventHandler.java --------- Co-authored-by: _tud <98935832+UnderscoreTud@users.noreply.github.com> Co-authored-by: Moderocky Co-authored-by: Patrick Miller --- .../ch/njol/skript/SkriptEventHandler.java | 175 +++++++++++------- .../ch/njol/skript/events/SimpleEvents.java | 27 ++- .../java/ch/njol/skript/lang/SkriptEvent.java | 87 ++++++++- .../ch/njol/skript/lang/SkriptEventInfo.java | 23 ++- .../njol/skript/structures/StructEvent.java | 28 ++- .../test/tests/lang/CancelledEventsTest.java | 51 +++++ .../skript/test/tests/lang/package-info.java | 24 +++ src/test/skript/junit/CancelledEventTest.sk | 32 ++++ 8 files changed, 361 insertions(+), 86 deletions(-) create mode 100644 src/test/java/org/skriptlang/skript/test/tests/lang/CancelledEventsTest.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/lang/package-info.java create mode 100644 src/test/skript/junit/CancelledEventTest.sk diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index b3ad81d8d4e..30954ba5cf9 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -18,17 +18,12 @@ */ package ch.njol.skript; -import java.lang.ref.WeakReference; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.stream.Collectors; - +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.Trigger; +import ch.njol.skript.timings.SkriptTimings; +import ch.njol.skript.util.Task; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; import org.bukkit.Bukkit; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; @@ -42,13 +37,16 @@ import org.bukkit.plugin.RegisteredListener; import org.eclipse.jdt.annotation.Nullable; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; - -import ch.njol.skript.lang.SkriptEvent; -import ch.njol.skript.lang.Trigger; -import ch.njol.skript.timings.SkriptTimings; -import ch.njol.skript.util.Task; +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; public final class SkriptEventHandler { @@ -112,66 +110,94 @@ private static List getTriggers(Class event) { * @param priority The priority of the Event. */ private static void check(Event event, EventPriority priority) { + // get all triggers for this event, return if none List triggers = getTriggers(event.getClass()); if (triggers.isEmpty()) return; - if (Skript.logVeryHigh()) { - boolean hasTrigger = false; - for (Trigger trigger : triggers) { - SkriptEvent triggerEvent = trigger.getEvent(); - if ( - triggerEvent.getEventPriority() == priority - && triggerEvent.canExecuteAsynchronously() ? triggerEvent.check(event) : Boolean.TRUE.equals(Task.callSync(() -> triggerEvent.check(event))) - ) { - hasTrigger = true; - break; - } - } - if (!hasTrigger) - return; + // Check if this event should be treated as cancelled + boolean isCancelled = isCancelled(event); - logEventStart(event); - } - - boolean isCancelled = event instanceof Cancellable && ((Cancellable) event).isCancelled() && !listenCancelled.contains(event.getClass()); - boolean isResultDeny = !(event instanceof PlayerInteractEvent && (((PlayerInteractEvent) event).getAction() == Action.LEFT_CLICK_AIR || ((PlayerInteractEvent) event).getAction() == Action.RIGHT_CLICK_AIR) && ((PlayerInteractEvent) event).useItemInHand() != Result.DENY); - - if (isCancelled && isResultDeny) { - if (Skript.logVeryHigh()) - Skript.info(" -x- was cancelled"); - return; - } + // This logs events even if there isn't a trigger that's going to run at that priority. + // However, there should only be a priority listener IF there's a trigger at that priority. + // So the time will be logged even if no triggers pass check(), which is still useful information. + logEventStart(event, priority); for (Trigger trigger : triggers) { SkriptEvent triggerEvent = trigger.getEvent(); + + // check if the trigger is at the right priority if (triggerEvent.getEventPriority() != priority) continue; - // these methods need to be run on whatever thread the trigger is - Runnable execute = () -> { - logTriggerStart(trigger); - Object timing = SkriptTimings.start(trigger.getDebugLabel()); - trigger.execute(event); - SkriptTimings.stop(timing); - logTriggerEnd(trigger); - }; - - if (trigger.getEvent().canExecuteAsynchronously()) { - if (triggerEvent.check(event)) - execute.run(); - } else { // Ensure main thread - Task.callSync(() -> { - if (triggerEvent.check(event)) - execute.run(); - return null; // we don't care about a return value - }); - } + // check if the cancel state of the event is correct + if (!triggerEvent.getListeningBehavior().matches(isCancelled)) + continue; + + // execute the trigger + execute(trigger, event); } logEventEnd(); } + /** + * Helper method to check if we should treat the provided Event as cancelled. + * + * @param event The event to check. + * @return Whether the event should be treated as cancelled. + */ + private static boolean isCancelled(Event event) { + return event instanceof Cancellable && + (((Cancellable) event).isCancelled() && isResultDeny(event)) && + // TODO: listenCancelled is deprecated and should be removed in 2.10 + !listenCancelled.contains(event.getClass()); + } + + /** + * Helper method for when the provided Event is a {@link PlayerInteractEvent}. + * These events are special in that they are called as cancelled when the player is left/right clicking on air. + * We don't want to treat those as cancelled, so we need to check if the {@link PlayerInteractEvent#useItemInHand()} result is DENY. + * That means the event was purposefully cancelled, and we should treat it as cancelled. + * + * @param event The event to check. + * @return Whether the event was a PlayerInteractEvent with air and the result was DENY. + */ + private static boolean isResultDeny(Event event) { + return !(event instanceof PlayerInteractEvent && + (((PlayerInteractEvent) event).getAction() == Action.LEFT_CLICK_AIR || ((PlayerInteractEvent) event).getAction() == Action.RIGHT_CLICK_AIR) && + ((PlayerInteractEvent) event).useItemInHand() != Result.DENY); + } + + /** + * Executes the provided Trigger with the provided Event as context. + * + * @param trigger The Trigger to execute. + * @param event The Event to execute the Trigger with. + */ + private static void execute(Trigger trigger, Event event) { + // these methods need to be run on whatever thread the trigger is + Runnable execute = () -> { + logTriggerStart(trigger); + Object timing = SkriptTimings.start(trigger.getDebugLabel()); + trigger.execute(event); + SkriptTimings.stop(timing); + logTriggerEnd(trigger); + }; + + if (trigger.getEvent().canExecuteAsynchronously()) { + if (trigger.getEvent().check(event)) + execute.run(); + } else { // Ensure main thread + Task.callSync(() -> { + if (trigger.getEvent().check(event)) + execute.run(); + return null; // we don't care about a return value + }); + } + } + + private static long startEvent; /** @@ -180,11 +206,30 @@ private static void check(Event event, EventPriority priority) { * @param event The Event that started. */ public static void logEventStart(Event event) { + logEventStart(event, null); + } + + /** + * Logs that the provided Event has started with a priority. + * Requires {@link Skript#logVeryHigh()} to be true to log anything. + * @param event The Event that started. + * @param priority The priority of the Event. + */ + public static void logEventStart(Event event, @Nullable EventPriority priority) { startEvent = System.nanoTime(); if (!Skript.logVeryHigh()) return; Skript.info(""); - Skript.info("== " + event.getClass().getName() + " =="); + + String message = "== " + event.getClass().getName(); + + if (priority != null) + message += " with priority " + priority; + + if (event instanceof Cancellable && ((Cancellable) event).isCancelled()) + message += " (cancelled)"; + + Skript.info(message + " =="); } /** @@ -307,8 +352,10 @@ public static void unregisterBukkitEvents(Trigger trigger) { } /** - * Events which are listened even if they are cancelled. + * Events which are listened even if they are cancelled. This should no longer be used. + * @deprecated Users should specify the listening behavior in the event declaration. "on any %event%:", "on cancelled %event%:". */ + @Deprecated public static final Set> listenCancelled = new HashSet<>(); /** diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index 18679fcbbbf..4a7b7300430 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -18,11 +18,20 @@ */ package ch.njol.skript.events; +import ch.njol.skript.Skript; +import ch.njol.skript.lang.SkriptEvent.ListeningBehavior; +import ch.njol.skript.lang.util.SimpleEvent; import com.destroystokyo.paper.event.block.AnvilDamagedEvent; +import com.destroystokyo.paper.event.entity.EntityJumpEvent; +import com.destroystokyo.paper.event.entity.ProjectileCollideEvent; +import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; +import com.destroystokyo.paper.event.player.PlayerJumpEvent; +import com.destroystokyo.paper.event.server.PaperServerListPingEvent; import com.destroystokyo.paper.event.player.PlayerReadyArrowEvent; import io.papermc.paper.event.player.PlayerStopUsingItemEvent; import io.papermc.paper.event.player.PlayerDeepSleepEvent; import io.papermc.paper.event.player.PlayerInventorySlotChangeEvent; +import io.papermc.paper.event.player.PlayerTradeEvent; import org.bukkit.event.block.BlockCanBuildEvent; import org.bukkit.event.block.BlockDamageEvent; import org.bukkit.event.block.BlockFertilizeEvent; @@ -35,9 +44,9 @@ import org.bukkit.event.block.BlockSpreadEvent; import org.bukkit.event.block.LeavesDecayEvent; import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.block.SpongeAbsorbEvent; import org.bukkit.event.enchantment.EnchantItemEvent; import org.bukkit.event.enchantment.PrepareItemEnchantEvent; -import org.bukkit.event.block.SpongeAbsorbEvent; import org.bukkit.event.entity.AreaEffectCloudApplyEvent; import org.bukkit.event.entity.CreeperPowerEvent; import org.bukkit.event.entity.EntityBreakDoorEvent; @@ -108,16 +117,6 @@ import org.spigotmc.event.entity.EntityDismountEvent; import org.spigotmc.event.entity.EntityMountEvent; -import com.destroystokyo.paper.event.entity.ProjectileCollideEvent; -import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; -import com.destroystokyo.paper.event.player.PlayerJumpEvent; -import com.destroystokyo.paper.event.server.PaperServerListPingEvent; -import com.destroystokyo.paper.event.entity.EntityJumpEvent; -import io.papermc.paper.event.player.PlayerTradeEvent; -import ch.njol.skript.Skript; -import ch.njol.skript.SkriptEventHandler; -import ch.njol.skript.lang.util.SimpleEvent; - /** * @author Peter Güttinger */ @@ -453,13 +452,13 @@ public class SimpleEvents { Skript.registerEvent("Resurrect Attempt", SimpleEvent.class, EntityResurrectEvent.class, "[entity] resurrect[ion] [attempt]") .description("Called when an entity dies, always. If they are not holding a totem, this is cancelled - you can, however, uncancel it.") .examples( - "on resurrect attempt:", + "on resurrect attempt:", "\tentity is player", "\tentity has permission \"admin.undying\"", "\tuncancel the event" ) - .since("2.2-dev28"); - SkriptEventHandler.listenCancelled.add(EntityResurrectEvent.class); // Listen this even when cancelled + .since("2.2-dev28") + .listeningBehavior(ListeningBehavior.ANY); Skript.registerEvent("Player World Change", SimpleEvent.class, PlayerChangedWorldEvent.class, "[player] world chang(ing|e[d])") .description("Called when a player enters a world. Does not work with other entities!") .examples("on player world change:", diff --git a/src/main/java/ch/njol/skript/lang/SkriptEvent.java b/src/main/java/ch/njol/skript/lang/SkriptEvent.java index 3be262a6005..fc72138db30 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEvent.java @@ -26,12 +26,14 @@ import ch.njol.skript.events.EvtClick; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.structures.StructEvent.EventData; -import org.skriptlang.skript.lang.script.Script; -import org.skriptlang.skript.lang.entry.EntryContainer; -import org.skriptlang.skript.lang.structure.Structure; +import ch.njol.skript.util.Utils; +import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.EventPriority; import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.entry.EntryContainer; +import org.skriptlang.skript.lang.script.Script; +import org.skriptlang.skript.lang.structure.Structure; import java.util.List; import java.util.Locale; @@ -54,6 +56,9 @@ public abstract class SkriptEvent extends Structure { private String expr; @Nullable protected EventPriority eventPriority; + @Nullable + protected ListeningBehavior listeningBehavior; + protected boolean supportsListeningBehavior; private SkriptEventInfo skriptEventInfo; /** @@ -65,7 +70,9 @@ public abstract class SkriptEvent extends Structure { public final boolean init(Literal[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { this.expr = parseResult.expr; - EventPriority priority = getParser().getData(EventData.class).getPriority(); + EventData eventData = getParser().getData(EventData.class); + + EventPriority priority = eventData.getPriority(); if (priority != null && !isEventPrioritySupported()) { Skript.error("This event doesn't support event priority"); return false; @@ -77,6 +84,23 @@ public final boolean init(Literal[] args, int matchedPattern, ParseResult par throw new IllegalStateException(); skriptEventInfo = (SkriptEventInfo) syntaxElementInfo; + // evaluate whether this event supports listening to cancelled events + supportsListeningBehavior = false; + for (Class eventClass : getEventClasses()) { + if (Cancellable.class.isAssignableFrom(eventClass)) { + supportsListeningBehavior = true; + break; + } + } + + listeningBehavior = eventData.getListenerBehavior(); + // if the behavior is non-null, it was set by the user + if (listeningBehavior != null && !isListeningBehaviorSupported()) { + String eventName = skriptEventInfo.name.toLowerCase(Locale.ENGLISH); + Skript.error(Utils.A(eventName) + " event does not support listening for cancelled or uncancelled events."); + return false; + } + return init(args, matchedPattern, parseResult); } @@ -199,6 +223,21 @@ public boolean isEventPrioritySupported() { return true; } + /** + * @return the {@link ListeningBehavior} to be used for this event. Defaults to the default listening behavior + * of the SkriptEventInfo for this SkriptEvent. + */ + public ListeningBehavior getListeningBehavior() { + return listeningBehavior != null ? listeningBehavior : skriptEventInfo.getListeningBehavior(); + } + + /** + * @return whether this SkriptEvent supports listening behaviors + */ + public boolean isListeningBehaviorSupported() { + return supportsListeningBehavior; + } + /** * Override this method to allow Skript to not force synchronization. */ @@ -241,4 +280,44 @@ public static SkriptEvent parse(String expr, SectionNode sectionNode, @Nullable return (SkriptEvent) Structure.parse(expr, sectionNode, defaultError, Skript.getEvents().iterator()); } + /** + * The listening behavior of a Skript event. This determines whether the event should run for cancelled events, uncancelled events, or both. + */ + public enum ListeningBehavior { + + /** + * This Skript event should run for any uncancelled event. + */ + UNCANCELLED, + + /** + * This Skript event should run for any cancelled event. + */ + CANCELLED, + + /** + * This Skript event should run for any event, cancelled or uncancelled. + */ + ANY; + + /** + * Checks whether this listening behavior matches the given cancelled state. + * @param cancelled Whether the event is cancelled. + * @return Whether an event with the given cancelled state should be run for this listening behavior. + */ + public boolean matches(final boolean cancelled) { + switch (this) { + case CANCELLED: + return cancelled; + case UNCANCELLED: + return !cancelled; + case ANY: + return true; + default: + assert false; + return false; + } + } + } + } diff --git a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java index 9805ab4b986..610b0671529 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java @@ -23,6 +23,7 @@ import org.bukkit.event.player.PlayerInteractAtEntityEvent; import org.eclipse.jdt.annotation.Nullable; import org.skriptlang.skript.lang.structure.StructureInfo; +import ch.njol.skript.lang.SkriptEvent.ListeningBehavior; import java.util.Locale; @@ -30,7 +31,9 @@ public final class SkriptEventInfo extends StructureInfo< public Class[] events; public final String name; - + + private ListeningBehavior listeningBehavior; + @Nullable private String[] description, examples, keywords, requiredPlugins; @@ -70,6 +73,20 @@ public SkriptEventInfo(String name, String[] patterns, Class eventClass, Stri // uses the name without 'on ' or '*' this.id = "" + name.toLowerCase(Locale.ENGLISH).replaceAll("[#'\"<>/&]", "").replaceAll("\\s+", "_"); + + // default listening behavior should be to listen to uncancelled events + this.listeningBehavior = ListeningBehavior.UNCANCELLED; + } + + /** + * Sets the default listening behavior for this SkriptEvent. If omitted, the default behavior is to listen to uncancelled events. + * + * @param listeningBehavior The listening behavior of this SkriptEvent. + * @return This SkriptEventInfo object + */ + public SkriptEventInfo listeningBehavior(ListeningBehavior listeningBehavior) { + this.listeningBehavior = listeningBehavior; + return this; } /** @@ -158,6 +175,10 @@ public String getName() { return name; } + public ListeningBehavior getListeningBehavior() { + return listeningBehavior; + } + @Nullable public String[] getDescription() { return description; diff --git a/src/main/java/ch/njol/skript/structures/StructEvent.java b/src/main/java/ch/njol/skript/structures/StructEvent.java index c09afc8b259..814571463ad 100644 --- a/src/main/java/ch/njol/skript/structures/StructEvent.java +++ b/src/main/java/ch/njol/skript/structures/StructEvent.java @@ -22,6 +22,7 @@ import ch.njol.skript.doc.NoDoc; import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptEvent.ListeningBehavior; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.parser.ParserInstance; import org.bukkit.event.Event; @@ -37,7 +38,7 @@ public class StructEvent extends Structure { static { Skript.registerStructure(StructEvent.class, - "[on] <.+> [with priority (:(lowest|low|normal|high|highest|monitor))]"); + "[on] [:uncancelled|:cancelled|any:(any|all)] <.+> [priority:with priority (:(lowest|low|normal|high|highest|monitor))]"); } private SkriptEvent event; @@ -51,8 +52,18 @@ public boolean init(Literal[] args, int matchedPattern, ParseResult parseResu // ensure there's no leftover data from previous parses data.clear(); - if (!parseResult.tags.isEmpty()) - data.priority = EventPriority.valueOf(parseResult.tags.get(0).toUpperCase(Locale.ENGLISH)); + if (parseResult.hasTag("uncancelled")) { + data.behavior = ListeningBehavior.UNCANCELLED; + } else if (parseResult.hasTag("cancelled")) { + data.behavior = ListeningBehavior.CANCELLED; + } else if (parseResult.hasTag("any")) { + data.behavior = ListeningBehavior.ANY; + } + + if (parseResult.hasTag("priority")) { + String lastTag = parseResult.tags.get(parseResult.tags.size() - 1); + data.priority = EventPriority.valueOf(lastTag.toUpperCase(Locale.ENGLISH)); + } event = SkriptEvent.parse(expr, entryContainer.getSource(), null); @@ -111,6 +122,8 @@ public static class EventData extends ParserInstance.Data { @Nullable private EventPriority priority; + @Nullable + private ListeningBehavior behavior; public EventData(ParserInstance parserInstance) { super(parserInstance); @@ -122,10 +135,19 @@ public EventPriority getPriority() { } /** + * @return the listening behavior that should be used for the event. Null indicates that the user did not specify a behavior. + */ + @Nullable + public ListeningBehavior getListenerBehavior() { + return behavior; + } + + /** * Clears all event-specific data from this instance. */ public void clear() { priority = null; + behavior = null; } } diff --git a/src/test/java/org/skriptlang/skript/test/tests/lang/CancelledEventsTest.java b/src/test/java/org/skriptlang/skript/test/tests/lang/CancelledEventsTest.java new file mode 100644 index 00000000000..17f7606fa6a --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/lang/CancelledEventsTest.java @@ -0,0 +1,51 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package org.skriptlang.skript.test.tests.lang; + +import ch.njol.skript.test.runner.SkriptJUnitTest; +import org.bukkit.Bukkit; +import org.bukkit.entity.Pig; +import org.bukkit.event.entity.EntityDeathEvent; +import org.junit.Test; + +import java.util.ArrayList; + +public class CancelledEventsTest extends SkriptJUnitTest { + + + static { + setShutdownDelay(1); + } + + @Test + public void callCancelledEvent() { + Pig pig = spawnTestPig(); + EntityDeathEvent event = new EntityDeathEvent(pig, new ArrayList<>()); + + // call cancelled event + event.setCancelled(true); + Bukkit.getPluginManager().callEvent(event); + + // call non-cancelled event + event.setCancelled(false); + Bukkit.getPluginManager().callEvent(event); + } + +} + diff --git a/src/test/java/org/skriptlang/skript/test/tests/lang/package-info.java b/src/test/java/org/skriptlang/skript/test/tests/lang/package-info.java new file mode 100644 index 00000000000..ea942bce2d4 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/lang/package-info.java @@ -0,0 +1,24 @@ +/** + * 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 + */ +@NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) +package org.skriptlang.skript.test.tests.lang; + +import org.eclipse.jdt.annotation.DefaultLocation; +import org.eclipse.jdt.annotation.NonNullByDefault; + diff --git a/src/test/skript/junit/CancelledEventTest.sk b/src/test/skript/junit/CancelledEventTest.sk new file mode 100644 index 00000000000..f7abc9d99bc --- /dev/null +++ b/src/test/skript/junit/CancelledEventTest.sk @@ -0,0 +1,32 @@ +options: + test: "org.skriptlang.skript.test.tests.lang.CancelledEventsTest" + +# TODO: add negative objectives to test if the cancelled listener is called when the event is not cancelled, for example. + +test "ExprDropsJUnit" when running JUnit: + set {_tests::1} to "listen for uncancelled event" + set {_tests::5} to "listen for cancelled event" + set {_tests::6} to "listen for any event" + + ensure junit test {@test} completes {_tests::*} + +on load: + set {-cancelled-event-test::call-count} to 0 + +on death of pig: + junit test is {@test} + + complete objective "listen for uncancelled event" for {@test} + +on cancelled death of pig: + junit test is {@test} + + complete objective "listen for cancelled event" for {@test} + +on any death of pig: + junit test is {@test} + + add 1 to {-cancelled-event-test::call-count} + + if {-cancelled-event-test::call-count} is 2: + complete objective "listen for any event" for {@test}