Fixes #5 and implements #4

Only clears items when exiting a clear region
Tracks when a player enters one or more clear regions, through a custom event
Tracks when a player leaves one or more clear regions, through a custom event
Uses the tracker's region info to check if a player is in a clear region where possible
Excludes boats with player passengers from the vehicle move event, and requires that vehicles are in the clear region, not outside it
Tracks player clear regions when a player joins or changes world
Reduces a lot of code clutter and redundancy in WorldGuardListener
This commit is contained in:
Kristian Knarvik 2024-01-11 23:50:13 +01:00
parent 282cc50df6
commit cd83c0404b
8 changed files with 361 additions and 59 deletions

View File

@ -2,6 +2,7 @@ package net.knarcraft.clearonworldguard;
import net.knarcraft.clearonworldguard.config.Configuration;
import net.knarcraft.clearonworldguard.listener.BlockListener;
import net.knarcraft.clearonworldguard.listener.ClearRegionListener;
import net.knarcraft.clearonworldguard.listener.EntityListener;
import net.knarcraft.clearonworldguard.listener.PlayerListener;
import org.bukkit.Bukkit;
@ -33,6 +34,7 @@ public final class ClearOnWorldGuard extends JavaPlugin {
Bukkit.getPluginManager().registerEvents(new PlayerListener(configuration), this);
Bukkit.getPluginManager().registerEvents(new EntityListener(configuration), this);
Bukkit.getPluginManager().registerEvents(new BlockListener(configuration), this);
Bukkit.getPluginManager().registerEvents(new ClearRegionListener(configuration), this);
this.debugMode = configuration.debugEnabled();
}

View File

@ -0,0 +1,55 @@
package net.knarcraft.clearonworldguard.event;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
* An event triggered when a player enters a clear region
*/
public class EnterClearRegionEvent extends PlayerEvent {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final Set<ProtectedRegion> regionEntered;
/**
* Instantiates a new clear region enter event
*
* @param who <p>The player involved in the event</p>
* @param regionsEntered <p>The region the player entered</p>
*/
public EnterClearRegionEvent(@NotNull Player who, @NotNull Set<ProtectedRegion> regionsEntered) {
super(who);
this.regionEntered = regionsEntered;
}
/**
* Gets the clear regions the player entered
*
* @return <p>The clear regions the player entered</p>
*/
public Set<ProtectedRegion> getRegionsEntered() {
return this.regionEntered;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
}
/**
* Gets registers handlers for this event
*
* @return <p>Registered handlers for this event</p>
*/
@SuppressWarnings("unused")
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
}

View File

@ -0,0 +1,55 @@
package net.knarcraft.clearonworldguard.event;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
* An event triggered when a player leaves a clear region
*/
public class ExitClearRegionEvent extends PlayerEvent {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final Set<ProtectedRegion> regionsExited;
/**
* Instantiates a new clear region exit event
*
* @param who <p>The player involved in the event</p>
* @param regionsExited <p>The region the player exited from</p>
*/
public ExitClearRegionEvent(@NotNull Player who, @NotNull Set<ProtectedRegion> regionsExited) {
super(who);
this.regionsExited = regionsExited;
}
/**
* Gets the clear region the player exited from
*
* @return <p>The region the player left</p>
*/
public Set<ProtectedRegion> getRegionsExited() {
return this.regionsExited;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
}
/**
* Gets registers handlers for this event
*
* @return <p>Registered handlers for this event</p>
*/
@SuppressWarnings("unused")
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
}

View File

@ -0,0 +1,54 @@
package net.knarcraft.clearonworldguard.listener;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import net.knarcraft.clearonworldguard.ClearOnWorldGuard;
import net.knarcraft.clearonworldguard.config.Configuration;
import net.knarcraft.clearonworldguard.event.EnterClearRegionEvent;
import net.knarcraft.clearonworldguard.event.ExitClearRegionEvent;
import net.knarcraft.clearonworldguard.manager.PlayerRegionTracker;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
* A listener for clear region entry and exit
*/
public class ClearRegionListener extends WorldGuardListener {
/**
* Instantiates a new WorldGuard listener
*
* @param configuration <p>The configuration to get regions and settings from</p>
*/
public ClearRegionListener(@NotNull Configuration configuration) {
super(configuration);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onClearRegionEnter(@NotNull EnterClearRegionEvent event) {
Player player = event.getPlayer();
Set<ProtectedRegion> regionsEntered = event.getRegionsEntered();
for (ProtectedRegion region : regionsEntered) {
PlayerRegionTracker.addRegion(player, region);
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onClearRegionExit(@NotNull ExitClearRegionEvent event) {
Player player = event.getPlayer();
Set<ProtectedRegion> regionsExited = event.getRegionsExited();
for (ProtectedRegion region : regionsExited) {
PlayerRegionTracker.removeRegion(player, region);
}
// Clear inventory, since the player left a clear region
if (!regionsExited.isEmpty()) {
ClearOnWorldGuard.logDebugMessage("Cleared items and vehicles of player " + player);
clearInventoriesAndRemoveRecursively(player);
}
}
}

View File

@ -2,8 +2,10 @@ package net.knarcraft.clearonworldguard.listener;
import net.knarcraft.clearonworldguard.ClearOnWorldGuard;
import net.knarcraft.clearonworldguard.config.Configuration;
import net.knarcraft.clearonworldguard.manager.PlayerRegionTracker;
import net.knarcraft.clearonworldguard.property.Permission;
import org.bukkit.Location;
import org.bukkit.entity.Boat;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
@ -100,7 +102,7 @@ public class EntityListener extends WorldGuardListener {
return;
}
if ((isInClearRegion(event.getEntity()) || isInClearRegion(player)) &&
if ((isInClearRegion(event.getEntity()) || PlayerRegionTracker.isInClearRegion(player)) &&
!player.hasPermission(Permission.BYPASS_ENTITY_INTERACTION.toString())) {
ClearOnWorldGuard.logDebugMessage("Prevented entity " + event.getEntity() + " from being damaged");
event.setCancelled(true);
@ -145,7 +147,16 @@ public class EntityListener extends WorldGuardListener {
Vehicle vehicle = event.getVehicle();
if (isInDifferentClearRegions(event.getFrom(), event.getTo())) {
// Player passengers are handled by the PlayerMoveEvent
if (vehicle instanceof Boat) {
for (Entity passenger : vehicle.getPassengers()) {
if (passenger instanceof Player) {
return;
}
}
}
if (isInClearRegion(event.getFrom()) && isInDifferentClearRegions(event.getFrom(), event.getTo())) {
ClearOnWorldGuard.logDebugMessage("Destroyed vehicle " + vehicle + " crossing a clear region border");
clearInventoriesAndRemoveRecursively(vehicle);
}

View File

@ -1,8 +1,13 @@
package net.knarcraft.clearonworldguard.listener;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import net.knarcraft.clearonworldguard.ClearOnWorldGuard;
import net.knarcraft.clearonworldguard.config.Configuration;
import net.knarcraft.clearonworldguard.event.EnterClearRegionEvent;
import net.knarcraft.clearonworldguard.event.ExitClearRegionEvent;
import net.knarcraft.clearonworldguard.manager.PlayerRegionTracker;
import net.knarcraft.clearonworldguard.property.Permission;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
@ -13,9 +18,11 @@ import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.entity.PlayerLeashEntityEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.inventory.ItemStack;
@ -23,6 +30,8 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SpawnEggMeta;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
* Listeners for player events
*/
@ -40,7 +49,7 @@ public class PlayerListener extends WorldGuardListener {
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onLeash(@NotNull PlayerLeashEntityEvent event) {
// Prevent leashing of entities while in a clear region
if (isInClearRegion(event.getPlayer()) &&
if (PlayerRegionTracker.isInClearRegion(event.getPlayer()) &&
!event.getPlayer().hasPermission(Permission.BYPASS_ENTITY_INTERACTION.toString())) {
ClearOnWorldGuard.logDebugMessage("Prevented " + event.getEntity() + " from being leashed by " +
event.getPlayer());
@ -56,12 +65,19 @@ public class PlayerListener extends WorldGuardListener {
return;
}
Player player = event.getPlayer();
// Prevent player from smuggling items in a chest boat or similar
if (isInDifferentClearRegions(from, to)) {
ClearOnWorldGuard.logDebugMessage("Cleared items and vehicles of player " + player);
clearInventoriesAndRemoveRecursively(player);
updateClearRegions(player, from, to);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onJoin(@NotNull PlayerJoinEvent event) {
// Update clear regions of the player
PlayerRegionTracker.replaceRegions(event.getPlayer(), getRegions(event.getPlayer().getLocation()));
}
@EventHandler(priority = EventPriority.MONITOR)
public void onWorldChange(@NotNull PlayerChangedWorldEvent event) {
// Update clear regions of the player
PlayerRegionTracker.replaceRegions(event.getPlayer(), getRegions(event.getPlayer().getLocation()));
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@ -72,13 +88,36 @@ public class PlayerListener extends WorldGuardListener {
if (toLocation == null || fromLocation.equals(toLocation)) {
return;
}
clearIfClearRegionChange(fromLocation, toLocation, event.getPlayer());
ClearOnWorldGuard.logDebugMessage("Detected teleporting player " + event.getPlayer());
updateClearRegions(event.getPlayer(), fromLocation, toLocation);
}
/**
* Triggers any necessary events in order to change a player's clear regions
*
* @param player <p>The player that moved</p>
* @param from <p>The location the player moved from</p>
* @param to <p>The location the player moved to</p>
*/
private void updateClearRegions(@NotNull Player player, @NotNull Location from, @NotNull Location to) {
Set<ProtectedRegion> regionsEntered = getRegionsEntered(from, to);
if (!regionsEntered.isEmpty()) {
Bukkit.getPluginManager().callEvent(new EnterClearRegionEvent(player, regionsEntered));
}
Set<ProtectedRegion> regionsLeft = getRegionsExited(from, to);
// Only trigger on the regions the player has entered
regionsLeft.removeIf(region -> !PlayerRegionTracker.getClearRegions(player).contains(region));
if (!regionsLeft.isEmpty()) {
Bukkit.getPluginManager().callEvent(new ExitClearRegionEvent(player, regionsLeft));
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onItemDrop(@NotNull PlayerDropItemEvent event) {
// Prevent a player from dropping an item while inside a clear region
if (isInClearRegion(event.getPlayer())) {
if (PlayerRegionTracker.isInClearRegion(event.getPlayer())) {
ClearOnWorldGuard.logDebugMessage("Prevented player " + event.getPlayer() + " from dropping an item");
event.setCancelled(true);
}
@ -87,7 +126,7 @@ public class PlayerListener extends WorldGuardListener {
@EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerDeath(@NotNull PlayerDeathEvent event) {
// Prevent deaths in clear regions from causing items to be kept or dropped
if (isInClearRegion(event.getEntity())) {
if (PlayerRegionTracker.isInClearRegion(event.getEntity())) {
ClearOnWorldGuard.logDebugMessage("Prevented player " + event.getEntity() + " from dropping or keeping " +
"items on death");
event.setKeepInventory(false);
@ -136,7 +175,7 @@ public class PlayerListener extends WorldGuardListener {
}
// Prevent entity interaction, such as trading or putting chests on donkeys
if ((isInClearRegion(event.getPlayer()) || isInClearRegion(event.getRightClicked())) &&
if ((PlayerRegionTracker.isInClearRegion(event.getPlayer()) || isInClearRegion(event.getRightClicked())) &&
!event.getPlayer().hasPermission(Permission.BYPASS_ENTITY_INTERACTION.toString())) {
ClearOnWorldGuard.logDebugMessage("Prevented player " + event.getPlayer() +
" from interacting with entity " + event.getRightClicked());

View File

@ -1,7 +1,6 @@
package net.knarcraft.clearonworldguard.listener;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldguard.protection.ApplicableRegionSet;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import net.knarcraft.clearonworldguard.ClearOnWorldGuard;
import net.knarcraft.clearonworldguard.config.Configuration;
@ -89,21 +88,6 @@ public abstract class WorldGuardListener implements Listener {
}
}
/**
* Clears the given player's inventory if they are leaving or entering a clear region
*
* @param fromLocation <p>The location the player moved or teleported from</p>
* @param toLocation <p>The location the player moved or teleported to</p>
* @param player <p>The player that's moving</p>
*/
protected void clearIfClearRegionChange(@NotNull Location fromLocation, @NotNull Location toLocation,
@NotNull Player player) {
if (isInDifferentClearRegions(fromLocation, toLocation)) {
ClearOnWorldGuard.logDebugMessage("Cleared inventory of player " + player);
player.getInventory().clear();
}
}
/**
* Checks whether the given locations are in different clear regions
*
@ -115,23 +99,47 @@ public abstract class WorldGuardListener implements Listener {
* @return <p>True if the two locations are in different clear regions</p>
*/
protected boolean isInDifferentClearRegions(@NotNull Location location1, @NotNull Location location2) {
World fromWorld = location1.getWorld();
if (fromWorld == null) {
ClearOnWorldGuard.logger().log(Level.WARNING, "Unable to check region change, as location " +
location1 + " has no world.");
return false;
}
ApplicableRegionSet setFrom = configuration.getRegionQuery().getApplicableRegions(BukkitAdapter.adapt(location1));
ApplicableRegionSet setTo = configuration.getRegionQuery().getApplicableRegions(BukkitAdapter.adapt(location2));
Set<ProtectedRegion> fromRegions = getOccupiedClearRegions(fromWorld, setFrom.getRegions());
Set<ProtectedRegion> toRegions = getOccupiedClearRegions(fromWorld, setTo.getRegions());
Set<ProtectedRegion> fromRegions = getOccupiedClearRegions(location1);
Set<ProtectedRegion> toRegions = getOccupiedClearRegions(location2);
// If the player is in one or more clear regions, clear unless the clear regions are the same
return (!fromRegions.isEmpty() || !toRegions.isEmpty()) && !fromRegions.equals(toRegions);
}
/**
* Gets the regions a player has newly entered
*
* @param location1 <p>The first location to check</p>
* @param location2 <p>The second location to check</p>
* @return <p>A list of all regions location2 is in, that location1 isn't in, or null if a world is null</p>
*/
protected @NotNull Set<ProtectedRegion> getRegionsEntered(@NotNull Location location1, @NotNull Location location2) {
Set<ProtectedRegion> fromRegions = getOccupiedClearRegions(location1);
Set<ProtectedRegion> toRegions = getOccupiedClearRegions(location2);
toRegions.removeAll(fromRegions);
ClearOnWorldGuard.logDebugMessage("Calculated entered regions to: " + toRegions);
return toRegions;
}
/**
* Gets the regions a player has newly exited
*
* @param location1 <p>The first location to check</p>
* @param location2 <p>The second location to check</p>
* @return <p>A list of all regions location1 is in, that location2 isn't in, or null if a world is null</p>
*/
protected @NotNull Set<ProtectedRegion> getRegionsExited(@NotNull Location location1, @NotNull Location location2) {
Set<ProtectedRegion> fromRegions = getOccupiedClearRegions(location1);
Set<ProtectedRegion> toRegions = getOccupiedClearRegions(location2);
fromRegions.removeAll(toRegions);
ClearOnWorldGuard.logDebugMessage("Calculated exited regions to: " + fromRegions);
return fromRegions;
}
/**
* Checks whether the given block is in a clear region
*
@ -159,36 +167,37 @@ public abstract class WorldGuardListener implements Listener {
* @return <p>True if the location is in a clear region</p>
*/
protected boolean isInClearRegion(@NotNull Location location) {
return !getOccupiedClearRegions(location).isEmpty();
}
/**
* Gets all regions set as clear regions the given location currently occupies
*
* @param location <p>The location to get clear regions for</p>
* @return <p>All clear regions the location occupies</p>
*/
private @NotNull Set<ProtectedRegion> getOccupiedClearRegions(@NotNull Location location) {
World playerWorld = location.getWorld();
if (playerWorld == null) {
ClearOnWorldGuard.logger().log(Level.WARNING, "Unable to check region change, as location " +
location + " has no world.");
return false;
return new HashSet<>();
}
ApplicableRegionSet setFrom = configuration.getRegionQuery().getApplicableRegions(BukkitAdapter.adapt(location));
Set<ProtectedRegion> fromRegions = getOccupiedClearRegions(playerWorld, setFrom.getRegions());
return !fromRegions.isEmpty();
Set<ProtectedRegion> regions = new HashSet<>(getRegions(location));
Set<ProtectedRegion> clearRegions = configuration.getProtectedRegions(playerWorld);
regions.removeIf(region -> !clearRegions.contains(region));
return regions;
}
/**
* Gets all regions set as clear regions the player currently occupies
* Gets all regions at the given location
*
* @param playerWorld <p>The world the player is currently in</p>
* @param playerRegions <p>The regions the player is in or will be in</p>
* @return <p>All clear regions found in playerRegions</p>
* @param location <p>The location to get regions for</p>
* @return <p>All regions at the location</p>
*/
private Set<ProtectedRegion> getOccupiedClearRegions(@NotNull World playerWorld,
@NotNull Set<ProtectedRegion> playerRegions) {
Set<ProtectedRegion> possibleRegions = configuration.getProtectedRegions(playerWorld);
Set<ProtectedRegion> result = new HashSet<>();
for (ProtectedRegion region : playerRegions) {
if (possibleRegions.contains(region)) {
result.add(region);
}
}
return result;
protected Set<ProtectedRegion> getRegions(@NotNull Location location) {
return configuration.getRegionQuery().getApplicableRegions(BukkitAdapter.adapt(location)).getRegions();
}
}

View File

@ -0,0 +1,77 @@
package net.knarcraft.clearonworldguard.manager;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A tracker that keeps track of every player's current clear region
*/
public final class PlayerRegionTracker {
private static final Map<Player, Set<ProtectedRegion>> currentPlayerRegions = new HashMap<>();
private PlayerRegionTracker() {
}
/**
* Replaces the current clear regions of the given player
*
* @param player <p>The player to add to</p>
* @param regions <p>The regions to add</p>
*/
public static void replaceRegions(@NotNull Player player, @NotNull Set<ProtectedRegion> regions) {
currentPlayerRegions.put(player, new HashSet<>(regions));
}
/**
* Adds the given clear region to the given player
*
* @param player <p>The player to add to</p>
* @param region <p>The region to add</p>
*/
public static void addRegion(@NotNull Player player, @NotNull ProtectedRegion region) {
currentPlayerRegions.putIfAbsent(player, new HashSet<>());
currentPlayerRegions.get(player).add(region);
}
/**
* Removes the given clear region from the given player
*
* @param player <p>The player to remove from</p>
* @param region <p>The region to remove</p>
*/
public static void removeRegion(@NotNull Player player, @NotNull ProtectedRegion region) {
currentPlayerRegions.putIfAbsent(player, new HashSet<>());
currentPlayerRegions.get(player).remove(region);
}
/**
* Checks whether the given player is in any clear region
*
* @param player <p>The player to check</p>
* @return <p>True if the player is in any clear region</p>
*/
public static boolean isInClearRegion(@NotNull Player player) {
currentPlayerRegions.putIfAbsent(player, new HashSet<>());
return !currentPlayerRegions.get(player).isEmpty();
}
/**
* Gets a player's current clear regions
*
* @param player <p>The player to get for</p>
* @return <p>The player's current clear regions</p>
*/
public static @NotNull Set<ProtectedRegion> getClearRegions(@NotNull Player player) {
currentPlayerRegions.putIfAbsent(player, new HashSet<>());
return currentPlayerRegions.get(player);
}
}