From cad575f6c5c3e01e47a72bfae77333b21ca5e99b Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Thu, 30 Nov 2023 12:07:53 -0800 Subject: [PATCH 01/13] 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) --- .../ch/njol/skript/SkriptEventHandler.java | 110 +++++++++++------- .../ch/njol/skript/events/SimpleEvents.java | 31 +++-- .../java/ch/njol/skript/lang/SkriptEvent.java | 41 ++++++- .../njol/skript/structures/StructEvent.java | 28 ++++- 4 files changed, 145 insertions(+), 65 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index 28eaff9b23c..65464490bc3 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,39 +110,42 @@ 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 && Boolean.TRUE.equals(Task.callSync(() -> triggerEvent.check(event)))) { - hasTrigger = true; - break; - } - } - if (!hasTrigger) - return; - - 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 handles cancelled left/right clicks on air. Left/right clicks on air are called as cancelled, + // but we want to listen to them as if they are uncancelled unless they're specifically cancelled, + // so we can't rely on isCancelled() alone. + // Checking useItemInHand() is a reliable method, as it will be DENY if the event was specifically cancelled. + // Note that right clicks on air with nothing in hand aren't ever sent to the server. + boolean isResultDeny = !(event instanceof PlayerInteractEvent && + (((PlayerInteractEvent) event).getAction() == Action.LEFT_CLICK_AIR || ((PlayerInteractEvent) event).getAction() == Action.RIGHT_CLICK_AIR) && + ((PlayerInteractEvent) event).useItemInHand() != Result.DENY); + + // Check if this event should be treated as cancelled + // listenCancelled is deprecated and should be removed in 2.9 + boolean isCancelled = event instanceof Cancellable && + (((Cancellable) event).isCancelled() && isResultDeny) && + !listenCancelled.contains(event.getClass()); + + // 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; + // check if the cancel state of the event is correct + if (!triggerEvent.matchesListeningBehavior(isCancelled)) + continue; + // these methods need to be run on whatever thread the trigger is Runnable execute = () -> { logTriggerStart(trigger); @@ -175,16 +176,35 @@ private static void check(Event event, EventPriority priority) { private static long startEvent; /** - * Logs that the provided Event has started. + * Logs that the provided Event has started, using the default EventPriority. * Requires {@link Skript#logVeryHigh()} to be true to log anything. * @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 +327,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 a294c6f7b98..522cacc2dac 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -18,9 +18,17 @@ */ package ch.njol.skript.events; +import ch.njol.skript.Skript; +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 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; @@ -33,9 +41,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; @@ -110,16 +118,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 */ @@ -473,15 +471,14 @@ public class SimpleEvents { .examples("on slime split:") .since("2.2-dev26"); 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.") + .description("Called when an entity dies, always. If they are not holding a totem, the event will be cancelled - you can, however, uncancel it. Remember to use \"on any\" or \"on cancelled\" to listen to cancelled events.") .examples( - "on resurrect attempt:", - "\tentity is player", - "\tentity has permission \"admin.undying\"", - "\tuncancel the event" + "on any 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 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 bbdbd7d170a..488e5549be4 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEvent.java @@ -54,6 +54,7 @@ public abstract class SkriptEvent extends Structure { private String expr; @Nullable protected EventPriority eventPriority; + protected ListeningBehavior listeningBehavior; private SkriptEventInfo skriptEventInfo; /** @@ -65,7 +66,11 @@ 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); + + listeningBehavior = eventData.getListenerBehavior(); + + EventPriority priority = eventData.getPriority(); if (priority != null && !isEventPrioritySupported()) { Skript.error("This event doesn't support event priority"); return false; @@ -199,6 +204,22 @@ public boolean isEventPrioritySupported() { return true; } + /** + * @return the {@link ListeningBehavior} to be used for this event. + */ + public ListeningBehavior getListeningBehavior() { + return listeningBehavior; + } + + /** + * @return whether this SkriptEvent should be run for the given cancelled state. + */ + public boolean matchesListeningBehavior(boolean isCancelled) { + return listeningBehavior == ListeningBehavior.ANY + || (listeningBehavior == ListeningBehavior.UNCANCELLED && !isCancelled) + || (listeningBehavior == ListeningBehavior.CANCELLED && isCancelled); + } + /** * Override this method to allow Skript to not force synchronization. */ @@ -241,4 +262,22 @@ 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 be run for cancelled events, uncancelled events, or both. + */ + public enum ListeningBehavior { + /** + * This Skript event should be run for any uncancelled event. + */ + UNCANCELLED, + /** + * This Skript event should be run for any cancelled event. + */ + CANCELLED, + /** + * This Skript event should be run for any event, cancelled or uncancelled. + */ + ANY + } + } diff --git a/src/main/java/ch/njol/skript/structures/StructEvent.java b/src/main/java/ch/njol/skript/structures/StructEvent.java index ceb6fe53f9f..97e244776e5 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; @@ -46,8 +47,20 @@ public class StructEvent extends Structure { @SuppressWarnings("ConstantConditions") public boolean init(Literal[] args, int matchedPattern, ParseResult parseResult, EntryContainer entryContainer) { String expr = parseResult.regexes.get(0).group(); - if (!parseResult.tags.isEmpty()) - getParser().getData(EventData.class).priority = EventPriority.valueOf(parseResult.tags.get(0).toUpperCase(Locale.ENGLISH)); + + EventData data = getParser().getData(EventData.class); + + data.behavior = ListeningBehavior.UNCANCELLED; + 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); return event != null; } @@ -102,6 +115,8 @@ public static class EventData extends ParserInstance.Data { @Nullable private EventPriority priority; + @Nullable + private ListeningBehavior behavior; public EventData(ParserInstance parserInstance) { super(parserInstance); @@ -112,6 +127,13 @@ public EventPriority getPriority() { return priority; } + /** + * @return the listening behavior that should be used for the event. Defaults to {@link ListeningBehavior#UNCANCELLED} + */ + public ListeningBehavior getListenerBehavior() { + return behavior == null ? ListeningBehavior.UNCANCELLED : behavior; + } + } } From eb97afeda3cac118c737a6bc0da1f1f3c1d4e80d Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Thu, 30 Nov 2023 13:17:59 -0800 Subject: [PATCH 02/13] Apply suggestions from code review Co-authored-by: _tud <98935832+UnderscoreTud@users.noreply.github.com> --- src/main/java/ch/njol/skript/SkriptEventHandler.java | 4 ++-- src/main/java/ch/njol/skript/structures/StructEvent.java | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index 65464490bc3..1d72138f24f 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -176,7 +176,7 @@ private static void check(Event event, EventPriority priority) { private static long startEvent; /** - * Logs that the provided Event has started, using the default EventPriority. + * Logs that the provided Event has started, using the default {@link EventPriority}. * Requires {@link Skript#logVeryHigh()} to be true to log anything. * @param event The Event that started. */ @@ -187,7 +187,7 @@ public static void logEventStart(Event event) { /** * 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 event The Event that started. * @param priority The priority of the Event. */ public static void logEventStart(Event event, @Nullable EventPriority priority) { diff --git a/src/main/java/ch/njol/skript/structures/StructEvent.java b/src/main/java/ch/njol/skript/structures/StructEvent.java index 97e244776e5..d3ed618f069 100644 --- a/src/main/java/ch/njol/skript/structures/StructEvent.java +++ b/src/main/java/ch/njol/skript/structures/StructEvent.java @@ -51,10 +51,11 @@ public boolean init(Literal[] args, int matchedPattern, ParseResult parseResu EventData data = getParser().getData(EventData.class); data.behavior = ListeningBehavior.UNCANCELLED; - if (parseResult.hasTag("cancelled")) + if (parseResult.hasTag("cancelled")) { data.behavior = ListeningBehavior.CANCELLED; - else if (parseResult.hasTag("any")) + } else if (parseResult.hasTag("any")) { data.behavior = ListeningBehavior.ANY; + } if (parseResult.hasTag("priority")) { String lastTag = parseResult.tags.get(parseResult.tags.size() - 1); From 80d4b1f9ac15101008997bef8c86676db1cee412 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Thu, 30 Nov 2023 19:14:30 -0800 Subject: [PATCH 03/13] 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 --- .../ch/njol/skript/SkriptEventHandler.java | 44 ++++++++++---- .../ch/njol/skript/events/EvtResurrect.java | 60 +++++++++++++++++++ .../ch/njol/skript/events/SimpleEvents.java | 10 ---- .../java/ch/njol/skript/lang/SkriptEvent.java | 45 +++++++++++--- .../njol/skript/structures/StructEvent.java | 15 +++-- 5 files changed, 139 insertions(+), 35 deletions(-) create mode 100644 src/main/java/ch/njol/skript/events/EvtResurrect.java diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index 1d72138f24f..ff70fcee2ab 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -115,20 +115,10 @@ private static void check(Event event, EventPriority priority) { if (triggers.isEmpty()) return; - // This handles cancelled left/right clicks on air. Left/right clicks on air are called as cancelled, - // but we want to listen to them as if they are uncancelled unless they're specifically cancelled, - // so we can't rely on isCancelled() alone. - // Checking useItemInHand() is a reliable method, as it will be DENY if the event was specifically cancelled. - // Note that right clicks on air with nothing in hand aren't ever sent to the server. - boolean isResultDeny = !(event instanceof PlayerInteractEvent && - (((PlayerInteractEvent) event).getAction() == Action.LEFT_CLICK_AIR || ((PlayerInteractEvent) event).getAction() == Action.RIGHT_CLICK_AIR) && - ((PlayerInteractEvent) event).useItemInHand() != Result.DENY); + // Check if this event should be treated as cancelled - // listenCancelled is deprecated and should be removed in 2.9 - boolean isCancelled = event instanceof Cancellable && - (((Cancellable) event).isCancelled() && isResultDeny) && - !listenCancelled.contains(event.getClass()); + boolean isCancelled = isCancelled(event); // 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. @@ -143,7 +133,7 @@ private static void check(Event event, EventPriority priority) { continue; // check if the cancel state of the event is correct - if (!triggerEvent.matchesListeningBehavior(isCancelled)) + if (triggerEvent.isListeningBehaviorSupported() && !triggerEvent.matchesListeningBehavior(isCancelled)) continue; // these methods need to be run on whatever thread the trigger is @@ -173,6 +163,34 @@ private static void check(Event event, EventPriority priority) { 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)) && + // listenCancelled is deprecated and should be removed in 2.9 + !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); + } + private static long startEvent; /** diff --git a/src/main/java/ch/njol/skript/events/EvtResurrect.java b/src/main/java/ch/njol/skript/events/EvtResurrect.java new file mode 100644 index 00000000000..e3da19eb53a --- /dev/null +++ b/src/main/java/ch/njol/skript/events/EvtResurrect.java @@ -0,0 +1,60 @@ +/** + * 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; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptEvent; +import ch.njol.skript.lang.SkriptParser; +import org.bukkit.event.Event; +import org.bukkit.event.entity.EntityResurrectEvent; +import org.eclipse.jdt.annotation.Nullable; + +public class EvtResurrect extends SkriptEvent { + static { + Skript.registerEvent("Resurrect Attempt", EvtResurrect.class, EntityResurrectEvent.class, "[entity] resurrect[ion] [attempt]") + .description("Called when an entity dies, always. If they are not holding a totem, the event will be cancelled - you can, however, uncancel it.") + .examples( + "on resurrect attempt:", + "\tentity is player", + "\tentity has permission \"admin.undying\"", + "\tuncancel the event" + ) + .since("2.2-dev28"); + } + + @Override + public boolean init(Literal[] args, int matchedPattern, SkriptParser.ParseResult parseResult) { + // if the user hasn't specified a listening behavior, we'll default to ANY + if (listeningBehavior == null) + listeningBehavior = ListeningBehavior.ANY; + return true; + } + + @Override + public boolean check(Event event) { + return true; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "resurrect attempt"; + } + +} diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index 522cacc2dac..77598a93eb0 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -52,7 +52,6 @@ import org.bukkit.event.entity.EntityPortalEnterEvent; import org.bukkit.event.entity.EntityPortalEvent; import org.bukkit.event.entity.EntityRegainHealthEvent; -import org.bukkit.event.entity.EntityResurrectEvent; import org.bukkit.event.entity.EntityTameEvent; import org.bukkit.event.entity.EntityToggleGlideEvent; import org.bukkit.event.entity.EntityToggleSwimEvent; @@ -470,15 +469,6 @@ public class SimpleEvents { .description("Called when a slime splits. Usually this happens when a big slime dies.") .examples("on slime split:") .since("2.2-dev26"); - 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, the event will be cancelled - you can, however, uncancel it. Remember to use \"on any\" or \"on cancelled\" to listen to cancelled events.") - .examples( - "on any resurrect attempt:", - "\tentity is player", - "\tentity has permission \"admin.undying\"", - "\tuncancel the event" - ) - .since("2.2-dev28"); 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 488e5549be4..88ba0491757 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,7 +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; /** @@ -68,8 +72,6 @@ public final boolean init(Literal[] args, int matchedPattern, ParseResult par EventData eventData = getParser().getData(EventData.class); - listeningBehavior = eventData.getListenerBehavior(); - EventPriority priority = eventData.getPriority(); if (priority != null && !isEventPrioritySupported()) { Skript.error("This event doesn't support event priority"); @@ -82,6 +84,27 @@ 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 = true; + for (Class eventClass : getEventClasses()) { + if (!Cancellable.class.isAssignableFrom(eventClass)) { + supportsListeningBehavior = false; + break; + } + } + + ListeningBehavior behavior = eventData.getListenerBehavior(); + // only set the listening behavior if it's not null, + // so children can know if the user has set a listening behavior or not + if (behavior != null) { + if (behavior == ListeningBehavior.CANCELLED && !isListeningBehaviorSupported()) { + String eventName = skriptEventInfo.name.toLowerCase(Locale.ENGLISH); + Skript.error(Utils.A(eventName) + " event cannot be cancelled, so listening for the cancelled event isn't allowed."); + return false; + } + listeningBehavior = behavior; + } + return init(args, matchedPattern, parseResult); } @@ -205,16 +228,24 @@ public boolean isEventPrioritySupported() { } /** - * @return the {@link ListeningBehavior} to be used for this event. + * @return the {@link ListeningBehavior} to be used for this event. If not set, the {@link ListeningBehavior#UNCANCELLED} is used. */ public ListeningBehavior getListeningBehavior() { - return listeningBehavior; + return listeningBehavior != null ? listeningBehavior : ListeningBehavior.UNCANCELLED; + } + + /** + * @return whether this SkriptEvent supports listening behaviors + */ + public boolean isListeningBehaviorSupported() { + return supportsListeningBehavior; } /** * @return whether this SkriptEvent should be run for the given cancelled state. */ public boolean matchesListeningBehavior(boolean isCancelled) { + ListeningBehavior listeningBehavior = getListeningBehavior(); return listeningBehavior == ListeningBehavior.ANY || (listeningBehavior == ListeningBehavior.UNCANCELLED && !isCancelled) || (listeningBehavior == ListeningBehavior.CANCELLED && isCancelled); diff --git a/src/main/java/ch/njol/skript/structures/StructEvent.java b/src/main/java/ch/njol/skript/structures/StructEvent.java index d3ed618f069..065bd317a6a 100644 --- a/src/main/java/ch/njol/skript/structures/StructEvent.java +++ b/src/main/java/ch/njol/skript/structures/StructEvent.java @@ -38,7 +38,7 @@ public class StructEvent extends Structure { static { Skript.registerStructure(StructEvent.class, - "[on] [uncancelled|:cancelled|any:(any|all)] <.+> [priority: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; @@ -49,9 +49,13 @@ public boolean init(Literal[] args, int matchedPattern, ParseResult parseResu String expr = parseResult.regexes.get(0).group(); EventData data = getParser().getData(EventData.class); + // clear old data values + data.behavior = null; + data.priority = null; - data.behavior = ListeningBehavior.UNCANCELLED; - if (parseResult.hasTag("cancelled")) { + 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; @@ -129,10 +133,11 @@ public EventPriority getPriority() { } /** - * @return the listening behavior that should be used for the event. Defaults to {@link ListeningBehavior#UNCANCELLED} + * @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 == null ? ListeningBehavior.UNCANCELLED : behavior; + return behavior; } } From 2b770e9ce56a7d34eea459d14ab3a4620677a86e Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Thu, 30 Nov 2023 19:18:13 -0800 Subject: [PATCH 04/13] revert incorrect java doc change --- src/main/java/ch/njol/skript/SkriptEventHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index ff70fcee2ab..f9430df2830 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -194,7 +194,7 @@ private static boolean isResultDeny(Event event) { private static long startEvent; /** - * Logs that the provided Event has started, using the default {@link EventPriority}. + * Logs that the provided Event has started. * Requires {@link Skript#logVeryHigh()} to be true to log anything. * @param event The Event that started. */ From 576d60ba45d726400335ae9cf20efe7f50fc4fec Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sat, 2 Dec 2023 20:39:18 -0800 Subject: [PATCH 05/13] Streamline setting default behavior --- .../ch/njol/skript/SkriptEventHandler.java | 4 +- .../ch/njol/skript/events/EvtResurrect.java | 60 ---------------- .../ch/njol/skript/events/SimpleEvents.java | 12 ++++ .../java/ch/njol/skript/lang/SkriptEvent.java | 36 ++++++---- .../ch/njol/skript/lang/SkriptEventInfo.java | 68 ++++++++++++------- 5 files changed, 80 insertions(+), 100 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/events/EvtResurrect.java diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index f9430df2830..a1b78b2df88 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -115,8 +115,6 @@ private static void check(Event event, EventPriority priority) { if (triggers.isEmpty()) return; - - // Check if this event should be treated as cancelled boolean isCancelled = isCancelled(event); @@ -133,7 +131,7 @@ private static void check(Event event, EventPriority priority) { continue; // check if the cancel state of the event is correct - if (triggerEvent.isListeningBehaviorSupported() && !triggerEvent.matchesListeningBehavior(isCancelled)) + if (!triggerEvent.getListeningBehavior().matches(isCancelled)) continue; // these methods need to be run on whatever thread the trigger is diff --git a/src/main/java/ch/njol/skript/events/EvtResurrect.java b/src/main/java/ch/njol/skript/events/EvtResurrect.java deleted file mode 100644 index e3da19eb53a..00000000000 --- a/src/main/java/ch/njol/skript/events/EvtResurrect.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * 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; -import ch.njol.skript.lang.Literal; -import ch.njol.skript.lang.SkriptEvent; -import ch.njol.skript.lang.SkriptParser; -import org.bukkit.event.Event; -import org.bukkit.event.entity.EntityResurrectEvent; -import org.eclipse.jdt.annotation.Nullable; - -public class EvtResurrect extends SkriptEvent { - static { - Skript.registerEvent("Resurrect Attempt", EvtResurrect.class, EntityResurrectEvent.class, "[entity] resurrect[ion] [attempt]") - .description("Called when an entity dies, always. If they are not holding a totem, the event will be cancelled - you can, however, uncancel it.") - .examples( - "on resurrect attempt:", - "\tentity is player", - "\tentity has permission \"admin.undying\"", - "\tuncancel the event" - ) - .since("2.2-dev28"); - } - - @Override - public boolean init(Literal[] args, int matchedPattern, SkriptParser.ParseResult parseResult) { - // if the user hasn't specified a listening behavior, we'll default to ANY - if (listeningBehavior == null) - listeningBehavior = ListeningBehavior.ANY; - return true; - } - - @Override - public boolean check(Event event) { - return true; - } - - @Override - public String toString(@Nullable Event event, boolean debug) { - return "resurrect attempt"; - } - -} diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index 77598a93eb0..135062a5347 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -19,6 +19,7 @@ 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; @@ -52,6 +53,7 @@ import org.bukkit.event.entity.EntityPortalEnterEvent; import org.bukkit.event.entity.EntityPortalEvent; import org.bukkit.event.entity.EntityRegainHealthEvent; +import org.bukkit.event.entity.EntityResurrectEvent; import org.bukkit.event.entity.EntityTameEvent; import org.bukkit.event.entity.EntityToggleGlideEvent; import org.bukkit.event.entity.EntityToggleSwimEvent; @@ -469,6 +471,16 @@ public class SimpleEvents { .description("Called when a slime splits. Usually this happens when a big slime dies.") .examples("on slime split:") .since("2.2-dev26"); + 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:", + "\tentity is player", + "\tentity has permission \"admin.undying\"", + "\tuncancel the event" + ) + .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 88ba0491757..ecb898ad83d 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEvent.java @@ -228,10 +228,11 @@ public boolean isEventPrioritySupported() { } /** - * @return the {@link ListeningBehavior} to be used for this event. If not set, the {@link ListeningBehavior#UNCANCELLED} is used. + * @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 : ListeningBehavior.UNCANCELLED; + return listeningBehavior != null ? listeningBehavior : skriptEventInfo.getListeningBehavior(); } /** @@ -241,16 +242,6 @@ public boolean isListeningBehaviorSupported() { return supportsListeningBehavior; } - /** - * @return whether this SkriptEvent should be run for the given cancelled state. - */ - public boolean matchesListeningBehavior(boolean isCancelled) { - ListeningBehavior listeningBehavior = getListeningBehavior(); - return listeningBehavior == ListeningBehavior.ANY - || (listeningBehavior == ListeningBehavior.UNCANCELLED && !isCancelled) - || (listeningBehavior == ListeningBehavior.CANCELLED && isCancelled); - } - /** * Override this method to allow Skript to not force synchronization. */ @@ -308,7 +299,26 @@ public enum ListeningBehavior { /** * This Skript event should be run for any event, cancelled or uncancelled. */ - ANY + 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 a437bd6e32b..b4aa9b3e998 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java @@ -18,14 +18,14 @@ */ package ch.njol.skript.lang; -import java.util.Locale; - -import org.skriptlang.skript.lang.structure.StructureInfo; +import ch.njol.skript.SkriptAPIException; import org.bukkit.event.Event; 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 ch.njol.skript.SkriptAPIException; +import java.util.Locale; public final class SkriptEventInfo extends StructureInfo { @@ -33,6 +33,8 @@ public final class SkriptEventInfo extends StructureInfo< public final String name; private final String id; + + private ListeningBehavior listeningBehavior; @Nullable private String[] description; @@ -49,16 +51,16 @@ public final class SkriptEventInfo extends StructureInfo< /** * @param name Capitalised name of the event without leading "On" which is added automatically (Start the name with an asterisk to prevent this). - * @param patterns - * @param c The SkriptEvent's class + * @param patterns the skript patterns for this event + * @param skriptEventClass The SkriptEvent's class * @param originClassPath The class path for the origin of this event. * @param events The Bukkit-Events this SkriptEvent listens to */ - public SkriptEventInfo(String name, final String[] patterns, final Class c, final String originClassPath, final Class[] events) { - super(patterns, c, originClassPath); + public SkriptEventInfo(String name, String[] patterns, Class skriptEventClass, String originClassPath, final Class[] events) { + super(patterns, skriptEventClass, originClassPath); assert name != null; assert patterns != null && patterns.length > 0; - assert c != null; + assert skriptEventClass != null; assert originClassPath != null; assert events != null && events.length > 0; @@ -69,7 +71,7 @@ public SkriptEventInfo(String name, final String[] patterns, final Class c, f || events[j].equals(PlayerInteractAtEntityEvent.class)) continue; // Spigot seems to have an exception for those two events... - throw new SkriptAPIException("The event " + name + " (" + c.getName() + ") registers with super/subclasses " + events[i].getName() + " and " + events[j].getName()); + throw new SkriptAPIException("The event " + name + " (" + skriptEventClass.getName() + ") registers with super/subclasses " + events[i].getName() + " and " + events[j].getName()); } } } @@ -84,8 +86,22 @@ public SkriptEventInfo(String name, final String[] patterns, final Class c, f // 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; + } + /** * Use this as {@link #description(String...)} to prevent warnings about missing documentation. */ @@ -94,10 +110,10 @@ public SkriptEventInfo(String name, final String[] patterns, final Class c, f /** * Only used for Skript's documentation. * - * @param description + * @param description The description of this SkriptEvent for Skript's documentation. * @return This SkriptEventInfo object */ - public SkriptEventInfo description(final String... description) { + public SkriptEventInfo description(String... description) { assert this.description == null; this.description = description; return this; @@ -106,10 +122,10 @@ public SkriptEventInfo description(final String... description) { /** * Only used for Skript's documentation. * - * @param examples + * @param examples The examples of this SkriptEvent for Skript's documentation. * @return This SkriptEventInfo object */ - public SkriptEventInfo examples(final String... examples) { + public SkriptEventInfo examples(String... examples) { assert this.examples == null; this.examples = examples; return this; @@ -118,10 +134,10 @@ public SkriptEventInfo examples(final String... examples) { /** * Only used for Skript's documentation. * - * @param keywords + * @param keywords The keywords of this SkriptEvent for searching in Skript's documentation. * @return This SkriptEventInfo object */ - public SkriptEventInfo keywords(final String... keywords) { + public SkriptEventInfo keywords(String... keywords) { assert this.keywords == null; this.keywords = keywords; return this; @@ -130,10 +146,10 @@ public SkriptEventInfo keywords(final String... keywords) { /** * Only used for Skript's documentation. * - * @param since + * @param since The version of Skript this SkriptEvent was added. * @return This SkriptEventInfo object */ - public SkriptEventInfo since(final String since) { + public SkriptEventInfo since(String since) { assert this.since == null; this.since = since; return this; @@ -144,10 +160,10 @@ public SkriptEventInfo since(final String since) { * * Only used for Skript's documentation. * - * @param id + * @param id The ID to use for this SkriptEvent within documentation. * @return This SkriptEventInfo object */ - public SkriptEventInfo documentationID(final String id) { + public SkriptEventInfo documentationID(String id) { assert this.documentationID == null; this.documentationID = id; return this; @@ -158,16 +174,16 @@ public SkriptEventInfo documentationID(final String id) { * * Only used for Skript's documentation. * - * @param pluginNames + * @param pluginNames The names of the plugins this SkriptEvent depends on. * @return This SkriptEventInfo object */ - public SkriptEventInfo requiredPlugins(final String... pluginNames) { + public SkriptEventInfo requiredPlugins(String... pluginNames) { assert this.requiredPlugins == null; this.requiredPlugins = pluginNames; return this; } - + public String getId() { return id; } @@ -175,6 +191,10 @@ public String getId() { public String getName() { return name; } + + public ListeningBehavior getListeningBehavior() { + return listeningBehavior; + } @Nullable public String[] getDescription() { From fb4281cfe2327e3b54f857434998f111e1788c2d Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sat, 2 Dec 2023 20:42:00 -0800 Subject: [PATCH 06/13] indentation --- .../ch/njol/skript/events/SimpleEvents.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/ch/njol/skript/events/SimpleEvents.java b/src/main/java/ch/njol/skript/events/SimpleEvents.java index 135062a5347..a6e315ebb22 100644 --- a/src/main/java/ch/njol/skript/events/SimpleEvents.java +++ b/src/main/java/ch/njol/skript/events/SimpleEvents.java @@ -472,15 +472,15 @@ public class SimpleEvents { .examples("on slime split:") .since("2.2-dev26"); 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:", - "\tentity is player", - "\tentity has permission \"admin.undying\"", - "\tuncancel the event" - ) - .since("2.2-dev28") - .listeningBehavior(ListeningBehavior.ANY); + .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:", + "\tentity is player", + "\tentity has permission \"admin.undying\"", + "\tuncancel the event" + ) + .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:", From 222d2eea5823987d77d047bac039f73c6924ece5 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sat, 2 Dec 2023 21:24:03 -0800 Subject: [PATCH 07/13] Prevent any sort of listening behavior on events that can't be cancelled. Reduce local complexity in SkriptEventHandler --- .../ch/njol/skript/SkriptEventHandler.java | 56 +++++++++++-------- .../java/ch/njol/skript/lang/SkriptEvent.java | 24 ++++---- 2 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index a1b78b2df88..666c1484ee8 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -134,28 +134,8 @@ private static void check(Event event, EventPriority priority) { if (!triggerEvent.getListeningBehavior().matches(isCancelled)) 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()) { - // check should be performed on the main thread - if (Boolean.FALSE.equals(Task.callSync(() -> triggerEvent.check(event)))) - continue; - execute.run(); - } else { // Ensure main thread - Task.callSync(() -> { - if (!triggerEvent.check(event)) - return null; - execute.run(); - return null; // we don't care about a return value - }); - } + // execute the trigger + execute(trigger, event); } logEventEnd(); @@ -189,6 +169,38 @@ private static boolean isResultDeny(Event event) { ((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()) { + // check should be performed on the main thread + if (Boolean.FALSE.equals(Task.callSync(() -> trigger.getEvent().check(event)))) + return; + execute.run(); + } else { // Ensure main thread + Task.callSync(() -> { + if (!trigger.getEvent().check(event)) + return null; + execute.run(); + return null; + }); + } + } + + private static long startEvent; /** diff --git a/src/main/java/ch/njol/skript/lang/SkriptEvent.java b/src/main/java/ch/njol/skript/lang/SkriptEvent.java index ecb898ad83d..4ba0fde36e9 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEvent.java @@ -93,16 +93,12 @@ public final boolean init(Literal[] args, int matchedPattern, ParseResult par } } - ListeningBehavior behavior = eventData.getListenerBehavior(); - // only set the listening behavior if it's not null, - // so children can know if the user has set a listening behavior or not - if (behavior != null) { - if (behavior == ListeningBehavior.CANCELLED && !isListeningBehaviorSupported()) { - String eventName = skriptEventInfo.name.toLowerCase(Locale.ENGLISH); - Skript.error(Utils.A(eventName) + " event cannot be cancelled, so listening for the cancelled event isn't allowed."); - return false; - } - listeningBehavior = behavior; + 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); @@ -285,19 +281,19 @@ public static SkriptEvent parse(String expr, SectionNode sectionNode, @Nullable } /** - * The listening behavior of a Skript event. This determines whether the event should be run for cancelled events, uncancelled events, or both. + * 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 be run for any uncancelled event. + * This Skript event should run for any uncancelled event. */ UNCANCELLED, /** - * This Skript event should be run for any cancelled event. + * This Skript event should run for any cancelled event. */ CANCELLED, /** - * This Skript event should be run for any event, cancelled or uncancelled. + * This Skript event should run for any event, cancelled or uncancelled. */ ANY; From 2476d7246ba7a04be3ee18b29022d3319d820e30 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Sat, 16 Dec 2023 22:05:51 -0800 Subject: [PATCH 08/13] Fix merge mistake --- .../ch/njol/skript/SkriptEventHandler.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index ac7eb6d5960..288b40f4147 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -185,16 +185,16 @@ private static void execute(Trigger trigger, Event event) { logTriggerEnd(trigger); }; - if (trigger.getEvent().canExecuteAsynchronously()) { - if (triggerEvent.check(event)) + if (trigger.getEvent().canExecuteAsynchronously()) { + if (trigger.getEvent().check(event)) + execute.run(); + } else { // Ensure main thread + Task.callSync(() -> { + if (trigger.getEvent().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 - }); - } + return null; // we don't care about a return value + }); + } } From 9bcaeff7ffedcbec33e9f1c3d788d1aeb200f4c7 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Mon, 18 Dec 2023 11:09:37 -0800 Subject: [PATCH 09/13] Apply suggestions from code review Co-authored-by: Patrick Miller --- src/main/java/ch/njol/skript/lang/SkriptEvent.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/ch/njol/skript/lang/SkriptEvent.java b/src/main/java/ch/njol/skript/lang/SkriptEvent.java index 4ba0fde36e9..b2b0065c8fa 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEvent.java @@ -284,14 +284,17 @@ public static SkriptEvent parse(String expr, SectionNode sectionNode, @Nullable * 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. */ From c6106388fdb38b2016e1d516a7d9216b3083e5d9 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Wed, 27 Dec 2023 15:49:31 -0800 Subject: [PATCH 10/13] Requested Changes --- src/main/java/ch/njol/skript/lang/SkriptEvent.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/lang/SkriptEvent.java b/src/main/java/ch/njol/skript/lang/SkriptEvent.java index b2b0065c8fa..03173d626fb 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptEvent.java +++ b/src/main/java/ch/njol/skript/lang/SkriptEvent.java @@ -85,10 +85,10 @@ public final boolean init(Literal[] args, int matchedPattern, ParseResult par skriptEventInfo = (SkriptEventInfo) syntaxElementInfo; // evaluate whether this event supports listening to cancelled events - supportsListeningBehavior = true; + supportsListeningBehavior = false; for (Class eventClass : getEventClasses()) { - if (!Cancellable.class.isAssignableFrom(eventClass)) { - supportsListeningBehavior = false; + if (Cancellable.class.isAssignableFrom(eventClass)) { + supportsListeningBehavior = true; break; } } From 30ccdad17d0079db12f156bacadce31edff891d9 Mon Sep 17 00:00:00 2001 From: sovdee <10354869+sovdeeth@users.noreply.github.com> Date: Fri, 29 Dec 2023 23:09:30 -0800 Subject: [PATCH 11/13] Cancelled Event Test --- .../test/tests/lang/CancelledEventsTest.java | 51 +++++++++++++++++++ .../skript/test/tests/lang/package-info.java | 24 +++++++++ src/test/skript/junit/CancelledEventTest.sk | 32 ++++++++++++ 3 files changed, 107 insertions(+) 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/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} From 0ed7f6e18d9083e05a8c04506ed4dbe107cf607c Mon Sep 17 00:00:00 2001 From: sovde <10354869+sovdeeth@users.noreply.github.com> Date: Thu, 14 Mar 2024 22:41:12 +0100 Subject: [PATCH 12/13] Fix merge mistakes I need to stop using the browser for merge conflicts --- src/main/java/ch/njol/skript/structures/StructEvent.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/structures/StructEvent.java b/src/main/java/ch/njol/skript/structures/StructEvent.java index 2788460f08b..814571463ad 100644 --- a/src/main/java/ch/njol/skript/structures/StructEvent.java +++ b/src/main/java/ch/njol/skript/structures/StructEvent.java @@ -140,13 +140,14 @@ public EventPriority getPriority() { @Nullable public ListeningBehavior getListenerBehavior() { return behavior; + } - /** + /** * Clears all event-specific data from this instance. */ public void clear() { priority = null; - behavior = null; + behavior = null; } } From 031ed26a2fe30d333f67717f63145e3842f08d52 Mon Sep 17 00:00:00 2001 From: sovde <10354869+sovdeeth@users.noreply.github.com> Date: Sun, 7 Apr 2024 13:13:50 +0200 Subject: [PATCH 13/13] Update SkriptEventHandler.java --- src/main/java/ch/njol/skript/SkriptEventHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/njol/skript/SkriptEventHandler.java b/src/main/java/ch/njol/skript/SkriptEventHandler.java index 288b40f4147..30954ba5cf9 100644 --- a/src/main/java/ch/njol/skript/SkriptEventHandler.java +++ b/src/main/java/ch/njol/skript/SkriptEventHandler.java @@ -150,7 +150,7 @@ private static void check(Event event, EventPriority priority) { private static boolean isCancelled(Event event) { return event instanceof Cancellable && (((Cancellable) event).isCancelled() && isResultDeny(event)) && - // listenCancelled is deprecated and should be removed in 2.9 + // TODO: listenCancelled is deprecated and should be removed in 2.10 !listenCancelled.contains(event.getClass()); }