diff --git a/src/main/java/net/knarcraft/clearonworldguard/ClearOnWorldGuard.java b/src/main/java/net/knarcraft/clearonworldguard/ClearOnWorldGuard.java index 2e9ce3e..9448e81 100644 --- a/src/main/java/net/knarcraft/clearonworldguard/ClearOnWorldGuard.java +++ b/src/main/java/net/knarcraft/clearonworldguard/ClearOnWorldGuard.java @@ -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(); } diff --git a/src/main/java/net/knarcraft/clearonworldguard/event/EnterClearRegionEvent.java b/src/main/java/net/knarcraft/clearonworldguard/event/EnterClearRegionEvent.java new file mode 100644 index 0000000..6ca8239 --- /dev/null +++ b/src/main/java/net/knarcraft/clearonworldguard/event/EnterClearRegionEvent.java @@ -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 regionEntered; + + /** + * Instantiates a new clear region enter event + * + * @param who

The player involved in the event

+ * @param regionsEntered

The region the player entered

+ */ + public EnterClearRegionEvent(@NotNull Player who, @NotNull Set regionsEntered) { + super(who); + this.regionEntered = regionsEntered; + } + + /** + * Gets the clear regions the player entered + * + * @return

The clear regions the player entered

+ */ + public Set getRegionsEntered() { + return this.regionEntered; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + /** + * Gets registers handlers for this event + * + * @return

Registered handlers for this event

+ */ + @SuppressWarnings("unused") + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } + +} diff --git a/src/main/java/net/knarcraft/clearonworldguard/event/ExitClearRegionEvent.java b/src/main/java/net/knarcraft/clearonworldguard/event/ExitClearRegionEvent.java new file mode 100644 index 0000000..37491da --- /dev/null +++ b/src/main/java/net/knarcraft/clearonworldguard/event/ExitClearRegionEvent.java @@ -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 regionsExited; + + /** + * Instantiates a new clear region exit event + * + * @param who

The player involved in the event

+ * @param regionsExited

The region the player exited from

+ */ + public ExitClearRegionEvent(@NotNull Player who, @NotNull Set regionsExited) { + super(who); + this.regionsExited = regionsExited; + } + + /** + * Gets the clear region the player exited from + * + * @return

The region the player left

+ */ + public Set getRegionsExited() { + return this.regionsExited; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + /** + * Gets registers handlers for this event + * + * @return

Registered handlers for this event

+ */ + @SuppressWarnings("unused") + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } + +} diff --git a/src/main/java/net/knarcraft/clearonworldguard/listener/ClearRegionListener.java b/src/main/java/net/knarcraft/clearonworldguard/listener/ClearRegionListener.java new file mode 100644 index 0000000..ddb1136 --- /dev/null +++ b/src/main/java/net/knarcraft/clearonworldguard/listener/ClearRegionListener.java @@ -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

The configuration to get regions and settings from

+ */ + public ClearRegionListener(@NotNull Configuration configuration) { + super(configuration); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onClearRegionEnter(@NotNull EnterClearRegionEvent event) { + Player player = event.getPlayer(); + Set 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 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); + } + } + +} diff --git a/src/main/java/net/knarcraft/clearonworldguard/listener/EntityListener.java b/src/main/java/net/knarcraft/clearonworldguard/listener/EntityListener.java index 02a8a40..6b99e27 100644 --- a/src/main/java/net/knarcraft/clearonworldguard/listener/EntityListener.java +++ b/src/main/java/net/knarcraft/clearonworldguard/listener/EntityListener.java @@ -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); } diff --git a/src/main/java/net/knarcraft/clearonworldguard/listener/PlayerListener.java b/src/main/java/net/knarcraft/clearonworldguard/listener/PlayerListener.java index ccdb1a6..0bce4db 100644 --- a/src/main/java/net/knarcraft/clearonworldguard/listener/PlayerListener.java +++ b/src/main/java/net/knarcraft/clearonworldguard/listener/PlayerListener.java @@ -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(); + updateClearRegions(player, from, to); + } - // 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); - } + @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

The player that moved

+ * @param from

The location the player moved from

+ * @param to

The location the player moved to

+ */ + private void updateClearRegions(@NotNull Player player, @NotNull Location from, @NotNull Location to) { + Set regionsEntered = getRegionsEntered(from, to); + if (!regionsEntered.isEmpty()) { + Bukkit.getPluginManager().callEvent(new EnterClearRegionEvent(player, regionsEntered)); + } + + Set 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()); diff --git a/src/main/java/net/knarcraft/clearonworldguard/listener/WorldGuardListener.java b/src/main/java/net/knarcraft/clearonworldguard/listener/WorldGuardListener.java index f4a4ec1..d91af18 100644 --- a/src/main/java/net/knarcraft/clearonworldguard/listener/WorldGuardListener.java +++ b/src/main/java/net/knarcraft/clearonworldguard/listener/WorldGuardListener.java @@ -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

The location the player moved or teleported from

- * @param toLocation

The location the player moved or teleported to

- * @param player

The player that's moving

- */ - 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

True if the two locations are in different clear regions

*/ 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 fromRegions = getOccupiedClearRegions(fromWorld, setFrom.getRegions()); - Set toRegions = getOccupiedClearRegions(fromWorld, setTo.getRegions()); + Set fromRegions = getOccupiedClearRegions(location1); + Set 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

The first location to check

+ * @param location2

The second location to check

+ * @return

A list of all regions location2 is in, that location1 isn't in, or null if a world is null

+ */ + protected @NotNull Set getRegionsEntered(@NotNull Location location1, @NotNull Location location2) { + Set fromRegions = getOccupiedClearRegions(location1); + Set 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

The first location to check

+ * @param location2

The second location to check

+ * @return

A list of all regions location1 is in, that location2 isn't in, or null if a world is null

+ */ + protected @NotNull Set getRegionsExited(@NotNull Location location1, @NotNull Location location2) { + Set fromRegions = getOccupiedClearRegions(location1); + Set 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

True if the location is in a clear region

*/ protected boolean isInClearRegion(@NotNull Location location) { + return !getOccupiedClearRegions(location).isEmpty(); + } + + /** + * Gets all regions set as clear regions the given location currently occupies + * + * @param location

The location to get clear regions for

+ * @return

All clear regions the location occupies

+ */ + private @NotNull Set 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 fromRegions = getOccupiedClearRegions(playerWorld, setFrom.getRegions()); - return !fromRegions.isEmpty(); + Set regions = new HashSet<>(getRegions(location)); + Set 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

The world the player is currently in

- * @param playerRegions

The regions the player is in or will be in

- * @return

All clear regions found in playerRegions

+ * @param location

The location to get regions for

+ * @return

All regions at the location

*/ - private Set getOccupiedClearRegions(@NotNull World playerWorld, - @NotNull Set playerRegions) { - Set possibleRegions = configuration.getProtectedRegions(playerWorld); - Set result = new HashSet<>(); - for (ProtectedRegion region : playerRegions) { - if (possibleRegions.contains(region)) { - result.add(region); - } - } - - return result; + protected Set getRegions(@NotNull Location location) { + return configuration.getRegionQuery().getApplicableRegions(BukkitAdapter.adapt(location)).getRegions(); } } diff --git a/src/main/java/net/knarcraft/clearonworldguard/manager/PlayerRegionTracker.java b/src/main/java/net/knarcraft/clearonworldguard/manager/PlayerRegionTracker.java new file mode 100644 index 0000000..bba5708 --- /dev/null +++ b/src/main/java/net/knarcraft/clearonworldguard/manager/PlayerRegionTracker.java @@ -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> currentPlayerRegions = new HashMap<>(); + + private PlayerRegionTracker() { + + } + + /** + * Replaces the current clear regions of the given player + * + * @param player

The player to add to

+ * @param regions

The regions to add

+ */ + public static void replaceRegions(@NotNull Player player, @NotNull Set regions) { + currentPlayerRegions.put(player, new HashSet<>(regions)); + } + + /** + * Adds the given clear region to the given player + * + * @param player

The player to add to

+ * @param region

The region to add

+ */ + 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

The player to remove from

+ * @param region

The region to remove

+ */ + 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

The player to check

+ * @return

True if the player is in any clear region

+ */ + 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

The player to get for

+ * @return

The player's current clear regions

+ */ + public static @NotNull Set getClearRegions(@NotNull Player player) { + currentPlayerRegions.putIfAbsent(player, new HashSet<>()); + return currentPlayerRegions.get(player); + } + +}