diff --git a/README.md b/README.md index 2126fe4..278ea07 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # Clear on WorldGuard -This plugin will clear a player's items when they enter or leave one of the configured regions. This is mainly useful -if players are able to go in creative mode in specific WorldGuard regions. This plugin would prevent players from -leaving with all kinds of items. Note that this plugin won't necessarily be useful unless the region has proper -restrictions, like denying item dropping, and denying any sell commands within the region. +This plugin will clear a player's items when they enter or leave, or teleport to or from one of the configured regions. +This is mainly useful if players are able to temporarily go in creative mode in specific WorldGuard regions. A lot of +additional things are blocked within clear regions in order to prevent creative mode abuse, such as: projectiles, XP +bottle usage, spawn egg usage, changing the mob in a spawner, dropping items, picking up items, breaking, placing or +interacting with blocks across clear region borders, block drops, pistons pushing or pulling across clear region +borders, TNT priming and keeping any inventory items if dying within a clear area. This list might not be exhaustive. ## Dependencies diff --git a/pom.xml b/pom.xml index 1488b4c..dd8e4bc 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ ClearOnWorldGuard - 1.8 + 16 UTF-8 @@ -83,7 +83,7 @@ org.jetbrains annotations - 20.1.0 + 24.0.1 compile diff --git a/src/main/java/net/knarcraft/clearonworldguard/WorldGuardListener.java b/src/main/java/net/knarcraft/clearonworldguard/WorldGuardListener.java index c860833..82af017 100644 --- a/src/main/java/net/knarcraft/clearonworldguard/WorldGuardListener.java +++ b/src/main/java/net/knarcraft/clearonworldguard/WorldGuardListener.java @@ -9,15 +9,32 @@ import com.sk89q.worldguard.protection.regions.RegionContainer; import com.sk89q.worldguard.protection.regions.RegionQuery; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.block.Block; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPistonExtendEvent; +import org.bukkit.event.block.BlockPistonRetractEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.TNTPrimeEvent; +import org.bukkit.event.entity.EntityPickupItemEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SpawnEggMeta; import org.jetbrains.annotations.NotNull; import java.util.HashMap; @@ -117,6 +134,138 @@ public class WorldGuardListener implements Listener { clearIfClearRegionChange(fromLocation, toLocation, event.getPlayer()); } + @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())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onXpBottle(@NotNull ProjectileLaunchEvent event) { + // Prevent a player from using an XP bottle in a clear region + if (isInClearRegion(event.getEntity())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onTNT(@NotNull TNTPrimeEvent event) { + // Prevent TNT from being primed in a clear region, or by a player in a clear region + Entity primingEntity = event.getPrimingEntity(); + if (primingEntity != null && isInClearRegion(primingEntity)) { + event.setCancelled(true); + return; + } + + Block primingBlock = event.getPrimingBlock(); + if (primingBlock != null && isInClearRegion(primingBlock)) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockPlace(@NotNull BlockPlaceEvent event) { + // Prevent placing a block outside a clear region + if (isInDifferentClearRegions(event.getPlayer().getLocation(), event.getBlockPlaced().getLocation())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockBreak(@NotNull BlockBreakEvent event) { + // Prevent breaking a block outside a clear region, while in the clear region + if (isInDifferentClearRegions(event.getPlayer().getLocation(), event.getBlock().getLocation())) { + event.setCancelled(true); + return; + } + + // Prevent item drops when the item is destroyed + if (isInClearRegion(event.getBlock())) { + event.setDropItems(false); + } + } + + // TODO: Prevent creepers from blowing up + // TODO: Prevent ignition of anything except netherrack and soul sand/soil + // TODO: Prevent dispensers from dispensing + // TODO: Prevent droppers from dropping + + @EventHandler(priority = EventPriority.HIGHEST) + public void onDeath(@NotNull PlayerDeathEvent event) { + // Prevent deaths in clear regions from causing items to be kept or dropped + if (isInClearRegion(event.getEntity())) { + event.setKeepInventory(false); + event.setKeepLevel(true); + event.getEntity().getInventory().clear(); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onItemPickup(@NotNull EntityPickupItemEvent event) { + // Prevent item pickup in clear regions + if (!(event.getEntity() instanceof Player player)) { + return; + } + + if (isInClearRegion(player)) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onPistonExtend(@NotNull BlockPistonExtendEvent event) { + // Prevent pistons from pushing blocks across the clear region border + Block piston = event.getBlock(); + List blocks = event.getBlocks(); + for (Block block : blocks) { + if (isInDifferentClearRegions(piston.getLocation(), block.getLocation())) { + event.setCancelled(true); + return; + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onPistonRetract(@NotNull BlockPistonRetractEvent event) { + // Prevent pistons from pulling blocks across the clear region border + Block piston = event.getBlock(); + List blocks = event.getBlocks(); + for (Block block : blocks) { + if (isInDifferentClearRegions(piston.getLocation(), block.getLocation())) { + event.setCancelled(true); + return; + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onInteract(@NotNull PlayerInteractEvent event) { + // Prevent interacting with blocks outside the clear region, such as chests + Block clicked = event.getClickedBlock(); + if (clicked != null && isInDifferentClearRegions(event.getPlayer().getLocation(), clicked.getLocation())) { + event.setCancelled(true); + return; + } + + // Block changing the creature in a spawner + if (event.getAction() == Action.RIGHT_CLICK_BLOCK && clicked != null && clicked.getType() == Material.SPAWNER && + isInClearRegion(clicked)) { + event.setCancelled(true); + return; + } + + // Block usage of spawn eggs + ItemStack item = event.getItem(); + if (item != null && isInClearRegion(event.getPlayer()) && item.hasItemMeta()) { + ItemMeta meta = event.getItem().getItemMeta(); + if (meta instanceof SpawnEggMeta) { + event.setCancelled(true); + } + } + } + /** * Clears the given player's inventory if they are leaving or entering a clear region * @@ -126,25 +275,78 @@ public class WorldGuardListener implements Listener { */ private void clearIfClearRegionChange(@NotNull Location fromLocation, @NotNull Location toLocation, @NotNull Player player) { - World playerWorld = fromLocation.getWorld(); - if (playerWorld == null) { - ClearOnWorldGuard.logger().log(Level.WARNING, "Unable to check region change, as location " + - fromLocation + " has no world."); - return; - } - - ApplicableRegionSet setFrom = query.getApplicableRegions(BukkitAdapter.adapt(fromLocation)); - ApplicableRegionSet setTo = query.getApplicableRegions(BukkitAdapter.adapt(toLocation)); - - Set fromRegions = getOccupiedClearRegions(playerWorld, setFrom.getRegions()); - Set toRegions = getOccupiedClearRegions(playerWorld, setTo.getRegions()); - - // If the player is in one or more clear regions, clear unless the clear regions are the same - if ((!fromRegions.isEmpty() || !toRegions.isEmpty()) && !fromRegions.equals(toRegions)) { + if (isInDifferentClearRegions(fromLocation, toLocation)) { player.getInventory().clear(); } } + /** + * Checks whether the given locations are in different clear regions + * + *

If location1 is in a clear region and location2 is not, or if location1 is in a different clear region than + * region2, or if region 1 is in an additional clear region, this will return true.

+ * + * @param location1

The first location to check

+ * @param location2

The second location to check

+ * @return

True if the two locations are in different clear regions

+ */ + private 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 = query.getApplicableRegions(BukkitAdapter.adapt(location1)); + ApplicableRegionSet setTo = query.getApplicableRegions(BukkitAdapter.adapt(location2)); + + Set fromRegions = getOccupiedClearRegions(fromWorld, setFrom.getRegions()); + Set toRegions = getOccupiedClearRegions(fromWorld, setTo.getRegions()); + + // 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); + } + + /** + * Checks whether the given block is in a clear region + * + * @param entity

The entity to check

+ * @return

True if the block is in a clear region

+ */ + private boolean isInClearRegion(@NotNull Entity entity) { + return isInClearRegion(entity.getLocation()); + } + + /** + * Checks whether the given block is in a clear region + * + * @param block

The block to check

+ * @return

True if the block is in a clear region

+ */ + private boolean isInClearRegion(@NotNull Block block) { + return isInClearRegion(block.getLocation()); + } + + /** + * Checks whether the given location is in a clear region + * + * @param location

The location to check

+ * @return

True if the location is in a clear region

+ */ + private boolean isInClearRegion(@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; + } + + ApplicableRegionSet setFrom = query.getApplicableRegions(BukkitAdapter.adapt(location)); + Set fromRegions = getOccupiedClearRegions(playerWorld, setFrom.getRegions()); + return !fromRegions.isEmpty(); + } + /** * Gets all regions set as clear regions the player currently occupies *