From 0bcebefdb0c4d5e1472918075b15723dadb9fe89 Mon Sep 17 00:00:00 2001 From: 1wair <1wairesd.com.industries@gmail.com> Date: Wed, 5 Aug 2026 23:53:15 +0200 Subject: [PATCH] feat: add region lifecycle events (add, delete, redefine, flag, member, priority) Add six cancellable Bukkit events that fire before key region operations, allowing third-party plugins to intercept and cancel them cleanly without resorting to reflection hacks or command interception. New events in worldguard-bukkit/.../event/region/: - RegionAddEvent - fired before /rg define - RegionDeleteEvent - fired before /rg remove - RegionRedefineEvent - fired before /rg redefine - RegionFlagChangeEvent - fired before /rg flag (set or clear) - RegionMemberChangeEvent - fired before addmember/addowner/removemember/removeowner (async) - RegionPriorityChangeEvent - fired before /rg setpriority All events carry: - World - the world the region belongs to - ProtectedRegion - the affected region - CommandSender (nullable) - the actor who triggered the operation, or null for API calls - setCancelled(true) - cancels the operation - setCancelMessage(String) - optional custom feedback message Architecture: - Six default methods added to WorldGuardPlatform (return true by default, so no existing platform implementations break) - BukkitWorldGuardPlatform overrides all six, adapts WE World to Bukkit World, fires the event via Bukkit.getPluginManager().callEvent() - RegionCommands wired for define/redefine/remove/flag/setPriority - MemberCommands wired for addMember/addOwner/removeMember/removeOwner; success message is only sent when the event is not cancelled This eliminates the need for plugins to use reflection into RegionManager internals or intercept raw commands in order to protect regions from deletion. --- .../bukkit/BukkitWorldGuardPlatform.java | 83 ++++++++ .../bukkit/event/region/RegionAddEvent.java | 155 ++++++++++++++ .../event/region/RegionDeleteEvent.java | 170 ++++++++++++++++ .../event/region/RegionFlagChangeEvent.java | 189 ++++++++++++++++++ .../event/region/RegionMemberChangeEvent.java | 172 ++++++++++++++++ .../region/RegionPriorityChangeEvent.java | 162 +++++++++++++++ .../event/region/RegionRedefineEvent.java | 167 ++++++++++++++++ .../commands/region/MemberCommands.java | 40 +++- .../commands/region/RegionCommands.java | 32 +++ .../internal/platform/WorldGuardPlatform.java | 103 ++++++++++ 10 files changed, 1269 insertions(+), 4 deletions(-) create mode 100644 worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionAddEvent.java create mode 100644 worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionDeleteEvent.java create mode 100644 worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionFlagChangeEvent.java create mode 100644 worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionMemberChangeEvent.java create mode 100644 worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionPriorityChangeEvent.java create mode 100644 worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionRedefineEvent.java diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitWorldGuardPlatform.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitWorldGuardPlatform.java index f1e70e051..ce6bdf901 100644 --- a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitWorldGuardPlatform.java +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitWorldGuardPlatform.java @@ -29,6 +29,14 @@ import com.sk89q.worldedit.world.gamemode.GameModes; import com.sk89q.worldguard.LocalPlayer; import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.event.region.RegionAddEvent; +import com.sk89q.worldguard.bukkit.event.region.RegionDeleteEvent; +import com.sk89q.worldguard.bukkit.event.region.RegionFlagChangeEvent; +import com.sk89q.worldguard.bukkit.event.region.RegionMemberChangeEvent; +import com.sk89q.worldguard.bukkit.event.region.RegionPriorityChangeEvent; +import com.sk89q.worldguard.bukkit.event.region.RegionRedefineEvent; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.managers.RemovalStrategy; import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; import com.sk89q.worldguard.protection.regions.ProtectedRegion; import com.sk89q.worldguard.util.profile.resolver.PaperPlayerService; @@ -55,6 +63,7 @@ import com.sk89q.worldguard.util.profile.resolver.ProfileService; import io.papermc.lib.PaperLib; import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.permissions.Permissible; @@ -274,4 +283,78 @@ public ProtectedRegion getSpawnProtection(World world) { } return null; } + + @Override + public boolean callRegionAddEvent(World world, ProtectedRegion region, @Nullable Object actor) { + if (!(world instanceof BukkitWorld)) return true; + org.bukkit.World bWorld = ((BukkitWorld) world).getWorld(); + RegionAddEvent event = new RegionAddEvent(bWorld, region, actor instanceof CommandSender ? (CommandSender) actor : null); + Bukkit.getPluginManager().callEvent(event); + return !event.isCancelled(); + } + + @Override + public boolean callRegionDeleteEvent(World world, ProtectedRegion region, + RemovalStrategy removalStrategy, @Nullable Object actor) { + if (!(world instanceof BukkitWorld)) return true; + org.bukkit.World bWorld = ((BukkitWorld) world).getWorld(); + RegionDeleteEvent event = new RegionDeleteEvent(bWorld, region, removalStrategy, + actor instanceof CommandSender ? (CommandSender) actor : null); + Bukkit.getPluginManager().callEvent(event); + return !event.isCancelled(); + } + + @Override + public boolean callRegionRedefineEvent(World world, ProtectedRegion oldRegion, + ProtectedRegion newRegion, @Nullable Object actor) { + if (!(world instanceof BukkitWorld)) return true; + org.bukkit.World bWorld = ((BukkitWorld) world).getWorld(); + RegionRedefineEvent event = new RegionRedefineEvent(bWorld, oldRegion, newRegion, + actor instanceof CommandSender ? (CommandSender) actor : null); + Bukkit.getPluginManager().callEvent(event); + return !event.isCancelled(); + } + + @Override + public boolean callRegionFlagChangeEvent(World world, ProtectedRegion region, Flag flag, + @Nullable Object newValue, @Nullable Object actor) { + if (!(world instanceof BukkitWorld)) return true; + org.bukkit.World bWorld = ((BukkitWorld) world).getWorld(); + RegionFlagChangeEvent event = new RegionFlagChangeEvent(bWorld, region, flag, newValue, + actor instanceof CommandSender ? (CommandSender) actor : null); + Bukkit.getPluginManager().callEvent(event); + return !event.isCancelled(); + } + + @Override + public boolean callRegionMemberChangeEvent(World world, ProtectedRegion region, String changeType, + @Nullable Object domain, @Nullable Object actor) { + if (!(world instanceof BukkitWorld)) return true; + org.bukkit.World bWorld = ((BukkitWorld) world).getWorld(); + com.sk89q.worldguard.domains.DefaultDomain dd = + domain instanceof com.sk89q.worldguard.domains.DefaultDomain + ? (com.sk89q.worldguard.domains.DefaultDomain) domain + : new com.sk89q.worldguard.domains.DefaultDomain(); + RegionMemberChangeEvent.ChangeType ct; + try { + ct = RegionMemberChangeEvent.ChangeType.valueOf(changeType); + } catch (IllegalArgumentException e) { + return true; + } + RegionMemberChangeEvent event = new RegionMemberChangeEvent(bWorld, region, ct, dd, + actor instanceof CommandSender ? (CommandSender) actor : null); + Bukkit.getPluginManager().callEvent(event); + return !event.isCancelled(); + } + + @Override + public boolean callRegionPriorityChangeEvent(World world, ProtectedRegion region, int oldPriority, + int newPriority, @Nullable Object actor) { + if (!(world instanceof BukkitWorld)) return true; + org.bukkit.World bWorld = ((BukkitWorld) world).getWorld(); + RegionPriorityChangeEvent event = new RegionPriorityChangeEvent(bWorld, region, oldPriority, newPriority, + actor instanceof CommandSender ? (CommandSender) actor : null); + Bukkit.getPluginManager().callEvent(event); + return !event.isCancelled(); + } } diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionAddEvent.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionAddEvent.java new file mode 100644 index 000000000..d447f6ea8 --- /dev/null +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionAddEvent.java @@ -0,0 +1,155 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.region; + +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.bukkit.command.CommandSender; +import org.bukkit.World; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Called when a {@link ProtectedRegion} is about to be added (defined) in a + * {@link com.sk89q.worldguard.protection.managers.RegionManager}. + * + *

Cancelling this event prevents the region from being created. The command + * or API call that initiated the addition will receive an error message.

+ * + *

This event is fired on the main server thread immediately before the + * region is added to the index. It is not fired when regions + * are loaded from storage on world load.

+ * + *

Example usage — prevent regions with a specific prefix from being created:

+ *
{@code
+ * @EventHandler
+ * public void onRegionAdd(RegionAddEvent event) {
+ *     if (event.getRegion().getId().startsWith("reserved_")) {
+ *         event.setCancelled(true);
+ *         event.setCancelMessage("Region names starting with 'reserved_' are not allowed.");
+ *     }
+ * }
+ * }
+ */ +public class RegionAddEvent extends Event implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + private final World world; + private final ProtectedRegion region; + @Nullable + private final CommandSender actor; + + private boolean cancelled = false; + @Nullable + private String cancelMessage; + + /** + * Create a new instance. + * + * @param world the world the region is being added to + * @param region the region that is about to be added + * @param actor the command sender who initiated the action, or {@code null} if triggered via API + */ + public RegionAddEvent(World world, ProtectedRegion region, @Nullable CommandSender actor) { + checkNotNull(world); + checkNotNull(region); + this.world = world; + this.region = region; + this.actor = actor; + } + + /** + * Get the world in which the region is being defined. + * + * @return the world + */ + public World getWorld() { + return world; + } + + /** + * Get the region that is about to be added. + * + * @return the region + */ + public ProtectedRegion getRegion() { + return region; + } + + /** + * Get the command sender who initiated this action. + * + *

Returns {@code null} when the region is created through a direct API call + * rather than through a player or console command.

+ * + * @return the actor, or {@code null} if not applicable + */ + @Nullable + public CommandSender getActor() { + return actor; + } + + /** + * Get the optional message to send back to the command sender when the + * event is cancelled. + * + *

If {@code null}, WorldGuard will use a generic cancellation message.

+ * + * @return the cancel message, or {@code null} if none is set + */ + @Nullable + public String getCancelMessage() { + return cancelMessage; + } + + /** + * Set a custom message to send to the command sender when this event is + * cancelled. Set to {@code null} to use WorldGuard's default message. + * + * @param cancelMessage the message, or {@code null} + */ + public void setCancelMessage(@Nullable String cancelMessage) { + this.cancelMessage = cancelMessage; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionDeleteEvent.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionDeleteEvent.java new file mode 100644 index 000000000..292fcbe73 --- /dev/null +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionDeleteEvent.java @@ -0,0 +1,170 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.region; + +import com.sk89q.worldguard.protection.managers.RemovalStrategy; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.bukkit.command.CommandSender; +import org.bukkit.World; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Called when a {@link ProtectedRegion} is about to be removed from a + * {@link com.sk89q.worldguard.protection.managers.RegionManager}. + * + *

Cancelling this event prevents the region (and any affected children, + * depending on the {@link RemovalStrategy}) from being deleted. The command + * or API call that initiated the removal will receive an error message.

+ * + *

This event is fired on the main server thread immediately before the + * removal is executed. It is not fired for transient regions.

+ * + *

Example usage — protect a region from deletion:

+ *
{@code
+ * @EventHandler
+ * public void onRegionDelete(RegionDeleteEvent event) {
+ *     if (isProtected(event.getRegion().getId())) {
+ *         event.setCancelled(true);
+ *         event.setCancelMessage("This region is protected and cannot be deleted.");
+ *     }
+ * }
+ * }
+ */ +public class RegionDeleteEvent extends Event implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + private final World world; + private final ProtectedRegion region; + private final RemovalStrategy removalStrategy; + + private boolean cancelled = false; + @Nullable + private String cancelMessage; + @Nullable + private final CommandSender actor; + + /** + * Create a new instance. + * + * @param world the world the region belongs to + * @param region the region that is about to be removed + * @param removalStrategy the strategy that will be used for child regions + * @param actor the command sender who initiated the action, or {@code null} if triggered via API + */ + public RegionDeleteEvent(World world, ProtectedRegion region, RemovalStrategy removalStrategy, + @Nullable CommandSender actor) { + checkNotNull(world); + checkNotNull(region); + checkNotNull(removalStrategy); + this.world = world; + this.region = region; + this.removalStrategy = removalStrategy; + this.actor = actor; + } + + /** + * Get the world in which the region resides. + * + * @return the world + */ + public World getWorld() { + return world; + } + + /** + * Get the region that is about to be deleted. + * + * @return the region + */ + public ProtectedRegion getRegion() { + return region; + } + + /** + * Get the removal strategy that will be applied to child regions. + * + * @return the removal strategy + */ + public RemovalStrategy getRemovalStrategy() { + return removalStrategy; + } + + /** + * Get the command sender who initiated this action. + * + *

Returns {@code null} when the region is removed through a direct API call + * rather than through a player or console command.

+ * + * @return the actor, or {@code null} if not applicable + */ + @Nullable + public CommandSender getActor() { + return actor; + } + + /** + * Get the optional message to send back to the command sender when the + * event is cancelled. + * + *

If {@code null}, WorldGuard will use a generic cancellation message.

+ * + * @return the cancel message, or {@code null} if none is set + */ + @Nullable + public String getCancelMessage() { + return cancelMessage; + } + + /** + * Set a custom message to send to the command sender when this event is + * cancelled. Set to {@code null} to use WorldGuard's default message. + * + * @param cancelMessage the message, or {@code null} + */ + public void setCancelMessage(@Nullable String cancelMessage) { + this.cancelMessage = cancelMessage; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionFlagChangeEvent.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionFlagChangeEvent.java new file mode 100644 index 000000000..9b421b393 --- /dev/null +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionFlagChangeEvent.java @@ -0,0 +1,189 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.region; + +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Called when a {@link Flag} on a {@link ProtectedRegion} is about to be + * set or cleared via the {@code /rg flag} command or equivalent API. + * + *

Cancelling this event prevents the flag change from being applied.

+ * + *

This event is fired on the main server thread immediately before the + * flag is written to the region.

+ * + *

Example usage — log all flag changes:

+ *
{@code
+ * @EventHandler
+ * public void onFlagChange(RegionFlagChangeEvent event) {
+ *     String val = event.getNewValue() != null ? event.getNewValue().toString() : "(cleared)";
+ *     Bukkit.getLogger().info("Flag " + event.getFlag().getName()
+ *             + " on " + event.getRegion().getId() + " changed to " + val);
+ * }
+ * }
+ */ +public class RegionFlagChangeEvent extends Event implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + private final World world; + private final ProtectedRegion region; + private final Flag flag; + @Nullable + private final Object newValue; + @Nullable + private final CommandSender actor; + + private boolean cancelled = false; + @Nullable + private String cancelMessage; + + /** + * Create a new instance. + * + * @param world the world the region belongs to + * @param region the region being modified + * @param flag the flag being changed + * @param newValue the new value being set, or {@code null} if the flag is being cleared + * @param actor the command sender who initiated the action, or {@code null} if via API + */ + public RegionFlagChangeEvent(World world, ProtectedRegion region, Flag flag, + @Nullable Object newValue, @Nullable CommandSender actor) { + checkNotNull(world); + checkNotNull(region); + checkNotNull(flag); + this.world = world; + this.region = region; + this.flag = flag; + this.newValue = newValue; + this.actor = actor; + } + + /** + * Get the world in which the region resides. + * + * @return the world + */ + public World getWorld() { + return world; + } + + /** + * Get the region whose flag is being changed. + * + * @return the region + */ + public ProtectedRegion getRegion() { + return region; + } + + /** + * Get the flag being changed. + * + * @return the flag + */ + public Flag getFlag() { + return flag; + } + + /** + * Get the new value the flag is being set to. + * + *

Returns {@code null} if the flag is being cleared (removed + * from the region so it falls back to the default or parent value).

+ * + * @return the new value, or {@code null} if the flag is being cleared + */ + @Nullable + public Object getNewValue() { + return newValue; + } + + /** + * Returns {@code true} if the flag is being cleared rather than set to + * a new value. + * + * @return true if the flag is being cleared + */ + public boolean isClearing() { + return newValue == null; + } + + /** + * Get the command sender who initiated this action. + * + *

Returns {@code null} when triggered through a direct API call.

+ * + * @return the actor, or {@code null} if not applicable + */ + @Nullable + public CommandSender getActor() { + return actor; + } + + /** + * Get the optional cancel message to send to the actor. + * + * @return the cancel message, or {@code null} + */ + @Nullable + public String getCancelMessage() { + return cancelMessage; + } + + /** + * Set a custom message to send to the actor when this event is cancelled. + * + * @param cancelMessage the message, or {@code null} to use the default + */ + public void setCancelMessage(@Nullable String cancelMessage) { + this.cancelMessage = cancelMessage; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionMemberChangeEvent.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionMemberChangeEvent.java new file mode 100644 index 000000000..c305da163 --- /dev/null +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionMemberChangeEvent.java @@ -0,0 +1,172 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.region; + +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Called when the members or owners of a {@link ProtectedRegion} are about + * to change — either added or removed via command or API. + * + *

Cancelling this event prevents the domain change from being applied.

+ * + *

This event is fired on an async worker thread (because UUID resolution + * is async), so listeners must be thread-safe. Use + * {@code @EventHandler(ignoreCancelled = true)} and avoid calling + * Bukkit API methods that require the main thread.

+ * + *

Example usage — prevent adding owners to a frozen region:

+ *
{@code
+ * @EventHandler
+ * public void onMemberChange(RegionMemberChangeEvent event) {
+ *     if (event.getChangeType() == RegionMemberChangeEvent.ChangeType.ADD_OWNER
+ *             && isFrozen(event.getRegion().getId())) {
+ *         event.setCancelled(true);
+ *     }
+ * }
+ * }
+ */ +public class RegionMemberChangeEvent extends Event implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + /** + * The type of domain change being made. + */ + public enum ChangeType { + /** A player or group is being added as a member. */ + ADD_MEMBER, + /** A player or group is being removed from members. */ + REMOVE_MEMBER, + /** A player or group is being added as an owner. */ + ADD_OWNER, + /** A player or group is being removed from owners. */ + REMOVE_OWNER + } + + private final World world; + private final ProtectedRegion region; + private final ChangeType changeType; + private final DefaultDomain domain; + @Nullable + private final CommandSender actor; + + private boolean cancelled = false; + + /** + * Create a new instance. + * + * @param world the world the region belongs to + * @param region the region being modified + * @param changeType the type of change being made + * @param domain the domain entries being added or removed + * @param actor the command sender who initiated the action, or {@code null} if via API + */ + public RegionMemberChangeEvent(World world, ProtectedRegion region, ChangeType changeType, + DefaultDomain domain, @Nullable CommandSender actor) { + super(true); // async = true — UUID resolution happens off-thread + checkNotNull(world); + checkNotNull(region); + checkNotNull(changeType); + checkNotNull(domain); + this.world = world; + this.region = region; + this.changeType = changeType; + this.domain = domain; + this.actor = actor; + } + + /** + * Get the world in which the region resides. + * + * @return the world + */ + public World getWorld() { + return world; + } + + /** + * Get the region whose domain is being modified. + * + * @return the region + */ + public ProtectedRegion getRegion() { + return region; + } + + /** + * Get the type of change being made (add/remove, member/owner). + * + * @return the change type + */ + public ChangeType getChangeType() { + return changeType; + } + + /** + * Get the domain entries that are being added or removed. + * + * @return the affected domain + */ + public DefaultDomain getDomain() { + return domain; + } + + /** + * Get the command sender who initiated this action. + * + *

Returns {@code null} when triggered through a direct API call.

+ * + * @return the actor, or {@code null} if not applicable + */ + @Nullable + public CommandSender getActor() { + return actor; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionPriorityChangeEvent.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionPriorityChangeEvent.java new file mode 100644 index 000000000..4a581095a --- /dev/null +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionPriorityChangeEvent.java @@ -0,0 +1,162 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.region; + +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Called when the priority of a {@link ProtectedRegion} is about to be + * changed via the {@code /rg setpriority} command or equivalent API. + * + *

Cancelling this event prevents the priority from being changed.

+ * + *

This event is fired on the main server thread immediately before the + * priority is written to the region.

+ */ +public class RegionPriorityChangeEvent extends Event implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + private final World world; + private final ProtectedRegion region; + private final int oldPriority; + private final int newPriority; + @Nullable + private final CommandSender actor; + + private boolean cancelled = false; + @Nullable + private String cancelMessage; + + /** + * Create a new instance. + * + * @param world the world the region belongs to + * @param region the region being modified + * @param oldPriority the current priority before the change + * @param newPriority the new priority being set + * @param actor the command sender who initiated the action, or {@code null} if via API + */ + public RegionPriorityChangeEvent(World world, ProtectedRegion region, int oldPriority, + int newPriority, @Nullable CommandSender actor) { + checkNotNull(world); + checkNotNull(region); + this.world = world; + this.region = region; + this.oldPriority = oldPriority; + this.newPriority = newPriority; + this.actor = actor; + } + + /** + * Get the world in which the region resides. + * + * @return the world + */ + public World getWorld() { + return world; + } + + /** + * Get the region whose priority is being changed. + * + * @return the region + */ + public ProtectedRegion getRegion() { + return region; + } + + /** + * Get the current priority of the region (before the change). + * + * @return the old priority + */ + public int getOldPriority() { + return oldPriority; + } + + /** + * Get the new priority being applied. + * + * @return the new priority + */ + public int getNewPriority() { + return newPriority; + } + + /** + * Get the command sender who initiated this action. + * + *

Returns {@code null} when triggered through a direct API call.

+ * + * @return the actor, or {@code null} if not applicable + */ + @Nullable + public CommandSender getActor() { + return actor; + } + + /** + * Get the optional cancel message to send to the actor. + * + * @return the cancel message, or {@code null} + */ + @Nullable + public String getCancelMessage() { + return cancelMessage; + } + + /** + * Set a custom message to send to the actor when this event is cancelled. + * + * @param cancelMessage the message, or {@code null} to use the default + */ + public void setCancelMessage(@Nullable String cancelMessage) { + this.cancelMessage = cancelMessage; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionRedefineEvent.java b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionRedefineEvent.java new file mode 100644 index 000000000..c72297025 --- /dev/null +++ b/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/region/RegionRedefineEvent.java @@ -0,0 +1,167 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.region; + +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Called when a {@link ProtectedRegion} is about to have its boundaries + * redefined (updated) via the {@code /rg redefine} command or equivalent API. + * + *

Cancelling this event prevents the region boundaries from being changed. + * Flags, members, owners and priority are preserved — only the shape changes.

+ * + *

This event is fired on the main server thread immediately before the + * redefine operation is executed.

+ * + *

Example usage — prevent redefinition of a locked region:

+ *
{@code
+ * @EventHandler
+ * public void onRegionRedefine(RegionRedefineEvent event) {
+ *     if (isLocked(event.getOldRegion().getId())) {
+ *         event.setCancelled(true);
+ *         event.setCancelMessage("This region's boundaries are locked.");
+ *     }
+ * }
+ * }
+ */ +public class RegionRedefineEvent extends Event implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + private final World world; + private final ProtectedRegion oldRegion; + private final ProtectedRegion newRegion; + @Nullable + private final CommandSender actor; + + private boolean cancelled = false; + @Nullable + private String cancelMessage; + + /** + * Create a new instance. + * + * @param world the world the region belongs to + * @param oldRegion the existing region before the change + * @param newRegion the new region that will replace it (same id, different bounds) + * @param actor the command sender who initiated the action, or {@code null} if triggered via API + */ + public RegionRedefineEvent(World world, ProtectedRegion oldRegion, ProtectedRegion newRegion, + @Nullable CommandSender actor) { + checkNotNull(world); + checkNotNull(oldRegion); + checkNotNull(newRegion); + this.world = world; + this.oldRegion = oldRegion; + this.newRegion = newRegion; + this.actor = actor; + } + + /** + * Get the world in which the region resides. + * + * @return the world + */ + public World getWorld() { + return world; + } + + /** + * Get the existing region as it currently stands (before the change). + * + * @return the old region + */ + public ProtectedRegion getOldRegion() { + return oldRegion; + } + + /** + * Get the new region that will replace the old one. + * + *

The new region carries the same id and the copied flags/members/owners + * from the old region, but with updated boundaries.

+ * + * @return the new region + */ + public ProtectedRegion getNewRegion() { + return newRegion; + } + + /** + * Get the command sender who initiated this action. + * + *

Returns {@code null} when triggered through a direct API call.

+ * + * @return the actor, or {@code null} if not applicable + */ + @Nullable + public CommandSender getActor() { + return actor; + } + + /** + * Get the optional cancel message to send to the actor. + * + * @return the cancel message, or {@code null} + */ + @Nullable + public String getCancelMessage() { + return cancelMessage; + } + + /** + * Set a custom message to send to the actor when this event is cancelled. + * Set to {@code null} to use WorldGuard's default message. + * + * @param cancelMessage the message, or {@code null} + */ + public void setCancelMessage(@Nullable String cancelMessage) { + this.cancelMessage = cancelMessage; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/MemberCommands.java b/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/MemberCommands.java index e41eb1b93..9cc480b85 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/MemberCommands.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/MemberCommands.java @@ -72,7 +72,15 @@ public void addMember(CommandContext args, Actor sender) throws CommandException final String description = String.format("Adding members to the region '%s' on '%s'", region.getId(), world.getName()); AsyncCommandBuilder.wrap(resolver, sender) .registerWithSupervisor(worldGuard.getSupervisor(), description) - .onSuccess(String.format("Region '%s' updated with new members.", region.getId()), region.getMembers()::addAll) + .onSuccess((String) null, resolved -> { + if (WorldGuard.getInstance().getPlatform().callRegionMemberChangeEvent( + world, region, "ADD_MEMBER", resolved, sender)) { + region.getMembers().addAll(resolved); + sender.print(String.format("Region '%s' updated with new members.", region.getId())); + } else { + sender.print("Member change was cancelled by a plugin."); + } + }) .onFailure("Failed to add new members", worldGuard.getExceptionConverter()) .buildAndExec(worldGuard.getExecutorService()); } @@ -106,7 +114,15 @@ public void addOwner(CommandContext args, Actor sender) throws CommandException final String description = String.format("Adding owners to the region '%s' on '%s'", region.getId(), world.getName()); AsyncCommandBuilder.wrap(checkedAddOwners(sender, manager, region, world, resolver), sender) .registerWithSupervisor(worldGuard.getSupervisor(), description) - .onSuccess(String.format("Region '%s' updated with new owners.", region.getId()), region.getOwners()::addAll) + .onSuccess((String) null, resolved -> { + if (WorldGuard.getInstance().getPlatform().callRegionMemberChangeEvent( + world, region, "ADD_OWNER", resolved, sender)) { + region.getOwners().addAll(resolved); + sender.print(String.format("Region '%s' updated with new owners.", region.getId())); + } else { + sender.print("Owner change was cancelled by a plugin."); + } + }) .onFailure("Failed to add new owners", worldGuard.getExceptionConverter()) .buildAndExec(worldGuard.getExecutorService()); } @@ -182,7 +198,15 @@ public void removeMember(CommandContext args, Actor sender) throws CommandExcept AsyncCommandBuilder.wrap(callable, sender) .registerWithSupervisor(worldGuard.getSupervisor(), description) .sendMessageAfterDelay("(Please wait... querying player names...)") - .onSuccess(String.format("Region '%s' updated with members removed.", region.getId()), region.getMembers()::removeAll) + .onSuccess((String) null, resolved -> { + if (WorldGuard.getInstance().getPlatform().callRegionMemberChangeEvent( + world, region, "REMOVE_MEMBER", resolved, sender)) { + region.getMembers().removeAll(resolved); + sender.print(String.format("Region '%s' updated with members removed.", region.getId())); + } else { + sender.print("Member removal was cancelled by a plugin."); + } + }) .onFailure("Failed to remove members", worldGuard.getExceptionConverter()) .buildAndExec(worldGuard.getExecutorService()); } @@ -225,7 +249,15 @@ public void removeOwner(CommandContext args, Actor sender) throws CommandExcepti AsyncCommandBuilder.wrap(callable, sender) .registerWithSupervisor(worldGuard.getSupervisor(), description) .sendMessageAfterDelay("(Please wait... querying player names...)") - .onSuccess(String.format("Region '%s' updated with owners removed.", region.getId()), region.getOwners()::removeAll) + .onSuccess((String) null, resolved -> { + if (WorldGuard.getInstance().getPlatform().callRegionMemberChangeEvent( + world, region, "REMOVE_OWNER", resolved, sender)) { + region.getOwners().removeAll(resolved); + sender.print(String.format("Region '%s' updated with owners removed.", region.getId())); + } else { + sender.print("Owner removal was cancelled by a plugin."); + } + }) .onFailure("Failed to remove owners", worldGuard.getExceptionConverter()) .buildAndExec(worldGuard.getExecutorService()); } diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java b/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java index 9299ad02b..70a082513 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java @@ -163,6 +163,11 @@ public void define(CommandContext args, Actor sender) throws CommandException { RegionAdder task = new RegionAdder(manager, region); task.addOwnersFromCommand(args, 2); + // Fire a platform event so other plugins can cancel region creation. + if (!WorldGuard.getInstance().getPlatform().callRegionAddEvent(world, region, sender)) { + throw new CommandException("Region creation was cancelled by a plugin."); + } + final String description = String.format("Adding region '%s'", region.getId()); AsyncCommandBuilder.wrap(task, sender) .registerWithSupervisor(worldGuard.getSupervisor(), description) @@ -214,6 +219,11 @@ public void redefine(CommandContext args, Actor sender) throws CommandException region.copyFrom(existing); + // Fire a platform event so other plugins can cancel boundary changes. + if (!WorldGuard.getInstance().getPlatform().callRegionRedefineEvent(world, existing, region, sender)) { + throw new CommandException("Region redefine was cancelled by a plugin."); + } + RegionAdder task = new RegionAdder(manager, region); final String description = String.format("Updating region '%s'", region.getId()); @@ -595,6 +605,10 @@ public void flag(CommandContext args, Actor sender) throws CommandException { // Set the flag value if a value was set if (value != null) { + // Fire a platform event so other plugins can cancel flag changes. + if (!WorldGuard.getInstance().getPlatform().callRegionFlagChangeEvent(world, existing, foundFlag, value, sender)) { + throw new CommandException("Region flag change was cancelled by a plugin."); + } // Set the flag if [value] was given even if [-g group] was given as well try { value = setFlag(existing, foundFlag, sender, value).toString(); @@ -608,6 +622,10 @@ public void flag(CommandContext args, Actor sender) throws CommandException { // No value? Clear the flag, if -g isn't specified } else if (!args.hasFlag('g')) { + // Fire a platform event for flag clearing (newValue == null signals clearing). + if (!WorldGuard.getInstance().getPlatform().callRegionFlagChangeEvent(world, existing, foundFlag, null, sender)) { + throw new CommandException("Region flag change was cancelled by a plugin."); + } // Clear the flag only if neither [value] nor [-g group] was given existing.setFlag(foundFlag, null); @@ -725,6 +743,12 @@ public void setPriority(CommandContext args, Actor sender) throws CommandExcepti throw new CommandPermissionsException(); } + // Fire a platform event so other plugins can cancel priority changes. + if (!WorldGuard.getInstance().getPlatform().callRegionPriorityChangeEvent( + world, existing, existing.getPriority(), priority, sender)) { + throw new CommandException("Region priority change was cancelled by a plugin."); + } + existing.setPriority(priority); sender.print("Priority of '" + existing.getId() + "' set to " + priority + " (higher numbers override)."); @@ -833,6 +857,14 @@ public void remove(CommandContext args, Actor sender) throws CommandException { task.setRemovalStrategy(RemovalStrategy.UNSET_PARENT_IN_CHILDREN); } + // Fire a platform event so other plugins can cancel region deletion. + RemovalStrategy effectiveStrategy = task.getRemovalStrategy() != null + ? task.getRemovalStrategy() + : RemovalStrategy.UNSET_PARENT_IN_CHILDREN; + if (!WorldGuard.getInstance().getPlatform().callRegionDeleteEvent(world, existing, effectiveStrategy, sender)) { + throw new CommandException("Region deletion was cancelled by a plugin."); + } + final String description = String.format("Removing region '%s' in '%s'", existing.getId(), world.getName()); AsyncCommandBuilder.wrap(task, sender) .registerWithSupervisor(WorldGuard.getInstance().getSupervisor(), description) diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/internal/platform/WorldGuardPlatform.java b/worldguard-core/src/main/java/com/sk89q/worldguard/internal/platform/WorldGuardPlatform.java index 0bbaaa94d..b1f1575a4 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/internal/platform/WorldGuardPlatform.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/internal/platform/WorldGuardPlatform.java @@ -26,12 +26,15 @@ import com.sk89q.worldguard.LocalPlayer; import com.sk89q.worldguard.config.ConfigurationManager; import com.sk89q.worldguard.protection.flags.FlagContext; +import com.sk89q.worldguard.protection.managers.RemovalStrategy; import com.sk89q.worldguard.protection.regions.ProtectedRegion; import com.sk89q.worldguard.protection.regions.RegionContainer; +import com.sk89q.worldguard.protection.flags.Flag; import com.sk89q.worldguard.session.SessionManager; import com.sk89q.worldguard.util.profile.cache.ProfileCache; import com.sk89q.worldguard.util.profile.resolver.ProfileService; +import javax.annotation.Nullable; import javax.annotation.Nullable; import java.nio.file.Path; @@ -170,4 +173,104 @@ public interface WorldGuardPlatform { default ProtectedRegion getSpawnProtection(World world) { return null; } + + /** + * Called just before a region is about to be defined (added) via command or API. + * + *

Implementations may fire a cancellable platform event here. If the + * event is cancelled this method must return {@code false}, causing + * WorldGuard to abort the add operation.

+ * + *

This method is not invoked when regions are loaded + * from storage during world/server startup.

+ * + * @param world the world in which the region is being defined + * @param region the region that is about to be added + * @param actor the platform-specific actor object, or {@code null} if triggered via API + * @return {@code true} if the operation should proceed, {@code false} to cancel + */ + default boolean callRegionAddEvent(World world, ProtectedRegion region, @Nullable Object actor) { + return true; + } + + /** + * Called just before a region is about to be removed via command or API. + * + *

Implementations may fire a cancellable platform event here. If the + * event is cancelled this method must return {@code false}, causing + * WorldGuard to abort the remove operation.

+ * + * @param world the world in which the region resides + * @param region the region that is about to be removed + * @param removalStrategy the removal strategy that will be applied to child regions + * @param actor the platform-specific actor object, or {@code null} if triggered via API + * @return {@code true} if the operation should proceed, {@code false} to cancel + */ + default boolean callRegionDeleteEvent(World world, ProtectedRegion region, + RemovalStrategy removalStrategy, @Nullable Object actor) { + return true; + } + + /** + * Called just before a region's boundaries are about to be redefined via command or API. + * + * @param world the world in which the region resides + * @param oldRegion the existing region before the change + * @param newRegion the new region that will replace it (same id, new bounds) + * @param actor the platform-specific actor object, or {@code null} if triggered via API + * @return {@code true} if the operation should proceed, {@code false} to cancel + */ + default boolean callRegionRedefineEvent(World world, ProtectedRegion oldRegion, + ProtectedRegion newRegion, @Nullable Object actor) { + return true; + } + + /** + * Called just before a flag on a region is about to be changed or cleared via command or API. + * + * @param world the world in which the region resides + * @param region the region being modified + * @param flag the flag being changed + * @param newValue the new value being set, or {@code null} if the flag is being cleared + * @param actor the platform-specific actor object, or {@code null} if triggered via API + * @return {@code true} if the operation should proceed, {@code false} to cancel + */ + default boolean callRegionFlagChangeEvent(World world, ProtectedRegion region, Flag flag, + @Nullable Object newValue, @Nullable Object actor) { + return true; + } + + /** + * Called just before members or owners of a region are about to change via command or API. + * + *

Note: implementations may fire this event asynchronously since UUID + * resolution is performed off the main thread.

+ * + * @param world the world in which the region resides + * @param region the region being modified + * @param changeType a string identifying the type of change + * ({@code "ADD_MEMBER"}, {@code "REMOVE_MEMBER"}, {@code "ADD_OWNER"}, {@code "REMOVE_OWNER"}) + * @param domain the platform-specific domain entries being added or removed + * @param actor the platform-specific actor object, or {@code null} if triggered via API + * @return {@code true} if the operation should proceed, {@code false} to cancel + */ + default boolean callRegionMemberChangeEvent(World world, ProtectedRegion region, String changeType, + @Nullable Object domain, @Nullable Object actor) { + return true; + } + + /** + * Called just before a region's priority is about to be changed via command or API. + * + * @param world the world in which the region resides + * @param region the region being modified + * @param oldPriority the current priority + * @param newPriority the new priority being set + * @param actor the platform-specific actor object, or {@code null} if triggered via API + * @return {@code true} if the operation should proceed, {@code false} to cancel + */ + default boolean callRegionPriorityChangeEvent(World world, ProtectedRegion region, int oldPriority, + int newPriority, @Nullable Object actor) { + return true; + } }