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; + } }