Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds ability to listen to cancelled events #6207

Merged
merged 18 commits into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 111 additions & 64 deletions src/main/java/ch/njol/skript/SkriptEventHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

Expand Down Expand Up @@ -112,66 +110,94 @@ private static List<Trigger> getTriggers(Class<? extends Event> 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<Trigger> 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;

/**
Expand All @@ -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 + " ==");
}

/**
Expand Down Expand Up @@ -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<Class<? extends Event>> listenCancelled = new HashSet<>();

/**
Expand Down
27 changes: 13 additions & 14 deletions src/main/java/ch/njol/skript/events/SimpleEvents.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -453,13 +452,13 @@ public class SimpleEvents {
Skript.registerEvent("Resurrect Attempt", SimpleEvent.class, EntityResurrectEvent.class, "[entity] resurrect[ion] [attempt]")
sovdeeth marked this conversation as resolved.
Show resolved Hide resolved
.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:",
Expand Down
Loading
Loading