diff --git a/plugin.yml b/plugin.yml index 980d34e..ad80f83 100644 --- a/plugin.yml +++ b/plugin.yml @@ -2,7 +2,7 @@ name: GriefPrevention main: me.ryanhamshire.GriefPrevention.GriefPrevention softdepend: [Vault, Multiverse-Core, My Worlds, MystCraft, Transporter] dev-url: http://dev.bukkit.org/server-mods/grief-prevention -version: 7.2.2 +version: 7.6.1 commands: abandonclaim: description: Deletes a claim. @@ -125,6 +125,9 @@ commands: claimslist: description: Lists information about a player's claim blocks and claims. usage: /ClaimsList or /ClaimsList + claimexplosions: + description: Toggles whether explosives may be used in a specific land claim. + usage: /ClaimExplosions permissions: griefprevention.createclaims: description: Grants permission to create claims. diff --git a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java index 867790a..b760cc0 100644 --- a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java @@ -21,6 +21,7 @@ package me.ryanhamshire.GriefPrevention; import java.util.ArrayList; import java.util.List; +import org.bukkit.ChatColor; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; @@ -30,10 +31,7 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; import org.bukkit.block.Chest; -import org.bukkit.entity.Arrow; -import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -49,7 +47,6 @@ import org.bukkit.event.block.BlockPistonRetractEvent; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.block.BlockSpreadEvent; import org.bukkit.event.block.SignChangeEvent; -import org.bukkit.event.entity.ProjectileHitEvent; import org.bukkit.event.world.StructureGrowEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; @@ -81,36 +78,6 @@ public class BlockEventHandler implements Listener this.trashBlocks.add(Material.WORKBENCH); } - //when a wooden button is triggered by an arrow... - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onProjectileHit(ProjectileHitEvent event) - { - Projectile projectile = event.getEntity(); - Location location = projectile.getLocation(); - Block block = location.getBlock(); - - //only care about wooden buttons - if(block.getType() != Material.WOOD_BUTTON) return; - - //only care about arrows - if(projectile instanceof Arrow) - { - Arrow arrow = (Arrow)projectile; - LivingEntity shooterEntity = arrow.getShooter(); - - //player arrows only trigger buttons when they have permission - if(shooterEntity instanceof Player) - { - //Player player = (Player)shooterEntity; - } - - //other arrows don't trigger buttons, could be used as a workaround to get access to areas without permission - else - { - } - } - } - //when a block is damaged... @EventHandler(ignoreCancelled = true) public void onBlockDamaged(BlockDamageEvent event) @@ -256,6 +223,19 @@ public class BlockEventHandler implements Listener { GriefPrevention.AddLogEntry("[Sign Placement] <" + player.getName() + "> " + lines.toString() + " @ " + GriefPrevention.getfriendlyLocationString(event.getBlock().getLocation())); playerData.lastMessage = signMessage; + + if(!player.hasPermission("griefprevention.eavesdrop")) + { + Player [] players = GriefPrevention.instance.getServer().getOnlinePlayers(); + for(int i = 0; i < players.length; i++) + { + Player otherPlayer = players[i]; + if(otherPlayer.hasPermission("griefprevention.eavesdrop")) + { + otherPlayer.sendMessage(ChatColor.GRAY + player.getName() + "(sign): " + signMessage); + } + } + } } } @@ -300,9 +280,10 @@ public class BlockEventHandler implements Listener if(claim != null) { //warn about TNT not destroying claimed blocks - if(block.getType() == Material.TNT) + if(block.getType() == Material.TNT && !claim.areExplosivesAllowed) { GriefPrevention.sendMessage(player, TextMode.Warn, Messages.NoTNTDamageClaims); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.ClaimExplosivesAdvertisement); } //if the player has permission for the claim and he's placing UNDER the claim diff --git a/src/me/ryanhamshire/GriefPrevention/Claim.java b/src/me/ryanhamshire/GriefPrevention/Claim.java index e42d7d5..c5a163e 100644 --- a/src/me/ryanhamshire/GriefPrevention/Claim.java +++ b/src/me/ryanhamshire/GriefPrevention/Claim.java @@ -1,833 +1,835 @@ -/* - GriefPrevention Server Plugin for Minecraft - Copyright (C) 2012 Ryan Hamshire - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -package me.ryanhamshire.GriefPrevention; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -import org.bukkit.*; -import org.bukkit.World.Environment; -import org.bukkit.block.Block; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; - -//represents a player claim -//creating an instance doesn't make an effective claim -//only claims which have been added to the datastore have any effect -public class Claim -{ - //two locations, which together define the boundaries of the claim - //note that the upper Y value is always ignored, because claims ALWAYS extend up to the sky - Location lesserBoundaryCorner; - Location greaterBoundaryCorner; - - //modification date. this comes from the file timestamp during load, and is updated with runtime changes - public Date modifiedDate; - - //id number. unique to this claim, never changes. - Long id = null; - - //ownername. for admin claims, this is the empty string - //use getOwnerName() to get a friendly name (will be "an administrator" for admin claims) - public String ownerName; - - //list of players who (beyond the claim owner) have permission to grant permissions in this claim - public ArrayList managers = new ArrayList(); - - //permissions for this claim, see ClaimPermission class - private HashMap playerNameToClaimPermissionMap = new HashMap(); - - //whether or not this claim is in the data store - //if a claim instance isn't in the data store, it isn't "active" - players can't interract with it - //why keep this? so that claims which have been removed from the data store can be correctly - //ignored even though they may have references floating around - public boolean inDataStore = false; - - //parent claim - //only used for claim subdivisions. top level claims have null here - public Claim parent = null; - - //children (subdivisions) - //note subdivisions themselves never have children - public ArrayList children = new ArrayList(); - - //information about a siege involving this claim. null means no siege is impacting this claim - public SiegeData siegeData = null; - - //following a siege, buttons/levers are unlocked temporarily. this represents that state - public boolean doorsOpen = false; - - //whether or not this is an administrative claim - //administrative claims are created and maintained by players with the griefprevention.adminclaims permission. - public boolean isAdminClaim() - { - return (this.ownerName == null || this.ownerName.isEmpty()); - } - - //accessor for ID - public Long getID() - { - return new Long(this.id); - } - - //basic constructor, just notes the creation time - //see above declarations for other defaults - Claim() - { - this.modifiedDate = Calendar.getInstance().getTime(); - } - - //players may only siege someone when he's not in an admin claim - //and when he has some level of permission in the claim - public boolean canSiege(Player defender) - { - if(this.isAdminClaim()) return false; - - if(this.allowAccess(defender) != null) return false; - - return true; - } - - //removes any fluids above sea level in a claim - //exclusionClaim is another claim indicating an sub-area to be excluded from this operation - //it may be null - public void removeSurfaceFluids(Claim exclusionClaim) - { - //don't do this for administrative claims - if(this.isAdminClaim()) return; - - //don't do it for very large claims - if(this.getArea() > 10000) return; - - //don't do it when surface fluids are allowed to be dumped - if(!GriefPrevention.instance.config_blockWildernessWaterBuckets) return; - - Location lesser = this.getLesserBoundaryCorner(); - Location greater = this.getGreaterBoundaryCorner(); - - if(lesser.getWorld().getEnvironment() == Environment.NETHER) return; //don't clean up lava in the nether - - int seaLevel = 0; //clean up all fluids in the end - - //respect sea level in normal worlds - if(lesser.getWorld().getEnvironment() == Environment.NORMAL) seaLevel = GriefPrevention.instance.getSeaLevel(lesser.getWorld()); - - for(int x = lesser.getBlockX(); x <= greater.getBlockX(); x++) - { - for(int z = lesser.getBlockZ(); z <= greater.getBlockZ(); z++) - { - for(int y = seaLevel - 1; y <= lesser.getWorld().getMaxHeight(); y++) - { - //dodge the exclusion claim - Block block = lesser.getWorld().getBlockAt(x, y, z); - if(exclusionClaim != null && exclusionClaim.contains(block.getLocation(), true, false)) continue; - - if(block.getType() == Material.STATIONARY_WATER || block.getType() == Material.STATIONARY_LAVA || block.getType() == Material.LAVA || block.getType() == Material.WATER) - { - block.setType(Material.AIR); - } - } - } - } - } - - //determines whether or not a claim has surface fluids (lots of water blocks, or any lava blocks) - //used to warn players when they abandon their claims about automatic fluid cleanup - boolean hasSurfaceFluids() - { - Location lesser = this.getLesserBoundaryCorner(); - Location greater = this.getGreaterBoundaryCorner(); - - //don't bother for very large claims, too expensive - if(this.getArea() > 10000) return false; - - int seaLevel = 0; //clean up all fluids in the end - - //respect sea level in normal worlds - if(lesser.getWorld().getEnvironment() == Environment.NORMAL) seaLevel = GriefPrevention.instance.getSeaLevel(lesser.getWorld()); - - int waterCount = 0; - for(int x = lesser.getBlockX(); x <= greater.getBlockX(); x++) - { - for(int z = lesser.getBlockZ(); z <= greater.getBlockZ(); z++) - { - for(int y = seaLevel - 1; y <= lesser.getWorld().getMaxHeight(); y++) - { - //dodge the exclusion claim - Block block = lesser.getWorld().getBlockAt(x, y, z); - - if(block.getType() == Material.STATIONARY_WATER || block.getType() == Material.WATER) - { - waterCount++; - if(waterCount > 10) return true; - } - - else if(block.getType() == Material.STATIONARY_LAVA || block.getType() == Material.LAVA) - { - return true; - } - } - } - } - - return false; - } - - //main constructor. note that only creating a claim instance does nothing - a claim must be added to the data store to be effective - Claim(Location lesserBoundaryCorner, Location greaterBoundaryCorner, String ownerName, String [] builderNames, String [] containerNames, String [] accessorNames, String [] managerNames, Long id) - { - //modification date - this.modifiedDate = Calendar.getInstance().getTime(); - - //id - this.id = id; - - //store corners - this.lesserBoundaryCorner = lesserBoundaryCorner; - this.greaterBoundaryCorner = greaterBoundaryCorner; - - //owner - this.ownerName = ownerName; - - //other permissions - for(int i = 0; i < builderNames.length; i++) - { - String name = builderNames[i]; - if(name != null && !name.isEmpty()) - { - this.playerNameToClaimPermissionMap.put(name, ClaimPermission.Build); - } - } - - for(int i = 0; i < containerNames.length; i++) - { - String name = containerNames[i]; - if(name != null && !name.isEmpty()) - { - this.playerNameToClaimPermissionMap.put(name, ClaimPermission.Inventory); - } - } - - for(int i = 0; i < accessorNames.length; i++) - { - String name = accessorNames[i]; - if(name != null && !name.isEmpty()) - { - this.playerNameToClaimPermissionMap.put(name, ClaimPermission.Access); - } - } - - for(int i = 0; i < managerNames.length; i++) - { - String name = managerNames[i]; - if(name != null && !name.isEmpty()) - { - this.managers.add(name); - } - } - } - - //measurements. all measurements are in blocks - public int getArea() - { - int claimWidth = this.greaterBoundaryCorner.getBlockX() - this.lesserBoundaryCorner.getBlockX() + 1; - int claimHeight = this.greaterBoundaryCorner.getBlockZ() - this.lesserBoundaryCorner.getBlockZ() + 1; - - return claimWidth * claimHeight; - } - - public int getWidth() - { - return this.greaterBoundaryCorner.getBlockX() - this.lesserBoundaryCorner.getBlockX() + 1; - } - - public int getHeight() - { - return this.greaterBoundaryCorner.getBlockZ() - this.lesserBoundaryCorner.getBlockZ() + 1; - } - - //distance check for claims, distance in this case is a band around the outside of the claim rather then euclidean distance - public boolean isNear(Location location, int howNear) - { - Claim claim = new Claim - (new Location(this.lesserBoundaryCorner.getWorld(), this.lesserBoundaryCorner.getBlockX() - howNear, this.lesserBoundaryCorner.getBlockY(), this.lesserBoundaryCorner.getBlockZ() - howNear), - new Location(this.greaterBoundaryCorner.getWorld(), this.greaterBoundaryCorner.getBlockX() + howNear, this.greaterBoundaryCorner.getBlockY(), this.greaterBoundaryCorner.getBlockZ() + howNear), - "", new String[] {}, new String[] {}, new String[] {}, new String[] {}, null); - - return claim.contains(location, false, true); - } - - //permissions. note administrative "public" claims have different rules than other claims - //all of these return NULL when a player has permission, or a String error message when the player doesn't have permission - public String allowEdit(Player player) - { - //if we don't know who's asking, always say no (i've been told some mods can make this happen somehow) - if(player == null) return ""; - - //special cases... - - //admin claims need adminclaims permission only. - if(this.isAdminClaim()) - { - if(player.hasPermission("griefprevention.adminclaims")) return null; - } - - //anyone with deleteclaims permission can modify non-admin claims at any time - else - { - if(player.hasPermission("griefprevention.deleteclaims")) return null; - } - - //no resizing, deleting, and so forth while under siege - if(this.ownerName.equals(player.getName())) - { - if(this.siegeData != null) - { - return GriefPrevention.instance.dataStore.getMessage(Messages.NoModifyDuringSiege); - } - - //otherwise, owners can do whatever - return null; - } - - //permission inheritance for subdivisions - if(this.parent != null) - return this.parent.allowBuild(player); - - //error message if all else fails - return GriefPrevention.instance.dataStore.getMessage(Messages.OnlyOwnersModifyClaims, this.getOwnerName()); - } - - //build permission check - public String allowBuild(Player player) - { - //if we don't know who's asking, always say no (i've been told some mods can make this happen somehow) - if(player == null) return ""; - - //when a player tries to build in a claim, if he's under siege, the siege may extend to include the new claim - GriefPrevention.instance.dataStore.tryExtendSiege(player, this); - - //admin claims can always be modified by admins, no exceptions - if(this.isAdminClaim()) - { - if(player.hasPermission("griefprevention.adminclaims")) return null; - } - - //no building while under siege - if(this.siegeData != null) - { - return GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildUnderSiege, this.siegeData.attacker.getName()); - } - - //no building while in pvp combat - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getName()); - if(playerData.inPvpCombat()) - { - return GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildPvP); - } - - //owners can make changes, or admins with ignore claims mode enabled - if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null; - - //anyone with explicit build permission can make changes - if(this.hasExplicitPermission(player, ClaimPermission.Build)) return null; - - //also everyone is a member of the "public", so check for public permission - ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get("public"); - if(ClaimPermission.Build == permissionLevel) return null; - - //subdivision permission inheritance - if(this.parent != null) - return this.parent.allowBuild(player); - - //failure message for all other cases - String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildPermission, this.getOwnerName()); - if(player.hasPermission("griefprevention.ignoreclaims")) - reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); - return reason; - } - - private boolean hasExplicitPermission(Player player, ClaimPermission level) - { - String playerName = player.getName(); - Set keys = this.playerNameToClaimPermissionMap.keySet(); - Iterator iterator = keys.iterator(); - while(iterator.hasNext()) - { - String identifier = iterator.next(); - if(playerName.equalsIgnoreCase(identifier) && this.playerNameToClaimPermissionMap.get(identifier) == level) return true; - - else if(identifier.startsWith("[") && identifier.endsWith("]")) - { - //drop the brackets - String permissionIdentifier = identifier.substring(1, identifier.length() - 1); - - //defensive coding - if(permissionIdentifier == null || permissionIdentifier.isEmpty()) continue; - - //check permission - if(player.hasPermission(permissionIdentifier) && this.playerNameToClaimPermissionMap.get(identifier) == level) return true; - } - } - - return false; - } - - //break permission check - public String allowBreak(Player player, Material material) - { - //if under siege, some blocks will be breakable - if(this.siegeData != null) - { - boolean breakable = false; - - //search for block type in list of breakable blocks - for(int i = 0; i < GriefPrevention.instance.config_siege_blocks.size(); i++) - { - Material breakableMaterial = GriefPrevention.instance.config_siege_blocks.get(i); - if(breakableMaterial.getId() == material.getId()) - { - breakable = true; - break; - } - } - - //custom error messages for siege mode - if(!breakable) - { - return GriefPrevention.instance.dataStore.getMessage(Messages.NonSiegeMaterial); - } - else if(this.ownerName.equals(player.getName())) - { - return GriefPrevention.instance.dataStore.getMessage(Messages.NoOwnerBuildUnderSiege); - } - else - { - return null; - } - } - - //if not under siege, build rules apply - return this.allowBuild(player); - } - - //access permission check - public String allowAccess(Player player) - { - //following a siege where the defender lost, the claim will allow everyone access for a time - if(this.doorsOpen) return null; - - //admin claims need adminclaims permission only. - if(this.isAdminClaim()) - { - if(player.hasPermission("griefprevention.adminclaims")) return null; - } - - //claim owner and admins in ignoreclaims mode have access - if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null; - - //look for explicit individual access, inventory, or build permission - if(this.hasExplicitPermission(player, ClaimPermission.Access)) return null; - if(this.hasExplicitPermission(player, ClaimPermission.Inventory)) return null; - if(this.hasExplicitPermission(player, ClaimPermission.Build)) return null; - - //also check for public permission - ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get("public"); - if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel || ClaimPermission.Access == permissionLevel) return null; - - //permission inheritance for subdivisions - if(this.parent != null) - return this.parent.allowAccess(player); - - //catch-all error message for all other cases - String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoAccessPermission, this.getOwnerName()); - if(player.hasPermission("griefprevention.ignoreclaims")) - reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); - return reason; - } - - //inventory permission check - public String allowContainers(Player player) - { - //if we don't know who's asking, always say no (i've been told some mods can make this happen somehow) - if(player == null) return ""; - - //trying to access inventory in a claim may extend an existing siege to include this claim - GriefPrevention.instance.dataStore.tryExtendSiege(player, this); - - //if under siege, nobody accesses containers - if(this.siegeData != null) - { - return GriefPrevention.instance.dataStore.getMessage(Messages.NoContainersSiege, siegeData.attacker.getName()); - } - - //owner and administrators in ignoreclaims mode have access - if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null; - - //admin claims need adminclaims permission only. - if(this.isAdminClaim()) - { - if(player.hasPermission("griefprevention.adminclaims")) return null; - } - - //check for explicit individual container or build permission - if(this.hasExplicitPermission(player, ClaimPermission.Inventory)) return null; - if(this.hasExplicitPermission(player, ClaimPermission.Build)) return null; - - //check for public container or build permission - ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get("public"); - if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel) return null; - - //permission inheritance for subdivisions - if(this.parent != null) - return this.parent.allowContainers(player); - - //error message for all other cases - String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoContainersPermission, this.getOwnerName()); - if(player.hasPermission("griefprevention.ignoreclaims")) - reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); - return reason; - } - - //grant permission check, relatively simple - public String allowGrantPermission(Player player) - { - //if we don't know who's asking, always say no (i've been told some mods can make this happen somehow) - if(player == null) return ""; - - //anyone who can modify the claim can do this - if(this.allowEdit(player) == null) return null; - - //anyone who's in the managers (/PermissionTrust) list can do this - for(int i = 0; i < this.managers.size(); i++) - { - String managerID = this.managers.get(i); - if(player.getName().equalsIgnoreCase(managerID)) return null; - - else if(managerID.startsWith("[") && managerID.endsWith("]")) - { - managerID = managerID.substring(1, managerID.length() - 1); - if(managerID == null || managerID.isEmpty()) continue; - if(player.hasPermission(managerID)) return null; - } - } - - //permission inheritance for subdivisions - if(this.parent != null) - return this.parent.allowGrantPermission(player); - - //generic error message - String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoPermissionTrust, this.getOwnerName()); - if(player.hasPermission("griefprevention.ignoreclaims")) - reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); - return reason; - } - - //grants a permission for a player or the public - public void setPermission(String playerName, ClaimPermission permissionLevel) - { - this.playerNameToClaimPermissionMap.put(playerName.toLowerCase(), permissionLevel); - } - - //revokes a permission for a player or the public - public void dropPermission(String playerName) - { - this.playerNameToClaimPermissionMap.remove(playerName.toLowerCase()); - } - - //clears all permissions (except owner of course) - public void clearPermissions() - { - this.playerNameToClaimPermissionMap.clear(); - } - - //gets ALL permissions - //useful for making copies of permissions during a claim resize and listing all permissions in a claim - public void getPermissions(ArrayList builders, ArrayList containers, ArrayList accessors, ArrayList managers) - { - //loop through all the entries in the hash map - Iterator> mappingsIterator = this.playerNameToClaimPermissionMap.entrySet().iterator(); - while(mappingsIterator.hasNext()) - { - Map.Entry entry = mappingsIterator.next(); - - //build up a list for each permission level - if(entry.getValue() == ClaimPermission.Build) - { - builders.add(entry.getKey()); - } - else if(entry.getValue() == ClaimPermission.Inventory) - { - containers.add(entry.getKey()); - } - else - { - accessors.add(entry.getKey()); - } - } - - //managers are handled a little differently - for(int i = 0; i < this.managers.size(); i++) - { - managers.add(this.managers.get(i)); - } - } - - //returns a copy of the location representing lower x, y, z limits - public Location getLesserBoundaryCorner() - { - return this.lesserBoundaryCorner.clone(); - } - - //returns a copy of the location representing upper x, y, z limits - //NOTE: remember upper Y will always be ignored, all claims always extend to the sky - public Location getGreaterBoundaryCorner() - { - return this.greaterBoundaryCorner.clone(); - } - - //returns a friendly owner name (for admin claims, returns "an administrator" as the owner) - public String getOwnerName() - { - if(this.parent != null) - return this.parent.getOwnerName(); - - if(this.ownerName.length() == 0) - return GriefPrevention.instance.dataStore.getMessage(Messages.OwnerNameForAdminClaims); - - return this.ownerName; - } - - //whether or not a location is in a claim - //ignoreHeight = true means location UNDER the claim will return TRUE - //excludeSubdivisions = true means that locations inside subdivisions of the claim will return FALSE - public boolean contains(Location location, boolean ignoreHeight, boolean excludeSubdivisions) - { - //not in the same world implies false - if(!location.getWorld().equals(this.lesserBoundaryCorner.getWorld())) return false; - - int x = location.getBlockX(); - int y = location.getBlockY(); - int z = location.getBlockZ(); - - //main check - boolean inClaim = (ignoreHeight || y >= this.lesserBoundaryCorner.getBlockY()) && - x >= this.lesserBoundaryCorner.getBlockX() && - x <= this.greaterBoundaryCorner.getBlockX() && - z >= this.lesserBoundaryCorner.getBlockZ() && - z <= this.greaterBoundaryCorner.getBlockZ(); - - if(!inClaim) return false; - - //additional check for subdivisions - //you're only in a subdivision when you're also in its parent claim - //NOTE: if a player creates subdivions then resizes the parent claim, it's possible that - //a subdivision can reach outside of its parent's boundaries. so this check is important! - if(this.parent != null) - { - return this.parent.contains(location, ignoreHeight, false); - } - - //code to exclude subdivisions in this check - else if(excludeSubdivisions) - { - //search all subdivisions to see if the location is in any of them - for(int i = 0; i < this.children.size(); i++) - { - //if we find such a subdivision, return false - if(this.children.get(i).contains(location, ignoreHeight, true)) - { - return false; - } - } - } - - //otherwise yes - return true; - } - - //whether or not two claims overlap - //used internally to prevent overlaps when creating claims - boolean overlaps(Claim otherClaim) - { - //NOTE: if trying to understand this makes your head hurt, don't feel bad - it hurts mine too. - //try drawing pictures to visualize test cases. - - if(!this.lesserBoundaryCorner.getWorld().equals(otherClaim.getLesserBoundaryCorner().getWorld())) return false; - - //first, check the corners of this claim aren't inside any existing claims - if(otherClaim.contains(this.lesserBoundaryCorner, true, false)) return true; - if(otherClaim.contains(this.greaterBoundaryCorner, true, false)) return true; - if(otherClaim.contains(new Location(this.lesserBoundaryCorner.getWorld(), this.lesserBoundaryCorner.getBlockX(), 0, this.greaterBoundaryCorner.getBlockZ()), true, false)) return true; - if(otherClaim.contains(new Location(this.lesserBoundaryCorner.getWorld(), this.greaterBoundaryCorner.getBlockX(), 0, this.lesserBoundaryCorner.getBlockZ()), true, false)) return true; - - //verify that no claim's lesser boundary point is inside this new claim, to cover the "existing claim is entirely inside new claim" case - if(this.contains(otherClaim.getLesserBoundaryCorner(), true, false)) return true; - - //verify this claim doesn't band across an existing claim, either horizontally or vertically - if( this.getLesserBoundaryCorner().getBlockZ() <= otherClaim.getGreaterBoundaryCorner().getBlockZ() && - this.getLesserBoundaryCorner().getBlockZ() >= otherClaim.getLesserBoundaryCorner().getBlockZ() && - this.getLesserBoundaryCorner().getBlockX() < otherClaim.getLesserBoundaryCorner().getBlockX() && - this.getGreaterBoundaryCorner().getBlockX() > otherClaim.getGreaterBoundaryCorner().getBlockX() ) - return true; - - if( this.getGreaterBoundaryCorner().getBlockZ() <= otherClaim.getGreaterBoundaryCorner().getBlockZ() && - this.getGreaterBoundaryCorner().getBlockZ() >= otherClaim.getLesserBoundaryCorner().getBlockZ() && - this.getLesserBoundaryCorner().getBlockX() < otherClaim.getLesserBoundaryCorner().getBlockX() && - this.getGreaterBoundaryCorner().getBlockX() > otherClaim.getGreaterBoundaryCorner().getBlockX() ) - return true; - - if( this.getLesserBoundaryCorner().getBlockX() <= otherClaim.getGreaterBoundaryCorner().getBlockX() && - this.getLesserBoundaryCorner().getBlockX() >= otherClaim.getLesserBoundaryCorner().getBlockX() && - this.getLesserBoundaryCorner().getBlockZ() < otherClaim.getLesserBoundaryCorner().getBlockZ() && - this.getGreaterBoundaryCorner().getBlockZ() > otherClaim.getGreaterBoundaryCorner().getBlockZ() ) - return true; - - if( this.getGreaterBoundaryCorner().getBlockX() <= otherClaim.getGreaterBoundaryCorner().getBlockX() && - this.getGreaterBoundaryCorner().getBlockX() >= otherClaim.getLesserBoundaryCorner().getBlockX() && - this.getLesserBoundaryCorner().getBlockZ() < otherClaim.getLesserBoundaryCorner().getBlockZ() && - this.getGreaterBoundaryCorner().getBlockZ() > otherClaim.getGreaterBoundaryCorner().getBlockZ() ) - return true; - - return false; - } - - //whether more entities may be added to a claim - public String allowMoreEntities() - { - if(this.parent != null) return this.parent.allowMoreEntities(); - - //this rule only applies to creative mode worlds - if(!GriefPrevention.instance.creativeRulesApply(this.getLesserBoundaryCorner())) return null; - - //admin claims aren't restricted - if(this.isAdminClaim()) return null; - - //don't apply this rule to very large claims - if(this.getArea() > 10000) return null; - - //determine maximum allowable entity count, based on claim size - int maxEntities = this.getArea() / 50; - if(maxEntities == 0) return GriefPrevention.instance.dataStore.getMessage(Messages.ClaimTooSmallForEntities); - - //count current entities (ignoring players) - Chunk lesserChunk = this.getLesserBoundaryCorner().getChunk(); - Chunk greaterChunk = this.getGreaterBoundaryCorner().getChunk(); - - int totalEntities = 0; - for(int x = lesserChunk.getX(); x <= greaterChunk.getX(); x++) - for(int z = lesserChunk.getZ(); z <= greaterChunk.getZ(); z++) - { - Chunk chunk = lesserChunk.getWorld().getChunkAt(x, z); - Entity [] entities = chunk.getEntities(); - for(int i = 0; i < entities.length; i++) - { - Entity entity = entities[i]; - if(!(entity instanceof Player) && this.contains(entity.getLocation(), false, false)) - { - totalEntities++; - if(totalEntities > maxEntities) entity.remove(); - } - } - } - - if(totalEntities > maxEntities) return GriefPrevention.instance.dataStore.getMessage(Messages.TooManyEntitiesInClaim); - - return null; - } - - //implements a strict ordering of claims, used to keep the claims collection sorted for faster searching - boolean greaterThan(Claim otherClaim) - { - Location thisCorner = this.getLesserBoundaryCorner(); - Location otherCorner = otherClaim.getLesserBoundaryCorner(); - - if(thisCorner.getBlockX() > otherCorner.getBlockX()) return true; - - if(thisCorner.getBlockX() < otherCorner.getBlockX()) return false; - - if(thisCorner.getBlockZ() > otherCorner.getBlockZ()) return true; - - if(thisCorner.getBlockZ() < otherCorner.getBlockZ()) return false; - - return thisCorner.getWorld().getName().compareTo(otherCorner.getWorld().getName()) < 0; - } - - long getPlayerInvestmentScore() - { - //decide which blocks will be considered player placed - Location lesserBoundaryCorner = this.getLesserBoundaryCorner(); - ArrayList playerBlocks = RestoreNatureProcessingTask.getPlayerBlocks(lesserBoundaryCorner.getWorld().getEnvironment(), lesserBoundaryCorner.getBlock().getBiome()); - - //scan the claim for player placed blocks - double score = 0; - - boolean creativeMode = GriefPrevention.instance.creativeRulesApply(lesserBoundaryCorner); - - for(int x = this.lesserBoundaryCorner.getBlockX(); x <= this.greaterBoundaryCorner.getBlockX(); x++) - { - for(int z = this.lesserBoundaryCorner.getBlockZ(); z <= this.greaterBoundaryCorner.getBlockZ(); z++) - { - int y = this.lesserBoundaryCorner.getBlockY(); - for(; y < GriefPrevention.instance.getSeaLevel(this.lesserBoundaryCorner.getWorld()) - 5; y++) - { - Block block = this.lesserBoundaryCorner.getWorld().getBlockAt(x, y, z); - if(playerBlocks.contains(block.getTypeId())) - { - if(block.getType() == Material.CHEST && !creativeMode) - { - score += 10; - } - else - { - score += .5; - } - } - } - - for(; y < this.lesserBoundaryCorner.getWorld().getMaxHeight(); y++) - { - Block block = this.lesserBoundaryCorner.getWorld().getBlockAt(x, y, z); - if(playerBlocks.contains(block.getTypeId())) - { - if(block.getType() == Material.CHEST && !creativeMode) - { - score += 10; - } - else if(creativeMode && (block.getType() == Material.LAVA || block.getType() == Material.STATIONARY_LAVA)) - { - score -= 10; - } - else - { - score += 1; - } - } - } - } - } - - return (long)score; - } -} +/* + GriefPrevention Server Plugin for Minecraft + Copyright (C) 2012 Ryan Hamshire + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package me.ryanhamshire.GriefPrevention; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.bukkit.*; +import org.bukkit.World.Environment; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; + +//represents a player claim +//creating an instance doesn't make an effective claim +//only claims which have been added to the datastore have any effect +public class Claim +{ + //two locations, which together define the boundaries of the claim + //note that the upper Y value is always ignored, because claims ALWAYS extend up to the sky + Location lesserBoundaryCorner; + Location greaterBoundaryCorner; + + //modification date. this comes from the file timestamp during load, and is updated with runtime changes + public Date modifiedDate; + + //id number. unique to this claim, never changes. + Long id = null; + + //ownername. for admin claims, this is the empty string + //use getOwnerName() to get a friendly name (will be "an administrator" for admin claims) + public String ownerName; + + //list of players who (beyond the claim owner) have permission to grant permissions in this claim + public ArrayList managers = new ArrayList(); + + //permissions for this claim, see ClaimPermission class + private HashMap playerNameToClaimPermissionMap = new HashMap(); + + //whether or not this claim is in the data store + //if a claim instance isn't in the data store, it isn't "active" - players can't interract with it + //why keep this? so that claims which have been removed from the data store can be correctly + //ignored even though they may have references floating around + public boolean inDataStore = false; + + public boolean areExplosivesAllowed = false; + + //parent claim + //only used for claim subdivisions. top level claims have null here + public Claim parent = null; + + //children (subdivisions) + //note subdivisions themselves never have children + public ArrayList children = new ArrayList(); + + //information about a siege involving this claim. null means no siege is impacting this claim + public SiegeData siegeData = null; + + //following a siege, buttons/levers are unlocked temporarily. this represents that state + public boolean doorsOpen = false; + + //whether or not this is an administrative claim + //administrative claims are created and maintained by players with the griefprevention.adminclaims permission. + public boolean isAdminClaim() + { + return (this.ownerName == null || this.ownerName.isEmpty()); + } + + //accessor for ID + public Long getID() + { + return this.id; + } + + //basic constructor, just notes the creation time + //see above declarations for other defaults + Claim() + { + this.modifiedDate = Calendar.getInstance().getTime(); + } + + //players may only siege someone when he's not in an admin claim + //and when he has some level of permission in the claim + public boolean canSiege(Player defender) + { + if(this.isAdminClaim()) return false; + + if(this.allowAccess(defender) != null) return false; + + return true; + } + + //removes any fluids above sea level in a claim + //exclusionClaim is another claim indicating an sub-area to be excluded from this operation + //it may be null + public void removeSurfaceFluids(Claim exclusionClaim) + { + //don't do this for administrative claims + if(this.isAdminClaim()) return; + + //don't do it for very large claims + if(this.getArea() > 10000) return; + + //don't do it when surface fluids are allowed to be dumped + if(!GriefPrevention.instance.config_blockWildernessWaterBuckets) return; + + Location lesser = this.getLesserBoundaryCorner(); + Location greater = this.getGreaterBoundaryCorner(); + + if(lesser.getWorld().getEnvironment() == Environment.NETHER) return; //don't clean up lava in the nether + + int seaLevel = 0; //clean up all fluids in the end + + //respect sea level in normal worlds + if(lesser.getWorld().getEnvironment() == Environment.NORMAL) seaLevel = GriefPrevention.instance.getSeaLevel(lesser.getWorld()); + + for(int x = lesser.getBlockX(); x <= greater.getBlockX(); x++) + { + for(int z = lesser.getBlockZ(); z <= greater.getBlockZ(); z++) + { + for(int y = seaLevel - 1; y <= lesser.getWorld().getMaxHeight(); y++) + { + //dodge the exclusion claim + Block block = lesser.getWorld().getBlockAt(x, y, z); + if(exclusionClaim != null && exclusionClaim.contains(block.getLocation(), true, false)) continue; + + if(block.getType() == Material.STATIONARY_WATER || block.getType() == Material.STATIONARY_LAVA || block.getType() == Material.LAVA || block.getType() == Material.WATER) + { + block.setType(Material.AIR); + } + } + } + } + } + + //determines whether or not a claim has surface fluids (lots of water blocks, or any lava blocks) + //used to warn players when they abandon their claims about automatic fluid cleanup + boolean hasSurfaceFluids() + { + Location lesser = this.getLesserBoundaryCorner(); + Location greater = this.getGreaterBoundaryCorner(); + + //don't bother for very large claims, too expensive + if(this.getArea() > 10000) return false; + + int seaLevel = 0; //clean up all fluids in the end + + //respect sea level in normal worlds + if(lesser.getWorld().getEnvironment() == Environment.NORMAL) seaLevel = GriefPrevention.instance.getSeaLevel(lesser.getWorld()); + + int waterCount = 0; + for(int x = lesser.getBlockX(); x <= greater.getBlockX(); x++) + { + for(int z = lesser.getBlockZ(); z <= greater.getBlockZ(); z++) + { + for(int y = seaLevel - 1; y <= lesser.getWorld().getMaxHeight(); y++) + { + //dodge the exclusion claim + Block block = lesser.getWorld().getBlockAt(x, y, z); + + if(block.getType() == Material.STATIONARY_WATER || block.getType() == Material.WATER) + { + waterCount++; + if(waterCount > 10) return true; + } + + else if(block.getType() == Material.STATIONARY_LAVA || block.getType() == Material.LAVA) + { + return true; + } + } + } + } + + return false; + } + + //main constructor. note that only creating a claim instance does nothing - a claim must be added to the data store to be effective + Claim(Location lesserBoundaryCorner, Location greaterBoundaryCorner, String ownerName, String [] builderNames, String [] containerNames, String [] accessorNames, String [] managerNames, Long id) + { + //modification date + this.modifiedDate = Calendar.getInstance().getTime(); + + //id + this.id = id; + + //store corners + this.lesserBoundaryCorner = lesserBoundaryCorner; + this.greaterBoundaryCorner = greaterBoundaryCorner; + + //owner + this.ownerName = ownerName; + + //other permissions + for(int i = 0; i < builderNames.length; i++) + { + String name = builderNames[i]; + if(name != null && !name.isEmpty()) + { + this.playerNameToClaimPermissionMap.put(name, ClaimPermission.Build); + } + } + + for(int i = 0; i < containerNames.length; i++) + { + String name = containerNames[i]; + if(name != null && !name.isEmpty()) + { + this.playerNameToClaimPermissionMap.put(name, ClaimPermission.Inventory); + } + } + + for(int i = 0; i < accessorNames.length; i++) + { + String name = accessorNames[i]; + if(name != null && !name.isEmpty()) + { + this.playerNameToClaimPermissionMap.put(name, ClaimPermission.Access); + } + } + + for(int i = 0; i < managerNames.length; i++) + { + String name = managerNames[i]; + if(name != null && !name.isEmpty()) + { + this.managers.add(name); + } + } + } + + //measurements. all measurements are in blocks + public int getArea() + { + int claimWidth = this.greaterBoundaryCorner.getBlockX() - this.lesserBoundaryCorner.getBlockX() + 1; + int claimHeight = this.greaterBoundaryCorner.getBlockZ() - this.lesserBoundaryCorner.getBlockZ() + 1; + + return claimWidth * claimHeight; + } + + public int getWidth() + { + return this.greaterBoundaryCorner.getBlockX() - this.lesserBoundaryCorner.getBlockX() + 1; + } + + public int getHeight() + { + return this.greaterBoundaryCorner.getBlockZ() - this.lesserBoundaryCorner.getBlockZ() + 1; + } + + //distance check for claims, distance in this case is a band around the outside of the claim rather then euclidean distance + public boolean isNear(Location location, int howNear) + { + Claim claim = new Claim + (new Location(this.lesserBoundaryCorner.getWorld(), this.lesserBoundaryCorner.getBlockX() - howNear, this.lesserBoundaryCorner.getBlockY(), this.lesserBoundaryCorner.getBlockZ() - howNear), + new Location(this.greaterBoundaryCorner.getWorld(), this.greaterBoundaryCorner.getBlockX() + howNear, this.greaterBoundaryCorner.getBlockY(), this.greaterBoundaryCorner.getBlockZ() + howNear), + "", new String[] {}, new String[] {}, new String[] {}, new String[] {}, null); + + return claim.contains(location, false, true); + } + + //permissions. note administrative "public" claims have different rules than other claims + //all of these return NULL when a player has permission, or a String error message when the player doesn't have permission + public String allowEdit(Player player) + { + //if we don't know who's asking, always say no (i've been told some mods can make this happen somehow) + if(player == null) return ""; + + //special cases... + + //admin claims need adminclaims permission only. + if(this.isAdminClaim()) + { + if(player.hasPermission("griefprevention.adminclaims")) return null; + } + + //anyone with deleteclaims permission can modify non-admin claims at any time + else + { + if(player.hasPermission("griefprevention.deleteclaims")) return null; + } + + //no resizing, deleting, and so forth while under siege + if(this.ownerName.equals(player.getName())) + { + if(this.siegeData != null) + { + return GriefPrevention.instance.dataStore.getMessage(Messages.NoModifyDuringSiege); + } + + //otherwise, owners can do whatever + return null; + } + + //permission inheritance for subdivisions + if(this.parent != null) + return this.parent.allowBuild(player); + + //error message if all else fails + return GriefPrevention.instance.dataStore.getMessage(Messages.OnlyOwnersModifyClaims, this.getOwnerName()); + } + + //build permission check + public String allowBuild(Player player) + { + //if we don't know who's asking, always say no (i've been told some mods can make this happen somehow) + if(player == null) return ""; + + //when a player tries to build in a claim, if he's under siege, the siege may extend to include the new claim + GriefPrevention.instance.dataStore.tryExtendSiege(player, this); + + //admin claims can always be modified by admins, no exceptions + if(this.isAdminClaim()) + { + if(player.hasPermission("griefprevention.adminclaims")) return null; + } + + //no building while under siege + if(this.siegeData != null) + { + return GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildUnderSiege, this.siegeData.attacker.getName()); + } + + //no building while in pvp combat + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getName()); + if(playerData.inPvpCombat()) + { + return GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildPvP); + } + + //owners can make changes, or admins with ignore claims mode enabled + if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null; + + //anyone with explicit build permission can make changes + if(this.hasExplicitPermission(player, ClaimPermission.Build)) return null; + + //also everyone is a member of the "public", so check for public permission + ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get("public"); + if(ClaimPermission.Build == permissionLevel) return null; + + //subdivision permission inheritance + if(this.parent != null) + return this.parent.allowBuild(player); + + //failure message for all other cases + String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildPermission, this.getOwnerName()); + if(player.hasPermission("griefprevention.ignoreclaims")) + reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); + return reason; + } + + private boolean hasExplicitPermission(Player player, ClaimPermission level) + { + String playerName = player.getName(); + Set keys = this.playerNameToClaimPermissionMap.keySet(); + Iterator iterator = keys.iterator(); + while(iterator.hasNext()) + { + String identifier = iterator.next(); + if(playerName.equalsIgnoreCase(identifier) && this.playerNameToClaimPermissionMap.get(identifier) == level) return true; + + else if(identifier.startsWith("[") && identifier.endsWith("]")) + { + //drop the brackets + String permissionIdentifier = identifier.substring(1, identifier.length() - 1); + + //defensive coding + if(permissionIdentifier == null || permissionIdentifier.isEmpty()) continue; + + //check permission + if(player.hasPermission(permissionIdentifier) && this.playerNameToClaimPermissionMap.get(identifier) == level) return true; + } + } + + return false; + } + + //break permission check + public String allowBreak(Player player, Material material) + { + //if under siege, some blocks will be breakable + if(this.siegeData != null) + { + boolean breakable = false; + + //search for block type in list of breakable blocks + for(int i = 0; i < GriefPrevention.instance.config_siege_blocks.size(); i++) + { + Material breakableMaterial = GriefPrevention.instance.config_siege_blocks.get(i); + if(breakableMaterial.getId() == material.getId()) + { + breakable = true; + break; + } + } + + //custom error messages for siege mode + if(!breakable) + { + return GriefPrevention.instance.dataStore.getMessage(Messages.NonSiegeMaterial); + } + else if(this.ownerName.equals(player.getName())) + { + return GriefPrevention.instance.dataStore.getMessage(Messages.NoOwnerBuildUnderSiege); + } + else + { + return null; + } + } + + //if not under siege, build rules apply + return this.allowBuild(player); + } + + //access permission check + public String allowAccess(Player player) + { + //following a siege where the defender lost, the claim will allow everyone access for a time + if(this.doorsOpen) return null; + + //admin claims need adminclaims permission only. + if(this.isAdminClaim()) + { + if(player.hasPermission("griefprevention.adminclaims")) return null; + } + + //claim owner and admins in ignoreclaims mode have access + if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null; + + //look for explicit individual access, inventory, or build permission + if(this.hasExplicitPermission(player, ClaimPermission.Access)) return null; + if(this.hasExplicitPermission(player, ClaimPermission.Inventory)) return null; + if(this.hasExplicitPermission(player, ClaimPermission.Build)) return null; + + //also check for public permission + ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get("public"); + if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel || ClaimPermission.Access == permissionLevel) return null; + + //permission inheritance for subdivisions + if(this.parent != null) + return this.parent.allowAccess(player); + + //catch-all error message for all other cases + String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoAccessPermission, this.getOwnerName()); + if(player.hasPermission("griefprevention.ignoreclaims")) + reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); + return reason; + } + + //inventory permission check + public String allowContainers(Player player) + { + //if we don't know who's asking, always say no (i've been told some mods can make this happen somehow) + if(player == null) return ""; + + //trying to access inventory in a claim may extend an existing siege to include this claim + GriefPrevention.instance.dataStore.tryExtendSiege(player, this); + + //if under siege, nobody accesses containers + if(this.siegeData != null) + { + return GriefPrevention.instance.dataStore.getMessage(Messages.NoContainersSiege, siegeData.attacker.getName()); + } + + //owner and administrators in ignoreclaims mode have access + if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null; + + //admin claims need adminclaims permission only. + if(this.isAdminClaim()) + { + if(player.hasPermission("griefprevention.adminclaims")) return null; + } + + //check for explicit individual container or build permission + if(this.hasExplicitPermission(player, ClaimPermission.Inventory)) return null; + if(this.hasExplicitPermission(player, ClaimPermission.Build)) return null; + + //check for public container or build permission + ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get("public"); + if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel) return null; + + //permission inheritance for subdivisions + if(this.parent != null) + return this.parent.allowContainers(player); + + //error message for all other cases + String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoContainersPermission, this.getOwnerName()); + if(player.hasPermission("griefprevention.ignoreclaims")) + reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); + return reason; + } + + //grant permission check, relatively simple + public String allowGrantPermission(Player player) + { + //if we don't know who's asking, always say no (i've been told some mods can make this happen somehow) + if(player == null) return ""; + + //anyone who can modify the claim can do this + if(this.allowEdit(player) == null) return null; + + //anyone who's in the managers (/PermissionTrust) list can do this + for(int i = 0; i < this.managers.size(); i++) + { + String managerID = this.managers.get(i); + if(player.getName().equalsIgnoreCase(managerID)) return null; + + else if(managerID.startsWith("[") && managerID.endsWith("]")) + { + managerID = managerID.substring(1, managerID.length() - 1); + if(managerID == null || managerID.isEmpty()) continue; + if(player.hasPermission(managerID)) return null; + } + } + + //permission inheritance for subdivisions + if(this.parent != null) + return this.parent.allowGrantPermission(player); + + //generic error message + String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoPermissionTrust, this.getOwnerName()); + if(player.hasPermission("griefprevention.ignoreclaims")) + reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); + return reason; + } + + //grants a permission for a player or the public + public void setPermission(String playerName, ClaimPermission permissionLevel) + { + this.playerNameToClaimPermissionMap.put(playerName.toLowerCase(), permissionLevel); + } + + //revokes a permission for a player or the public + public void dropPermission(String playerName) + { + this.playerNameToClaimPermissionMap.remove(playerName.toLowerCase()); + } + + //clears all permissions (except owner of course) + public void clearPermissions() + { + this.playerNameToClaimPermissionMap.clear(); + } + + //gets ALL permissions + //useful for making copies of permissions during a claim resize and listing all permissions in a claim + public void getPermissions(ArrayList builders, ArrayList containers, ArrayList accessors, ArrayList managers) + { + //loop through all the entries in the hash map + Iterator> mappingsIterator = this.playerNameToClaimPermissionMap.entrySet().iterator(); + while(mappingsIterator.hasNext()) + { + Map.Entry entry = mappingsIterator.next(); + + //build up a list for each permission level + if(entry.getValue() == ClaimPermission.Build) + { + builders.add(entry.getKey()); + } + else if(entry.getValue() == ClaimPermission.Inventory) + { + containers.add(entry.getKey()); + } + else + { + accessors.add(entry.getKey()); + } + } + + //managers are handled a little differently + for(int i = 0; i < this.managers.size(); i++) + { + managers.add(this.managers.get(i)); + } + } + + //returns a copy of the location representing lower x, y, z limits + public Location getLesserBoundaryCorner() + { + return this.lesserBoundaryCorner.clone(); + } + + //returns a copy of the location representing upper x, y, z limits + //NOTE: remember upper Y will always be ignored, all claims always extend to the sky + public Location getGreaterBoundaryCorner() + { + return this.greaterBoundaryCorner.clone(); + } + + //returns a friendly owner name (for admin claims, returns "an administrator" as the owner) + public String getOwnerName() + { + if(this.parent != null) + return this.parent.getOwnerName(); + + if(this.ownerName.length() == 0) + return GriefPrevention.instance.dataStore.getMessage(Messages.OwnerNameForAdminClaims); + + return this.ownerName; + } + + //whether or not a location is in a claim + //ignoreHeight = true means location UNDER the claim will return TRUE + //excludeSubdivisions = true means that locations inside subdivisions of the claim will return FALSE + public boolean contains(Location location, boolean ignoreHeight, boolean excludeSubdivisions) + { + //not in the same world implies false + if(!location.getWorld().equals(this.lesserBoundaryCorner.getWorld())) return false; + + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + + //main check + boolean inClaim = (ignoreHeight || y >= this.lesserBoundaryCorner.getBlockY()) && + x >= this.lesserBoundaryCorner.getBlockX() && + x <= this.greaterBoundaryCorner.getBlockX() && + z >= this.lesserBoundaryCorner.getBlockZ() && + z <= this.greaterBoundaryCorner.getBlockZ(); + + if(!inClaim) return false; + + //additional check for subdivisions + //you're only in a subdivision when you're also in its parent claim + //NOTE: if a player creates subdivions then resizes the parent claim, it's possible that + //a subdivision can reach outside of its parent's boundaries. so this check is important! + if(this.parent != null) + { + return this.parent.contains(location, ignoreHeight, false); + } + + //code to exclude subdivisions in this check + else if(excludeSubdivisions) + { + //search all subdivisions to see if the location is in any of them + for(int i = 0; i < this.children.size(); i++) + { + //if we find such a subdivision, return false + if(this.children.get(i).contains(location, ignoreHeight, true)) + { + return false; + } + } + } + + //otherwise yes + return true; + } + + //whether or not two claims overlap + //used internally to prevent overlaps when creating claims + boolean overlaps(Claim otherClaim) + { + //NOTE: if trying to understand this makes your head hurt, don't feel bad - it hurts mine too. + //try drawing pictures to visualize test cases. + + if(!this.lesserBoundaryCorner.getWorld().equals(otherClaim.getLesserBoundaryCorner().getWorld())) return false; + + //first, check the corners of this claim aren't inside any existing claims + if(otherClaim.contains(this.lesserBoundaryCorner, true, false)) return true; + if(otherClaim.contains(this.greaterBoundaryCorner, true, false)) return true; + if(otherClaim.contains(new Location(this.lesserBoundaryCorner.getWorld(), this.lesserBoundaryCorner.getBlockX(), 0, this.greaterBoundaryCorner.getBlockZ()), true, false)) return true; + if(otherClaim.contains(new Location(this.lesserBoundaryCorner.getWorld(), this.greaterBoundaryCorner.getBlockX(), 0, this.lesserBoundaryCorner.getBlockZ()), true, false)) return true; + + //verify that no claim's lesser boundary point is inside this new claim, to cover the "existing claim is entirely inside new claim" case + if(this.contains(otherClaim.getLesserBoundaryCorner(), true, false)) return true; + + //verify this claim doesn't band across an existing claim, either horizontally or vertically + if( this.getLesserBoundaryCorner().getBlockZ() <= otherClaim.getGreaterBoundaryCorner().getBlockZ() && + this.getLesserBoundaryCorner().getBlockZ() >= otherClaim.getLesserBoundaryCorner().getBlockZ() && + this.getLesserBoundaryCorner().getBlockX() < otherClaim.getLesserBoundaryCorner().getBlockX() && + this.getGreaterBoundaryCorner().getBlockX() > otherClaim.getGreaterBoundaryCorner().getBlockX() ) + return true; + + if( this.getGreaterBoundaryCorner().getBlockZ() <= otherClaim.getGreaterBoundaryCorner().getBlockZ() && + this.getGreaterBoundaryCorner().getBlockZ() >= otherClaim.getLesserBoundaryCorner().getBlockZ() && + this.getLesserBoundaryCorner().getBlockX() < otherClaim.getLesserBoundaryCorner().getBlockX() && + this.getGreaterBoundaryCorner().getBlockX() > otherClaim.getGreaterBoundaryCorner().getBlockX() ) + return true; + + if( this.getLesserBoundaryCorner().getBlockX() <= otherClaim.getGreaterBoundaryCorner().getBlockX() && + this.getLesserBoundaryCorner().getBlockX() >= otherClaim.getLesserBoundaryCorner().getBlockX() && + this.getLesserBoundaryCorner().getBlockZ() < otherClaim.getLesserBoundaryCorner().getBlockZ() && + this.getGreaterBoundaryCorner().getBlockZ() > otherClaim.getGreaterBoundaryCorner().getBlockZ() ) + return true; + + if( this.getGreaterBoundaryCorner().getBlockX() <= otherClaim.getGreaterBoundaryCorner().getBlockX() && + this.getGreaterBoundaryCorner().getBlockX() >= otherClaim.getLesserBoundaryCorner().getBlockX() && + this.getLesserBoundaryCorner().getBlockZ() < otherClaim.getLesserBoundaryCorner().getBlockZ() && + this.getGreaterBoundaryCorner().getBlockZ() > otherClaim.getGreaterBoundaryCorner().getBlockZ() ) + return true; + + return false; + } + + //whether more entities may be added to a claim + public String allowMoreEntities() + { + if(this.parent != null) return this.parent.allowMoreEntities(); + + //this rule only applies to creative mode worlds + if(!GriefPrevention.instance.creativeRulesApply(this.getLesserBoundaryCorner())) return null; + + //admin claims aren't restricted + if(this.isAdminClaim()) return null; + + //don't apply this rule to very large claims + if(this.getArea() > 10000) return null; + + //determine maximum allowable entity count, based on claim size + int maxEntities = this.getArea() / 50; + if(maxEntities == 0) return GriefPrevention.instance.dataStore.getMessage(Messages.ClaimTooSmallForEntities); + + //count current entities (ignoring players) + Chunk lesserChunk = this.getLesserBoundaryCorner().getChunk(); + Chunk greaterChunk = this.getGreaterBoundaryCorner().getChunk(); + + int totalEntities = 0; + for(int x = lesserChunk.getX(); x <= greaterChunk.getX(); x++) + for(int z = lesserChunk.getZ(); z <= greaterChunk.getZ(); z++) + { + Chunk chunk = lesserChunk.getWorld().getChunkAt(x, z); + Entity [] entities = chunk.getEntities(); + for(int i = 0; i < entities.length; i++) + { + Entity entity = entities[i]; + if(!(entity instanceof Player) && this.contains(entity.getLocation(), false, false)) + { + totalEntities++; + if(totalEntities > maxEntities) entity.remove(); + } + } + } + + if(totalEntities > maxEntities) return GriefPrevention.instance.dataStore.getMessage(Messages.TooManyEntitiesInClaim); + + return null; + } + + //implements a strict ordering of claims, used to keep the claims collection sorted for faster searching + boolean greaterThan(Claim otherClaim) + { + Location thisCorner = this.getLesserBoundaryCorner(); + Location otherCorner = otherClaim.getLesserBoundaryCorner(); + + if(thisCorner.getBlockX() > otherCorner.getBlockX()) return true; + + if(thisCorner.getBlockX() < otherCorner.getBlockX()) return false; + + if(thisCorner.getBlockZ() > otherCorner.getBlockZ()) return true; + + if(thisCorner.getBlockZ() < otherCorner.getBlockZ()) return false; + + return thisCorner.getWorld().getName().compareTo(otherCorner.getWorld().getName()) < 0; + } + + long getPlayerInvestmentScore() + { + //decide which blocks will be considered player placed + Location lesserBoundaryCorner = this.getLesserBoundaryCorner(); + ArrayList playerBlocks = RestoreNatureProcessingTask.getPlayerBlocks(lesserBoundaryCorner.getWorld().getEnvironment(), lesserBoundaryCorner.getBlock().getBiome()); + + //scan the claim for player placed blocks + double score = 0; + + boolean creativeMode = GriefPrevention.instance.creativeRulesApply(lesserBoundaryCorner); + + for(int x = this.lesserBoundaryCorner.getBlockX(); x <= this.greaterBoundaryCorner.getBlockX(); x++) + { + for(int z = this.lesserBoundaryCorner.getBlockZ(); z <= this.greaterBoundaryCorner.getBlockZ(); z++) + { + int y = this.lesserBoundaryCorner.getBlockY(); + for(; y < GriefPrevention.instance.getSeaLevel(this.lesserBoundaryCorner.getWorld()) - 5; y++) + { + Block block = this.lesserBoundaryCorner.getWorld().getBlockAt(x, y, z); + if(playerBlocks.contains(block.getTypeId())) + { + if(block.getType() == Material.CHEST && !creativeMode) + { + score += 10; + } + else + { + score += .5; + } + } + } + + for(; y < this.lesserBoundaryCorner.getWorld().getMaxHeight(); y++) + { + Block block = this.lesserBoundaryCorner.getWorld().getBlockAt(x, y, z); + if(playerBlocks.contains(block.getTypeId())) + { + if(block.getType() == Material.CHEST && !creativeMode) + { + score += 10; + } + else if(creativeMode && (block.getType() == Material.LAVA || block.getType() == Material.STATIONARY_LAVA)) + { + score -= 10; + } + else + { + score += 1; + } + } + } + } + } + + return (long)score; + } +} diff --git a/src/me/ryanhamshire/GriefPrevention/DataStore.java b/src/me/ryanhamshire/GriefPrevention/DataStore.java index 370a1ce..b846aee 100644 --- a/src/me/ryanhamshire/GriefPrevention/DataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DataStore.java @@ -963,7 +963,11 @@ public abstract class DataStore this.addDefault(defaults, Messages.NoTNTDamageClaims, "Warning: TNT will not destroy claimed blocks.", null); this.addDefault(defaults, Messages.IgnoreClaimsAdvertisement, "To override, use /IgnoreClaims.", null); this.addDefault(defaults, Messages.NoPermissionForCommand, "You don't have permission to do that.", null); - this.addDefault(defaults, Messages.ClaimsListNoPermission, "You don't have permission to get information about another player's land claims.", null); + this.addDefault(defaults, Messages.ClaimsListNoPermission, "You don't have permission to get information about another player's land claims.", null); + this.addDefault(defaults, Messages.ExplosivesDisabled, "This claim is now protected from explosions. Use /ClaimExplosions again to disable.", null); + this.addDefault(defaults, Messages.ExplosivesEnabled, "This claim is now vulnerable to explosions. Use /ClaimExplosions again to re-enable protections.", null); + this.addDefault(defaults, Messages.ClaimExplosivesAdvertisement, "To allow explosives to destroy blocks in this land claim, use /ClaimExplosions.", null); + this.addDefault(defaults, Messages.PlayerInPvPSafeZone, "That player is in a PvP safe zone.", null); //load the config file FileConfiguration config = YamlConfiguration.loadConfiguration(new File(messagesFilePath)); diff --git a/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java b/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java index 76e41fd..7fd9bb6 100644 --- a/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java @@ -1,546 +1,572 @@ -/* - GriefPrevention Server Plugin for Minecraft - Copyright (C) 2012 Ryan Hamshire - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -package me.ryanhamshire.GriefPrevention; - -import java.util.Calendar; -import java.util.List; - -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World.Environment; -import org.bukkit.block.Block; -import org.bukkit.entity.Arrow; -import org.bukkit.entity.Creature; -import org.bukkit.entity.Creeper; -import org.bukkit.entity.Enderman; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Monster; -import org.bukkit.entity.Player; -import org.bukkit.entity.ThrownPotion; -import org.bukkit.entity.Villager; - -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.entity.CreatureSpawnEvent; -import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; -import org.bukkit.event.entity.EntityBreakDoorEvent; -import org.bukkit.event.entity.EntityChangeBlockEvent; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.entity.EntityExplodeEvent; -import org.bukkit.event.entity.EntityInteractEvent; -import org.bukkit.event.entity.ExpBottleEvent; -import org.bukkit.event.entity.ItemSpawnEvent; -import org.bukkit.event.hanging.HangingBreakByEntityEvent; -import org.bukkit.event.hanging.HangingBreakEvent; -import org.bukkit.event.hanging.HangingPlaceEvent; -import org.bukkit.event.vehicle.VehicleDamageEvent; - -//handles events related to entities -class EntityEventHandler implements Listener -{ - //convenience reference for the singleton datastore - private DataStore dataStore; - - public EntityEventHandler(DataStore dataStore) - { - this.dataStore = dataStore; - } - - //don't allow endermen to change blocks - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onEntityChangeBLock(EntityChangeBlockEvent event) - { - if(!GriefPrevention.instance.config_endermenMoveBlocks && event.getEntityType() == EntityType.ENDERMAN) - { - event.setCancelled(true); - } - - else if(!GriefPrevention.instance.config_silverfishBreakBlocks && event.getEntityType() == EntityType.SILVERFISH) - { - event.setCancelled(true); - } - - //don't allow the wither to break blocks, when the wither is determined, too expensive to constantly check for claimed blocks - else if(event.getEntityType() == EntityType.WITHER) - { - event.setCancelled(true); - } - } - - //don't allow zombies to break down doors - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onZombieBreakDoor(EntityBreakDoorEvent event) - { - if(!GriefPrevention.instance.config_zombiesBreakDoors) event.setCancelled(true); - } - - //don't allow entities to trample crops - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onEntityInteract(EntityInteractEvent event) - { - if(!GriefPrevention.instance.config_creaturesTrampleCrops && event.getBlock().getType() == Material.SOIL) - { - event.setCancelled(true); - } - } - - //when an entity explodes... - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onEntityExplode(EntityExplodeEvent explodeEvent) - { - List blocks = explodeEvent.blockList(); - Location location = explodeEvent.getLocation(); - - //FEATURE: explosions don't destroy blocks when they explode near or above sea level in standard worlds - boolean isCreeper = (explodeEvent.getEntity() != null && explodeEvent.getEntity() instanceof Creeper); - if( location.getWorld().getEnvironment() == Environment.NORMAL && GriefPrevention.instance.config_claims_enabledWorlds.contains(location.getWorld()) && ((isCreeper && GriefPrevention.instance.config_blockSurfaceCreeperExplosions) || (!isCreeper && GriefPrevention.instance.config_blockSurfaceOtherExplosions))) - { - for(int i = 0; i < blocks.size(); i++) - { - Block block = blocks.get(i); - if(GriefPrevention.instance.config_mods_explodableIds.Contains(new MaterialInfo(block.getTypeId(), block.getData(), null))) continue; - - if(block.getLocation().getBlockY() > GriefPrevention.instance.getSeaLevel(location.getWorld()) - 7) - { - blocks.remove(i--); - } - } - } - - //special rule for creative worlds: explosions don't destroy anything - if(GriefPrevention.instance.creativeRulesApply(explodeEvent.getLocation())) - { - for(int i = 0; i < blocks.size(); i++) - { - Block block = blocks.get(i); - if(GriefPrevention.instance.config_mods_explodableIds.Contains(new MaterialInfo(block.getTypeId(), block.getData(), null))) continue; - - blocks.remove(i--); - } - } - - //FEATURE: explosions don't damage claimed blocks - Claim claim = null; - for(int i = 0; i < blocks.size(); i++) //for each destroyed block - { - Block block = blocks.get(i); - if(block.getType() == Material.AIR) continue; //if it's air, we don't care - - if(GriefPrevention.instance.config_mods_explodableIds.Contains(new MaterialInfo(block.getTypeId(), block.getData(), null))) continue; - - claim = this.dataStore.getClaimAt(block.getLocation(), false, claim); - //if the block is claimed, remove it from the list of destroyed blocks - if(claim != null) - { - blocks.remove(i--); - } - - //if the block is not claimed and is a log, trigger the anti-tree-top code - else if(block.getType() == Material.LOG) - { - GriefPrevention.instance.handleLogBroken(block); - } - } - } - - //when an item spawns... - @EventHandler(priority = EventPriority.LOWEST) - public void onItemSpawn(ItemSpawnEvent event) - { - //if in a creative world, cancel the event (don't drop items on the ground) - if(GriefPrevention.instance.creativeRulesApply(event.getLocation())) - { - event.setCancelled(true); - } - } - - //when an experience bottle explodes... - @EventHandler(priority = EventPriority.LOWEST) - public void onExpBottle(ExpBottleEvent event) - { - //if in a creative world, cancel the event (don't drop exp on the ground) - if(GriefPrevention.instance.creativeRulesApply(event.getEntity().getLocation())) - { - event.setExperience(0); - } - } - - //when a creature spawns... - @EventHandler(priority = EventPriority.LOWEST) - public void onEntitySpawn(CreatureSpawnEvent event) - { - LivingEntity entity = event.getEntity(); - - //these rules apply only to creative worlds - if(!GriefPrevention.instance.creativeRulesApply(entity.getLocation())) return; - - //chicken eggs and breeding could potentially make a mess in the wilderness, once griefers get involved - SpawnReason reason = event.getSpawnReason(); - if(reason != SpawnReason.SPAWNER_EGG && reason != SpawnReason.BUILD_IRONGOLEM && reason != SpawnReason.BUILD_SNOWMAN) - { - event.setCancelled(true); - return; - } - - //otherwise, just apply the limit on total entities per claim (and no spawning in the wilderness!) - Claim claim = this.dataStore.getClaimAt(event.getLocation(), false, null); - if(claim == null || claim.allowMoreEntities() != null) - { - event.setCancelled(true); - return; - } - } - - //when an entity dies... - @EventHandler - public void onEntityDeath(EntityDeathEvent event) - { - LivingEntity entity = event.getEntity(); - - //special rule for creative worlds: killed entities don't drop items or experience orbs - if(GriefPrevention.instance.creativeRulesApply(entity.getLocation())) - { - event.setDroppedExp(0); - event.getDrops().clear(); - } - - //FEATURE: when a player is involved in a siege (attacker or defender role) - //his death will end the siege - - if(!(entity instanceof Player)) return; //only tracking players - - Player player = (Player)entity; - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); - - //if involved in a siege - if(playerData.siegeData != null) - { - //don't drop items as usual, they will be sent to the siege winner - event.getDrops().clear(); - - //end it, with the dieing player being the loser - this.dataStore.endSiege(playerData.siegeData, null, player.getName(), true /*ended due to death*/); - } - } - - //when an entity picks up an item - @EventHandler(priority = EventPriority.LOWEST) - public void onEntityPickup(EntityChangeBlockEvent event) - { - //FEATURE: endermen don't steal claimed blocks - - //if its an enderman - if(event.getEntity() instanceof Enderman) - { - //and the block is claimed - if(this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null) != null) - { - //he doesn't get to steal it - event.setCancelled(true); - } - } - } - - //when a painting is broken - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onHangingBreak(HangingBreakEvent event) - { - //FEATURE: claimed paintings are protected from breakage - - //only allow players to break paintings, not anything else (like water and explosions) - if(!(event instanceof HangingBreakByEntityEvent)) - { - event.setCancelled(true); - return; - } - - HangingBreakByEntityEvent entityEvent = (HangingBreakByEntityEvent)event; - - //who is removing it? - Entity remover = entityEvent.getRemover(); - - //again, making sure the breaker is a player - if(!(remover instanceof Player)) - { - event.setCancelled(true); - return; - } - - //if the player doesn't have build permission, don't allow the breakage - Player playerRemover = (Player)entityEvent.getRemover(); - String noBuildReason = GriefPrevention.instance.allowBuild(playerRemover, event.getEntity().getLocation()); - if(noBuildReason != null) - { - event.setCancelled(true); - GriefPrevention.sendMessage(playerRemover, TextMode.Err, noBuildReason); - } - } - - //when a painting is placed... - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPaintingPlace(HangingPlaceEvent event) - { - //FEATURE: similar to above, placing a painting requires build permission in the claim - - //if the player doesn't have permission, don't allow the placement - String noBuildReason = GriefPrevention.instance.allowBuild(event.getPlayer(), event.getEntity().getLocation()); - if(noBuildReason != null) - { - event.setCancelled(true); - GriefPrevention.sendMessage(event.getPlayer(), TextMode.Err, noBuildReason); - return; - } - - //otherwise, apply entity-count limitations for creative worlds - else if(GriefPrevention.instance.creativeRulesApply(event.getEntity().getLocation())) - { - PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getName()); - Claim claim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, playerData.lastClaim); - if(claim == null) return; - - String noEntitiesReason = claim.allowMoreEntities(); - if(noEntitiesReason != null) - { - GriefPrevention.sendMessage(event.getPlayer(), TextMode.Err, noEntitiesReason); - event.setCancelled(true); - return; - } - } - } - - //when an entity is damaged - @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) - public void onEntityDamage (EntityDamageEvent event) - { - //only actually interested in entities damaging entities (ignoring environmental damage) - if(!(event instanceof EntityDamageByEntityEvent)) return; - - //monsters are never protected - if(event.getEntity() instanceof Monster) return; - - EntityDamageByEntityEvent subEvent = (EntityDamageByEntityEvent) event; - - //determine which player is attacking, if any - Player attacker = null; - Arrow arrow = null; - Entity damageSource = subEvent.getDamager(); - if(damageSource instanceof Player) - { - attacker = (Player)damageSource; - } - else if(damageSource instanceof Arrow) - { - arrow = (Arrow)damageSource; - if(arrow.getShooter() instanceof Player) - { - attacker = (Player)arrow.getShooter(); - } - } - else if(damageSource instanceof ThrownPotion) - { - ThrownPotion potion = (ThrownPotion)damageSource; - if(potion.getShooter() instanceof Player) - { - attacker = (Player)potion.getShooter(); - } - } - - //if the attacker is a player and defender is a player (pvp combat) - if(attacker != null && event.getEntity() instanceof Player && GriefPrevention.instance.config_pvp_enabledWorlds.contains(attacker.getWorld())) - { - //FEATURE: prevent pvp in the first minute after spawn, and prevent pvp when one or both players have no inventory - - //doesn't apply when the attacker has the no pvp immunity permission - //this rule is here to allow server owners to have a world with no spawn camp protection by assigning permissions based on the player's world - if(attacker.hasPermission("griefprevention.nopvpimmunity")) return; - - Player defender = (Player)(event.getEntity()); - - PlayerData defenderData = this.dataStore.getPlayerData(((Player)event.getEntity()).getName()); - PlayerData attackerData = this.dataStore.getPlayerData(attacker.getName()); - - //otherwise if protecting spawning players - if(GriefPrevention.instance.config_pvp_protectFreshSpawns) - { - if(defenderData.pvpImmune) - { - event.setCancelled(true); - GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.ThatPlayerPvPImmune); - return; - } - - if(attackerData.pvpImmune) - { - event.setCancelled(true); - GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune); - return; - } - } - - //FEATURE: prevent players who very recently participated in pvp combat from hiding inventory to protect it from looting - //FEATURE: prevent players who are in pvp combat from logging out to avoid being defeated - - long now = Calendar.getInstance().getTimeInMillis(); - defenderData.lastPvpTimestamp = now; - defenderData.lastPvpPlayer = attacker.getName(); - attackerData.lastPvpTimestamp = now; - attackerData.lastPvpPlayer = defender.getName(); - } - - //FEATURE: protect claimed animals, boats, minecarts - //NOTE: animals can be lead with wheat, vehicles can be pushed around. - //so unless precautions are taken by the owner, a resourceful thief might find ways to steal anyway - - //if theft protection is enabled - if(event instanceof EntityDamageByEntityEvent) - { - //if the entity is an non-monster creature (remember monsters disqualified above), or a vehicle - if ((subEvent.getEntity() instanceof Creature && GriefPrevention.instance.config_claims_protectCreatures)) - { - Claim cachedClaim = null; - PlayerData playerData = null; - if(attacker != null) - { - playerData = this.dataStore.getPlayerData(attacker.getName()); - cachedClaim = playerData.lastClaim; - } - - Claim claim = this.dataStore.getClaimAt(event.getEntity().getLocation(), false, cachedClaim); - - //if it's claimed - if(claim != null) - { - //if damaged by anything other than a player (exception villagers injured by zombies in admin claims), cancel the event - //why exception? so admins can set up a village which can't be CHANGED by players, but must be "protected" by players. - if(attacker == null) - { - //exception case - if(event.getEntity() instanceof Villager && damageSource instanceof Monster && claim.isAdminClaim()) - { - return; - } - - //all other cases - else - { - event.setCancelled(true); - } - } - - //otherwise the player damaging the entity must have permission - else - { - String noContainersReason = claim.allowContainers(attacker); - if(noContainersReason != null) - { - event.setCancelled(true); - - //kill the arrow to avoid infinite bounce between crowded together animals - if(arrow != null) arrow.remove(); - - GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.NoDamageClaimedEntity, claim.getOwnerName()); - } - - //cache claim for later - if(playerData != null) - { - playerData.lastClaim = claim; - } - } - } - } - } - } - - //when a vehicle is damaged - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onVehicleDamage (VehicleDamageEvent event) - { - //all of this is anti theft code - if(!GriefPrevention.instance.config_claims_preventTheft) return; - - //determine which player is attacking, if any - Player attacker = null; - Entity damageSource = event.getAttacker(); - if(damageSource instanceof Player) - { - attacker = (Player)damageSource; - } - else if(damageSource instanceof Arrow) - { - Arrow arrow = (Arrow)damageSource; - if(arrow.getShooter() instanceof Player) - { - attacker = (Player)arrow.getShooter(); - } - } - else if(damageSource instanceof ThrownPotion) - { - ThrownPotion potion = (ThrownPotion)damageSource; - if(potion.getShooter() instanceof Player) - { - attacker = (Player)potion.getShooter(); - } - } - - //NOTE: vehicles can be pushed around. - //so unless precautions are taken by the owner, a resourceful thief might find ways to steal anyway - Claim cachedClaim = null; - PlayerData playerData = null; - if(attacker != null) - { - playerData = this.dataStore.getPlayerData(attacker.getName()); - cachedClaim = playerData.lastClaim; - } - - Claim claim = this.dataStore.getClaimAt(event.getVehicle().getLocation(), false, cachedClaim); - - //if it's claimed - if(claim != null) - { - //if damaged by anything other than a player, cancel the event - if(attacker == null) - { - event.setCancelled(true); - } - - //otherwise the player damaging the entity must have permission - else - { - String noContainersReason = claim.allowContainers(attacker); - if(noContainersReason != null) - { - event.setCancelled(true); - GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.NoDamageClaimedEntity, claim.getOwnerName()); - } - - //cache claim for later - if(playerData != null) - { - playerData.lastClaim = claim; - } - } - } - } -} +/* + GriefPrevention Server Plugin for Minecraft + Copyright (C) 2012 Ryan Hamshire + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package me.ryanhamshire.GriefPrevention; + +import java.util.Calendar; +import java.util.List; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World.Environment; +import org.bukkit.block.Block; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Creature; +import org.bukkit.entity.Creeper; +import org.bukkit.entity.Enderman; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Monster; +import org.bukkit.entity.Player; +import org.bukkit.entity.ThrownPotion; +import org.bukkit.entity.Villager; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; +import org.bukkit.event.entity.EntityBreakDoorEvent; +import org.bukkit.event.entity.EntityChangeBlockEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.entity.EntityInteractEvent; +import org.bukkit.event.entity.ExpBottleEvent; +import org.bukkit.event.entity.ItemSpawnEvent; +import org.bukkit.event.hanging.HangingBreakByEntityEvent; +import org.bukkit.event.hanging.HangingBreakEvent; +import org.bukkit.event.hanging.HangingPlaceEvent; +import org.bukkit.event.vehicle.VehicleDamageEvent; + +//handles events related to entities +class EntityEventHandler implements Listener +{ + //convenience reference for the singleton datastore + private DataStore dataStore; + + public EntityEventHandler(DataStore dataStore) + { + this.dataStore = dataStore; + } + + //don't allow endermen to change blocks + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onEntityChangeBLock(EntityChangeBlockEvent event) + { + if(!GriefPrevention.instance.config_endermenMoveBlocks && event.getEntityType() == EntityType.ENDERMAN) + { + event.setCancelled(true); + } + + else if(!GriefPrevention.instance.config_silverfishBreakBlocks && event.getEntityType() == EntityType.SILVERFISH) + { + event.setCancelled(true); + } + + //don't allow the wither to break blocks, when the wither is determined, too expensive to constantly check for claimed blocks + else if(event.getEntityType() == EntityType.WITHER) + { + event.setCancelled(true); + } + } + + //don't allow zombies to break down doors + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onZombieBreakDoor(EntityBreakDoorEvent event) + { + if(!GriefPrevention.instance.config_zombiesBreakDoors) event.setCancelled(true); + } + + //don't allow entities to trample crops + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onEntityInteract(EntityInteractEvent event) + { + if(!GriefPrevention.instance.config_creaturesTrampleCrops && event.getBlock().getType() == Material.SOIL) + { + event.setCancelled(true); + } + } + + //when an entity explodes... + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onEntityExplode(EntityExplodeEvent explodeEvent) + { + List blocks = explodeEvent.blockList(); + Location location = explodeEvent.getLocation(); + + //FEATURE: explosions don't destroy blocks when they explode near or above sea level in standard worlds + boolean isCreeper = (explodeEvent.getEntity() != null && explodeEvent.getEntity() instanceof Creeper); + if( location.getWorld().getEnvironment() == Environment.NORMAL && GriefPrevention.instance.config_claims_enabledWorlds.contains(location.getWorld()) && ((isCreeper && GriefPrevention.instance.config_blockSurfaceCreeperExplosions) || (!isCreeper && GriefPrevention.instance.config_blockSurfaceOtherExplosions))) + { + for(int i = 0; i < blocks.size(); i++) + { + Block block = blocks.get(i); + if(GriefPrevention.instance.config_mods_explodableIds.Contains(new MaterialInfo(block.getTypeId(), block.getData(), null))) continue; + + if(block.getLocation().getBlockY() > GriefPrevention.instance.getSeaLevel(location.getWorld()) - 7) + { + blocks.remove(i--); + } + } + } + + //special rule for creative worlds: explosions don't destroy anything + if(GriefPrevention.instance.creativeRulesApply(explodeEvent.getLocation())) + { + for(int i = 0; i < blocks.size(); i++) + { + Block block = blocks.get(i); + if(GriefPrevention.instance.config_mods_explodableIds.Contains(new MaterialInfo(block.getTypeId(), block.getData(), null))) continue; + + blocks.remove(i--); + } + } + + //FEATURE: explosions don't damage claimed blocks + Claim claim = null; + for(int i = 0; i < blocks.size(); i++) //for each destroyed block + { + Block block = blocks.get(i); + if(block.getType() == Material.AIR) continue; //if it's air, we don't care + + if(GriefPrevention.instance.config_mods_explodableIds.Contains(new MaterialInfo(block.getTypeId(), block.getData(), null))) continue; + + claim = this.dataStore.getClaimAt(block.getLocation(), false, claim); + //if the block is claimed, remove it from the list of destroyed blocks + if(claim != null && !claim.areExplosivesAllowed) + { + blocks.remove(i--); + } + + //if the block is not claimed and is a log, trigger the anti-tree-top code + else if(block.getType() == Material.LOG) + { + GriefPrevention.instance.handleLogBroken(block); + } + } + } + + //when an item spawns... + @EventHandler(priority = EventPriority.LOWEST) + public void onItemSpawn(ItemSpawnEvent event) + { + //if in a creative world, cancel the event (don't drop items on the ground) + if(GriefPrevention.instance.creativeRulesApply(event.getLocation())) + { + event.setCancelled(true); + } + } + + //when an experience bottle explodes... + @EventHandler(priority = EventPriority.LOWEST) + public void onExpBottle(ExpBottleEvent event) + { + //if in a creative world, cancel the event (don't drop exp on the ground) + if(GriefPrevention.instance.creativeRulesApply(event.getEntity().getLocation())) + { + event.setExperience(0); + } + } + + //when a creature spawns... + @EventHandler(priority = EventPriority.LOWEST) + public void onEntitySpawn(CreatureSpawnEvent event) + { + LivingEntity entity = event.getEntity(); + + //these rules apply only to creative worlds + if(!GriefPrevention.instance.creativeRulesApply(entity.getLocation())) return; + + //chicken eggs and breeding could potentially make a mess in the wilderness, once griefers get involved + SpawnReason reason = event.getSpawnReason(); + if(reason != SpawnReason.SPAWNER_EGG && reason != SpawnReason.BUILD_IRONGOLEM && reason != SpawnReason.BUILD_SNOWMAN) + { + event.setCancelled(true); + return; + } + + //otherwise, just apply the limit on total entities per claim (and no spawning in the wilderness!) + Claim claim = this.dataStore.getClaimAt(event.getLocation(), false, null); + if(claim == null || claim.allowMoreEntities() != null) + { + event.setCancelled(true); + return; + } + } + + //when an entity dies... + @EventHandler + public void onEntityDeath(EntityDeathEvent event) + { + LivingEntity entity = event.getEntity(); + + //special rule for creative worlds: killed entities don't drop items or experience orbs + if(GriefPrevention.instance.creativeRulesApply(entity.getLocation())) + { + event.setDroppedExp(0); + event.getDrops().clear(); + } + + //FEATURE: when a player is involved in a siege (attacker or defender role) + //his death will end the siege + + if(!(entity instanceof Player)) return; //only tracking players + + Player player = (Player)entity; + PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + + //if involved in a siege + if(playerData.siegeData != null) + { + //don't drop items as usual, they will be sent to the siege winner + event.getDrops().clear(); + + //end it, with the dieing player being the loser + this.dataStore.endSiege(playerData.siegeData, null, player.getName(), true /*ended due to death*/); + } + } + + //when an entity picks up an item + @EventHandler(priority = EventPriority.LOWEST) + public void onEntityPickup(EntityChangeBlockEvent event) + { + //FEATURE: endermen don't steal claimed blocks + + //if its an enderman + if(event.getEntity() instanceof Enderman) + { + //and the block is claimed + if(this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null) != null) + { + //he doesn't get to steal it + event.setCancelled(true); + } + } + } + + //when a painting is broken + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onHangingBreak(HangingBreakEvent event) + { + //FEATURE: claimed paintings are protected from breakage + + //only allow players to break paintings, not anything else (like water and explosions) + if(!(event instanceof HangingBreakByEntityEvent)) + { + event.setCancelled(true); + return; + } + + HangingBreakByEntityEvent entityEvent = (HangingBreakByEntityEvent)event; + + //who is removing it? + Entity remover = entityEvent.getRemover(); + + //again, making sure the breaker is a player + if(!(remover instanceof Player)) + { + event.setCancelled(true); + return; + } + + //if the player doesn't have build permission, don't allow the breakage + Player playerRemover = (Player)entityEvent.getRemover(); + String noBuildReason = GriefPrevention.instance.allowBuild(playerRemover, event.getEntity().getLocation()); + if(noBuildReason != null) + { + event.setCancelled(true); + GriefPrevention.sendMessage(playerRemover, TextMode.Err, noBuildReason); + } + } + + //when a painting is placed... + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPaintingPlace(HangingPlaceEvent event) + { + //FEATURE: similar to above, placing a painting requires build permission in the claim + + //if the player doesn't have permission, don't allow the placement + String noBuildReason = GriefPrevention.instance.allowBuild(event.getPlayer(), event.getEntity().getLocation()); + if(noBuildReason != null) + { + event.setCancelled(true); + GriefPrevention.sendMessage(event.getPlayer(), TextMode.Err, noBuildReason); + return; + } + + //otherwise, apply entity-count limitations for creative worlds + else if(GriefPrevention.instance.creativeRulesApply(event.getEntity().getLocation())) + { + PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getName()); + Claim claim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, playerData.lastClaim); + if(claim == null) return; + + String noEntitiesReason = claim.allowMoreEntities(); + if(noEntitiesReason != null) + { + GriefPrevention.sendMessage(event.getPlayer(), TextMode.Err, noEntitiesReason); + event.setCancelled(true); + return; + } + } + } + + //when an entity is damaged + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) + public void onEntityDamage (EntityDamageEvent event) + { + //only actually interested in entities damaging entities (ignoring environmental damage) + if(!(event instanceof EntityDamageByEntityEvent)) return; + + //monsters are never protected + if(event.getEntity() instanceof Monster) return; + + EntityDamageByEntityEvent subEvent = (EntityDamageByEntityEvent) event; + + //determine which player is attacking, if any + Player attacker = null; + Arrow arrow = null; + Entity damageSource = subEvent.getDamager(); + if(damageSource instanceof Player) + { + attacker = (Player)damageSource; + } + else if(damageSource instanceof Arrow) + { + arrow = (Arrow)damageSource; + if(arrow.getShooter() instanceof Player) + { + attacker = (Player)arrow.getShooter(); + } + } + else if(damageSource instanceof ThrownPotion) + { + ThrownPotion potion = (ThrownPotion)damageSource; + if(potion.getShooter() instanceof Player) + { + attacker = (Player)potion.getShooter(); + } + } + + //if the attacker is a player and defender is a player (pvp combat) + if(attacker != null && event.getEntity() instanceof Player && GriefPrevention.instance.config_pvp_enabledWorlds.contains(attacker.getWorld())) + { + //FEATURE: prevent pvp in the first minute after spawn, and prevent pvp when one or both players have no inventory + + //doesn't apply when the attacker has the no pvp immunity permission + //this rule is here to allow server owners to have a world with no spawn camp protection by assigning permissions based on the player's world + if(attacker.hasPermission("griefprevention.nopvpimmunity")) return; + + Player defender = (Player)(event.getEntity()); + + PlayerData defenderData = this.dataStore.getPlayerData(((Player)event.getEntity()).getName()); + PlayerData attackerData = this.dataStore.getPlayerData(attacker.getName()); + + //otherwise if protecting spawning players + if(GriefPrevention.instance.config_pvp_protectFreshSpawns) + { + if(defenderData.pvpImmune) + { + event.setCancelled(true); + GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.ThatPlayerPvPImmune); + return; + } + + if(attackerData.pvpImmune) + { + event.setCancelled(true); + GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune); + return; + } + } + + //FEATURE: prevent players from engaging in PvP combat inside land claims (when it's disabled) + if(GriefPrevention.instance.config_pvp_noCombatInPlayerLandClaims || GriefPrevention.instance.config_pvp_noCombatInAdminLandClaims) + { + Claim attackerClaim = this.dataStore.getClaimAt(attacker.getLocation(), false, attackerData.lastClaim); + if( attackerClaim != null && + (attackerClaim.isAdminClaim() && GriefPrevention.instance.config_pvp_noCombatInAdminLandClaims || + !attackerClaim.isAdminClaim() && GriefPrevention.instance.config_pvp_noCombatInPlayerLandClaims)) + { + attackerData.lastClaim = attackerClaim; + event.setCancelled(true); + GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune); + return; + } + + Claim defenderClaim = this.dataStore.getClaimAt(defender.getLocation(), false, defenderData.lastClaim); + if( defenderClaim != null && + (defenderClaim.isAdminClaim() && GriefPrevention.instance.config_pvp_noCombatInAdminLandClaims || + !defenderClaim.isAdminClaim() && GriefPrevention.instance.config_pvp_noCombatInPlayerLandClaims)) + { + defenderData.lastClaim = defenderClaim; + event.setCancelled(true); + GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.PlayerInPvPSafeZone); + return; + } + } + + //FEATURE: prevent players who very recently participated in pvp combat from hiding inventory to protect it from looting + //FEATURE: prevent players who are in pvp combat from logging out to avoid being defeated + + long now = Calendar.getInstance().getTimeInMillis(); + defenderData.lastPvpTimestamp = now; + defenderData.lastPvpPlayer = attacker.getName(); + attackerData.lastPvpTimestamp = now; + attackerData.lastPvpPlayer = defender.getName(); + } + + //FEATURE: protect claimed animals, boats, minecarts + //NOTE: animals can be lead with wheat, vehicles can be pushed around. + //so unless precautions are taken by the owner, a resourceful thief might find ways to steal anyway + + //if theft protection is enabled + if(event instanceof EntityDamageByEntityEvent) + { + //if the entity is an non-monster creature (remember monsters disqualified above), or a vehicle + if ((subEvent.getEntity() instanceof Creature && GriefPrevention.instance.config_claims_protectCreatures)) + { + Claim cachedClaim = null; + PlayerData playerData = null; + if(attacker != null) + { + playerData = this.dataStore.getPlayerData(attacker.getName()); + cachedClaim = playerData.lastClaim; + } + + Claim claim = this.dataStore.getClaimAt(event.getEntity().getLocation(), false, cachedClaim); + + //if it's claimed + if(claim != null) + { + //if damaged by anything other than a player (exception villagers injured by zombies in admin claims), cancel the event + //why exception? so admins can set up a village which can't be CHANGED by players, but must be "protected" by players. + if(attacker == null) + { + //exception case + if(event.getEntity() instanceof Villager && damageSource instanceof Monster && claim.isAdminClaim()) + { + return; + } + + //all other cases + else + { + event.setCancelled(true); + } + } + + //otherwise the player damaging the entity must have permission + else + { + String noContainersReason = claim.allowContainers(attacker); + if(noContainersReason != null) + { + event.setCancelled(true); + + //kill the arrow to avoid infinite bounce between crowded together animals + if(arrow != null) arrow.remove(); + + GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.NoDamageClaimedEntity, claim.getOwnerName()); + } + + //cache claim for later + if(playerData != null) + { + playerData.lastClaim = claim; + } + } + } + } + } + } + + //when a vehicle is damaged + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onVehicleDamage (VehicleDamageEvent event) + { + //all of this is anti theft code + if(!GriefPrevention.instance.config_claims_preventTheft) return; + + //determine which player is attacking, if any + Player attacker = null; + Entity damageSource = event.getAttacker(); + if(damageSource instanceof Player) + { + attacker = (Player)damageSource; + } + else if(damageSource instanceof Arrow) + { + Arrow arrow = (Arrow)damageSource; + if(arrow.getShooter() instanceof Player) + { + attacker = (Player)arrow.getShooter(); + } + } + else if(damageSource instanceof ThrownPotion) + { + ThrownPotion potion = (ThrownPotion)damageSource; + if(potion.getShooter() instanceof Player) + { + attacker = (Player)potion.getShooter(); + } + } + + //NOTE: vehicles can be pushed around. + //so unless precautions are taken by the owner, a resourceful thief might find ways to steal anyway + Claim cachedClaim = null; + PlayerData playerData = null; + if(attacker != null) + { + playerData = this.dataStore.getPlayerData(attacker.getName()); + cachedClaim = playerData.lastClaim; + } + + Claim claim = this.dataStore.getClaimAt(event.getVehicle().getLocation(), false, cachedClaim); + + //if it's claimed + if(claim != null) + { + //if damaged by anything other than a player, cancel the event + if(attacker == null) + { + event.setCancelled(true); + } + + //otherwise the player damaging the entity must have permission + else + { + String noContainersReason = claim.allowContainers(attacker); + if(noContainersReason != null) + { + event.setCancelled(true); + GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.NoDamageClaimedEntity, claim.getOwnerName()); + } + + //cache claim for later + if(playerData != null) + { + playerData.lastClaim = claim; + } + } + } + } +} diff --git a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java index 343f593..5676948 100644 --- a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java +++ b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java @@ -70,6 +70,7 @@ public class GriefPrevention extends JavaPlugin public boolean config_claims_lockWoodenDoors; //whether wooden doors should be locked by default (require /accesstrust) public boolean config_claims_lockTrapDoors; //whether trap doors should be locked by default (require /accesstrust) public boolean config_claims_lockFenceGates; //whether fence gates should be locked by default (require /accesstrust) + public boolean config_claims_enderPearlsRequireAccessTrust; //whether teleporting into a claim with a pearl requires access trust public int config_claims_initialBlocks; //the number of claim blocks a new player starts with public int config_claims_blocksAccruedPerHour; //how many additional blocks players get each hour of play (can be zero) @@ -82,6 +83,7 @@ public class GriefPrevention extends JavaPlugin public int config_claims_claimsExtendIntoGroundDistance; //how far below the shoveled block a new claim will reach public int config_claims_minSize; //minimum width and height for non-admin claims public boolean config_claims_allowUnclaimInCreative; //whether players may unclaim land (resize or abandon) in creative mode + public boolean config_claims_autoRestoreUnclaimedCreativeLand; //whether unclaimed land in creative worlds is automatically /restorenature-d public boolean config_claims_noBuildOutsideClaims; //whether players can build in survival worlds outside their claimed areas @@ -113,6 +115,8 @@ public class GriefPrevention extends JavaPlugin public int config_pvp_combatTimeoutSeconds; //how long combat is considered to continue after the most recent damage public boolean config_pvp_allowCombatItemDrop; //whether a player can drop items during combat to hide them public ArrayList config_pvp_blockedCommands; //list of commands which may not be used during pvp combat + public boolean config_pvp_noCombatInPlayerLandClaims; //whether players may fight in player-owned land claims + public boolean config_pvp_noCombatInAdminLandClaims; //whether players may fight in admin-owned land claims public boolean config_trees_removeFloatingTreetops; //whether to automatically remove partially cut trees public boolean config_trees_regrowGriefedTrees; //whether to automatically replant partially cut trees @@ -172,6 +176,7 @@ public class GriefPrevention extends JavaPlugin //load the config if it exists FileConfiguration config = YamlConfiguration.loadConfiguration(new File(DataStore.configFilePath)); + FileConfiguration outConfig = new YamlConfiguration(); //read configuration settings (note defaults) @@ -280,7 +285,7 @@ public class GriefPrevention extends JavaPlugin for(int i = 0; i < worlds.size(); i++) { int seaLevelOverride = config.getInt("GriefPrevention.SeaLevelOverrides." + worlds.get(i).getName(), -1); - config.set("GriefPrevention.SeaLevelOverrides." + worlds.get(i).getName(), seaLevelOverride); + outConfig.set("GriefPrevention.SeaLevelOverrides." + worlds.get(i).getName(), seaLevelOverride); this.config_seaLevelOverride.put(worlds.get(i).getName(), seaLevelOverride); } @@ -290,6 +295,7 @@ public class GriefPrevention extends JavaPlugin this.config_claims_lockWoodenDoors = config.getBoolean("GriefPrevention.Claims.LockWoodenDoors", false); this.config_claims_lockTrapDoors = config.getBoolean("GriefPrevention.Claims.LockTrapDoors", false); this.config_claims_lockFenceGates = config.getBoolean("GriefPrevention.Claims.LockFenceGates", true); + this.config_claims_enderPearlsRequireAccessTrust = config.getBoolean("GriefPrevention.Claims.EnderPearlsRequireAccessTrust", true); this.config_claims_initialBlocks = config.getInt("GriefPrevention.Claims.InitialBlocks", 100); this.config_claims_blocksAccruedPerHour = config.getInt("GriefPrevention.Claims.BlocksAccruedPerHour", 100); this.config_claims_maxAccruedBlocks = config.getInt("GriefPrevention.Claims.MaxAccruedBlocks", 80000); @@ -302,21 +308,22 @@ public class GriefPrevention extends JavaPlugin this.config_claims_noBuildOutsideClaims = config.getBoolean("GriefPrevention.Claims.NoSurvivalBuildingOutsideClaims", false); this.config_claims_warnOnBuildOutside = config.getBoolean("GriefPrevention.Claims.WarnWhenBuildingOutsideClaims", true); this.config_claims_allowUnclaimInCreative = config.getBoolean("GriefPrevention.Claims.AllowUnclaimingCreativeModeLand", true); + this.config_claims_autoRestoreUnclaimedCreativeLand = config.getBoolean("GriefPrevention.Claims.AutoRestoreUnclaimedCreativeLand", true); this.config_claims_chestClaimExpirationDays = config.getInt("GriefPrevention.Claims.Expiration.ChestClaimDays", 7); - config.set("GriefPrevention.Claims.Expiration.ChestClaimDays", this.config_claims_chestClaimExpirationDays); + outConfig.set("GriefPrevention.Claims.Expiration.ChestClaimDays", this.config_claims_chestClaimExpirationDays); this.config_claims_unusedClaimExpirationDays = config.getInt("GriefPrevention.Claims.Expiration.UnusedClaimDays", 14); - config.set("GriefPrevention.Claims.Expiration.UnusedClaimDays", this.config_claims_unusedClaimExpirationDays); + outConfig.set("GriefPrevention.Claims.Expiration.UnusedClaimDays", this.config_claims_unusedClaimExpirationDays); this.config_claims_expirationDays = config.getInt("GriefPrevention.Claims.Expiration.AllClaimDays", 0); - config.set("GriefPrevention.Claims.Expiration.AllClaimDays", this.config_claims_expirationDays); + outConfig.set("GriefPrevention.Claims.Expiration.AllClaimDays", this.config_claims_expirationDays); this.config_claims_survivalAutoNatureRestoration = config.getBoolean("GriefPrevention.Claims.Expiration.AutomaticNatureRestoration.SurvivalWorlds", false); - config.set("GriefPrevention.Claims.Expiration.AutomaticNatureRestoration.SurvivalWorlds", this.config_claims_survivalAutoNatureRestoration); + outConfig.set("GriefPrevention.Claims.Expiration.AutomaticNatureRestoration.SurvivalWorlds", this.config_claims_survivalAutoNatureRestoration); this.config_claims_creativeAutoNatureRestoration = config.getBoolean("GriefPrevention.Claims.Expiration.AutomaticNatureRestoration.CreativeWorlds", true); - config.set("GriefPrevention.Claims.Expiration.AutomaticNatureRestoration.CreativeWorlds", this.config_claims_creativeAutoNatureRestoration); + outConfig.set("GriefPrevention.Claims.Expiration.AutomaticNatureRestoration.CreativeWorlds", this.config_claims_creativeAutoNatureRestoration); this.config_spam_enabled = config.getBoolean("GriefPrevention.Spam.Enabled", true); this.config_spam_loginCooldownMinutes = config.getInt("GriefPrevention.Spam.LoginCooldownMinutes", 2); @@ -527,94 +534,101 @@ public class GriefPrevention extends JavaPlugin } } + this.config_pvp_noCombatInPlayerLandClaims = config.getBoolean("GriefPrevention.PvP.ProtectPlayersInLandClaims.PlayerOwnedClaims", this.config_siege_enabledWorlds.size() == 0); + this.config_pvp_noCombatInAdminLandClaims = config.getBoolean("GriefPrevention.PvP.ProtectPlayersInLandClaims.AdministrativeClaims", this.config_siege_enabledWorlds.size() == 0); + //optional database settings String databaseUrl = config.getString("GriefPrevention.Database.URL", ""); String databaseUserName = config.getString("GriefPrevention.Database.UserName", ""); String databasePassword = config.getString("GriefPrevention.Database.Password", ""); - config.set("GriefPrevention.Claims.Worlds", claimsEnabledWorldNames); - config.set("GriefPrevention.Claims.CreativeRulesWorlds", creativeClaimsEnabledWorldNames); - config.set("GriefPrevention.Claims.PreventTheft", this.config_claims_preventTheft); - config.set("GriefPrevention.Claims.ProtectCreatures", this.config_claims_protectCreatures); - config.set("GriefPrevention.Claims.PreventButtonsSwitches", this.config_claims_preventButtonsSwitches); - config.set("GriefPrevention.Claims.LockWoodenDoors", this.config_claims_lockWoodenDoors); - config.set("GriefPrevention.Claims.LockTrapDoors", this.config_claims_lockTrapDoors); - config.set("GriefPrevention.Claims.LockFenceGates", this.config_claims_lockFenceGates); - config.set("GriefPrevention.Claims.InitialBlocks", this.config_claims_initialBlocks); - config.set("GriefPrevention.Claims.BlocksAccruedPerHour", this.config_claims_blocksAccruedPerHour); - config.set("GriefPrevention.Claims.MaxAccruedBlocks", this.config_claims_maxAccruedBlocks); - config.set("GriefPrevention.Claims.AutomaticNewPlayerClaimsRadius", this.config_claims_automaticClaimsForNewPlayersRadius); - config.set("GriefPrevention.Claims.ExtendIntoGroundDistance", this.config_claims_claimsExtendIntoGroundDistance); - config.set("GriefPrevention.Claims.CreationRequiresPermission", this.config_claims_creationRequiresPermission); - config.set("GriefPrevention.Claims.MinimumSize", this.config_claims_minSize); - config.set("GriefPrevention.Claims.MaximumDepth", this.config_claims_maxDepth); - config.set("GriefPrevention.Claims.IdleLimitDays", this.config_claims_expirationDays); - config.set("GriefPrevention.Claims.TrappedCommandCooldownHours", this.config_claims_trappedCooldownHours); - config.set("GriefPrevention.Claims.InvestigationTool", this.config_claims_investigationTool.name()); - config.set("GriefPrevention.Claims.ModificationTool", this.config_claims_modificationTool.name()); - config.set("GriefPrevention.Claims.NoSurvivalBuildingOutsideClaims", this.config_claims_noBuildOutsideClaims); - config.set("GriefPrevention.Claims.WarnWhenBuildingOutsideClaims", this.config_claims_warnOnBuildOutside); - config.set("GriefPrevention.Claims.AllowUnclaimingCreativeModeLand", this.config_claims_allowUnclaimInCreative); + outConfig.set("GriefPrevention.Claims.Worlds", claimsEnabledWorldNames); + outConfig.set("GriefPrevention.Claims.CreativeRulesWorlds", creativeClaimsEnabledWorldNames); + outConfig.set("GriefPrevention.Claims.PreventTheft", this.config_claims_preventTheft); + outConfig.set("GriefPrevention.Claims.ProtectCreatures", this.config_claims_protectCreatures); + outConfig.set("GriefPrevention.Claims.PreventButtonsSwitches", this.config_claims_preventButtonsSwitches); + outConfig.set("GriefPrevention.Claims.LockWoodenDoors", this.config_claims_lockWoodenDoors); + outConfig.set("GriefPrevention.Claims.LockTrapDoors", this.config_claims_lockTrapDoors); + outConfig.set("GriefPrevention.Claims.LockFenceGates", this.config_claims_lockFenceGates); + outConfig.set("GriefPrevention.Claims.EnderPearlsRequireAccessTrust", this.config_claims_enderPearlsRequireAccessTrust); + outConfig.set("GriefPrevention.Claims.InitialBlocks", this.config_claims_initialBlocks); + outConfig.set("GriefPrevention.Claims.BlocksAccruedPerHour", this.config_claims_blocksAccruedPerHour); + outConfig.set("GriefPrevention.Claims.MaxAccruedBlocks", this.config_claims_maxAccruedBlocks); + outConfig.set("GriefPrevention.Claims.AutomaticNewPlayerClaimsRadius", this.config_claims_automaticClaimsForNewPlayersRadius); + outConfig.set("GriefPrevention.Claims.ExtendIntoGroundDistance", this.config_claims_claimsExtendIntoGroundDistance); + outConfig.set("GriefPrevention.Claims.CreationRequiresPermission", this.config_claims_creationRequiresPermission); + outConfig.set("GriefPrevention.Claims.MinimumSize", this.config_claims_minSize); + outConfig.set("GriefPrevention.Claims.MaximumDepth", this.config_claims_maxDepth); + outConfig.set("GriefPrevention.Claims.IdleLimitDays", this.config_claims_expirationDays); + outConfig.set("GriefPrevention.Claims.TrappedCommandCooldownHours", this.config_claims_trappedCooldownHours); + outConfig.set("GriefPrevention.Claims.InvestigationTool", this.config_claims_investigationTool.name()); + outConfig.set("GriefPrevention.Claims.ModificationTool", this.config_claims_modificationTool.name()); + outConfig.set("GriefPrevention.Claims.NoSurvivalBuildingOutsideClaims", this.config_claims_noBuildOutsideClaims); + outConfig.set("GriefPrevention.Claims.WarnWhenBuildingOutsideClaims", this.config_claims_warnOnBuildOutside); + outConfig.set("GriefPrevention.Claims.AllowUnclaimingCreativeModeLand", this.config_claims_allowUnclaimInCreative); + outConfig.set("GriefPrevention.Claims.AutoRestoreUnclaimedCreativeLand", this.config_claims_autoRestoreUnclaimedCreativeLand); - config.set("GriefPrevention.Spam.Enabled", this.config_spam_enabled); - config.set("GriefPrevention.Spam.LoginCooldownMinutes", this.config_spam_loginCooldownMinutes); - config.set("GriefPrevention.Spam.MonitorSlashCommands", slashCommandsToMonitor); - config.set("GriefPrevention.Spam.WarningMessage", this.config_spam_warningMessage); - config.set("GriefPrevention.Spam.BanOffenders", this.config_spam_banOffenders); - config.set("GriefPrevention.Spam.BanMessage", this.config_spam_banMessage); - config.set("GriefPrevention.Spam.AllowedIpAddresses", this.config_spam_allowedIpAddresses); - config.set("GriefPrevention.Spam.DeathMessageCooldownSeconds", this.config_spam_deathMessageCooldownSeconds); + outConfig.set("GriefPrevention.Spam.Enabled", this.config_spam_enabled); + outConfig.set("GriefPrevention.Spam.LoginCooldownMinutes", this.config_spam_loginCooldownMinutes); + outConfig.set("GriefPrevention.Spam.MonitorSlashCommands", slashCommandsToMonitor); + outConfig.set("GriefPrevention.Spam.WarningMessage", this.config_spam_warningMessage); + outConfig.set("GriefPrevention.Spam.BanOffenders", this.config_spam_banOffenders); + outConfig.set("GriefPrevention.Spam.BanMessage", this.config_spam_banMessage); + outConfig.set("GriefPrevention.Spam.AllowedIpAddresses", this.config_spam_allowedIpAddresses); + outConfig.set("GriefPrevention.Spam.DeathMessageCooldownSeconds", this.config_spam_deathMessageCooldownSeconds); - config.set("GriefPrevention.PvP.Worlds", pvpEnabledWorldNames); - config.set("GriefPrevention.PvP.ProtectFreshSpawns", this.config_pvp_protectFreshSpawns); - config.set("GriefPrevention.PvP.PunishLogout", this.config_pvp_punishLogout); - config.set("GriefPrevention.PvP.CombatTimeoutSeconds", this.config_pvp_combatTimeoutSeconds); - config.set("GriefPrevention.PvP.AllowCombatItemDrop", this.config_pvp_allowCombatItemDrop); - config.set("GriefPrevention.PvP.BlockedSlashCommands", bannedPvPCommandsList); + outConfig.set("GriefPrevention.PvP.Worlds", pvpEnabledWorldNames); + outConfig.set("GriefPrevention.PvP.ProtectFreshSpawns", this.config_pvp_protectFreshSpawns); + outConfig.set("GriefPrevention.PvP.PunishLogout", this.config_pvp_punishLogout); + outConfig.set("GriefPrevention.PvP.CombatTimeoutSeconds", this.config_pvp_combatTimeoutSeconds); + outConfig.set("GriefPrevention.PvP.AllowCombatItemDrop", this.config_pvp_allowCombatItemDrop); + outConfig.set("GriefPrevention.PvP.BlockedSlashCommands", bannedPvPCommandsList); + outConfig.set("GriefPrevention.PvP.ProtectPlayersInLandClaims.PlayerOwnedClaims", this.config_pvp_noCombatInPlayerLandClaims); + outConfig.set("GriefPrevention.PvP.ProtectPlayersInLandClaims.AdministrativeClaims", this.config_pvp_noCombatInAdminLandClaims); - config.set("GriefPrevention.Trees.RemoveFloatingTreetops", this.config_trees_removeFloatingTreetops); - config.set("GriefPrevention.Trees.RegrowGriefedTrees", this.config_trees_regrowGriefedTrees); + outConfig.set("GriefPrevention.Trees.RemoveFloatingTreetops", this.config_trees_removeFloatingTreetops); + outConfig.set("GriefPrevention.Trees.RegrowGriefedTrees", this.config_trees_regrowGriefedTrees); - config.set("GriefPrevention.Economy.ClaimBlocksPurchaseCost", this.config_economy_claimBlocksPurchaseCost); - config.set("GriefPrevention.Economy.ClaimBlocksSellValue", this.config_economy_claimBlocksSellValue); + outConfig.set("GriefPrevention.Economy.ClaimBlocksPurchaseCost", this.config_economy_claimBlocksPurchaseCost); + outConfig.set("GriefPrevention.Economy.ClaimBlocksSellValue", this.config_economy_claimBlocksSellValue); - config.set("GriefPrevention.BlockSurfaceCreeperExplosions", this.config_blockSurfaceCreeperExplosions); - config.set("GriefPrevention.BlockSurfaceOtherExplosions", this.config_blockSurfaceOtherExplosions); - config.set("GriefPrevention.LimitSurfaceWaterBuckets", this.config_blockWildernessWaterBuckets); - config.set("GriefPrevention.LimitSkyTrees", this.config_blockSkyTrees); + outConfig.set("GriefPrevention.BlockSurfaceCreeperExplosions", this.config_blockSurfaceCreeperExplosions); + outConfig.set("GriefPrevention.BlockSurfaceOtherExplosions", this.config_blockSurfaceOtherExplosions); + outConfig.set("GriefPrevention.LimitSurfaceWaterBuckets", this.config_blockWildernessWaterBuckets); + outConfig.set("GriefPrevention.LimitSkyTrees", this.config_blockSkyTrees); - config.set("GriefPrevention.FireSpreads", this.config_fireSpreads); - config.set("GriefPrevention.FireDestroys", this.config_fireDestroys); + outConfig.set("GriefPrevention.FireSpreads", this.config_fireSpreads); + outConfig.set("GriefPrevention.FireDestroys", this.config_fireDestroys); - config.set("GriefPrevention.AddItemsToClaimedChests", this.config_addItemsToClaimedChests); + outConfig.set("GriefPrevention.AddItemsToClaimedChests", this.config_addItemsToClaimedChests); - config.set("GriefPrevention.EavesdropEnabled", this.config_eavesdrop); - config.set("GriefPrevention.WhisperCommands", whisperCommandsToMonitor); - config.set("GriefPrevention.SmartBan", this.config_smartBan); + outConfig.set("GriefPrevention.EavesdropEnabled", this.config_eavesdrop); + outConfig.set("GriefPrevention.WhisperCommands", whisperCommandsToMonitor); + outConfig.set("GriefPrevention.SmartBan", this.config_smartBan); - config.set("GriefPrevention.Siege.Worlds", siegeEnabledWorldNames); - config.set("GriefPrevention.Siege.BreakableBlocks", breakableBlocksList); + outConfig.set("GriefPrevention.Siege.Worlds", siegeEnabledWorldNames); + outConfig.set("GriefPrevention.Siege.BreakableBlocks", breakableBlocksList); - config.set("GriefPrevention.EndermenMoveBlocks", this.config_endermenMoveBlocks); - config.set("GriefPrevention.SilverfishBreakBlocks", this.config_silverfishBreakBlocks); - config.set("GriefPrevention.CreaturesTrampleCrops", this.config_creaturesTrampleCrops); - config.set("GriefPrevention.HardModeZombiesBreakDoors", this.config_zombiesBreakDoors); + outConfig.set("GriefPrevention.EndermenMoveBlocks", this.config_endermenMoveBlocks); + outConfig.set("GriefPrevention.SilverfishBreakBlocks", this.config_silverfishBreakBlocks); + outConfig.set("GriefPrevention.CreaturesTrampleCrops", this.config_creaturesTrampleCrops); + outConfig.set("GriefPrevention.HardModeZombiesBreakDoors", this.config_zombiesBreakDoors); - config.set("GriefPrevention.Database.URL", databaseUrl); - config.set("GriefPrevention.Database.UserName", databaseUserName); - config.set("GriefPrevention.Database.Password", databasePassword); + outConfig.set("GriefPrevention.Database.URL", databaseUrl); + outConfig.set("GriefPrevention.Database.UserName", databaseUserName); + outConfig.set("GriefPrevention.Database.Password", databasePassword); - config.set("GriefPrevention.Mods.BlockIdsRequiringAccessTrust", this.config_mods_accessTrustIds); - config.set("GriefPrevention.Mods.BlockIdsRequiringContainerTrust", this.config_mods_containerTrustIds); - config.set("GriefPrevention.Mods.BlockIdsExplodable", this.config_mods_explodableIds); - config.set("GriefPrevention.Mods.PlayersIgnoringAllClaims", this.config_mods_ignoreClaimsAccounts); - config.set("GriefPrevention.Mods.BlockIdsRequiringAccessTrust", accessTrustStrings); - config.set("GriefPrevention.Mods.BlockIdsRequiringContainerTrust", containerTrustStrings); - config.set("GriefPrevention.Mods.BlockIdsExplodable", explodableStrings); + outConfig.set("GriefPrevention.Mods.BlockIdsRequiringAccessTrust", this.config_mods_accessTrustIds); + outConfig.set("GriefPrevention.Mods.BlockIdsRequiringContainerTrust", this.config_mods_containerTrustIds); + outConfig.set("GriefPrevention.Mods.BlockIdsExplodable", this.config_mods_explodableIds); + outConfig.set("GriefPrevention.Mods.PlayersIgnoringAllClaims", this.config_mods_ignoreClaimsAccounts); + outConfig.set("GriefPrevention.Mods.BlockIdsRequiringAccessTrust", accessTrustStrings); + outConfig.set("GriefPrevention.Mods.BlockIdsRequiringContainerTrust", containerTrustStrings); + outConfig.set("GriefPrevention.Mods.BlockIdsExplodable", explodableStrings); try { - config.save(DataStore.configFilePath); + outConfig.save(DataStore.configFilePath); } catch(IOException exception) { @@ -1400,8 +1414,8 @@ public class GriefPrevention extends JavaPlugin claim.removeSurfaceFluids(null); this.dataStore.deleteClaim(claim); - //if in a creative mode world, delete the claim - if(GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner())) + //if in a creative mode world, /restorenature the claim + if(GriefPrevention.instance.config_claims_autoRestoreUnclaimedCreativeLand && GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner())) { GriefPrevention.instance.restoreClaim(claim, 0); } @@ -1424,6 +1438,40 @@ public class GriefPrevention extends JavaPlugin return true; } + else if(cmd.getName().equalsIgnoreCase("claimexplosions") && player != null) + { + //determine which claim the player is standing in + Claim claim = this.dataStore.getClaimAt(player.getLocation(), true /*ignore height*/, null); + + if(claim == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.DeleteClaimMissing); + } + + else + { + String noBuildReason = claim.allowBuild(player); + if(noBuildReason != null) + { + GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason); + return true; + } + + if(claim.areExplosivesAllowed) + { + claim.areExplosivesAllowed = false; + GriefPrevention.sendMessage(player, TextMode.Success, Messages.ExplosivesDisabled); + } + else + { + claim.areExplosivesAllowed = true; + GriefPrevention.sendMessage(player, TextMode.Success, Messages.ExplosivesEnabled); + } + } + + return true; + } + //deleteallclaims else if(cmd.getName().equalsIgnoreCase("deleteallclaims")) { @@ -1720,6 +1768,13 @@ public class GriefPrevention extends JavaPlugin return true; } + //can't start a siege when you're protected from pvp combat + if(attackerData.pvpImmune) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.CantFightWhileImmune); + return true; + } + //if a player name was specified, use that Player defender = null; if(args.length >= 1) diff --git a/src/me/ryanhamshire/GriefPrevention/Messages.java b/src/me/ryanhamshire/GriefPrevention/Messages.java index d14f079..34b81b8 100644 --- a/src/me/ryanhamshire/GriefPrevention/Messages.java +++ b/src/me/ryanhamshire/GriefPrevention/Messages.java @@ -1,24 +1,24 @@ -/* - GriefPrevention Server Plugin for Minecraft - Copyright (C) 2012 Ryan Hamshire - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -package me.ryanhamshire.GriefPrevention; - -public enum Messages -{ - RespectingClaims, IgnoringClaims, SuccessfulAbandon, RestoreNatureActivate, RestoreNatureAggressiveActivate, FillModeActive, TransferClaimPermission, TransferClaimMissing, TransferClaimAdminOnly, PlayerNotFound, TransferTopLevel, TransferSuccess, TrustListNoClaim, ClearPermsOwnerOnly, UntrustIndividualAllClaims, UntrustEveryoneAllClaims, NoPermissionTrust, ClearPermissionsOneClaim, UntrustIndividualSingleClaim, OnlySellBlocks, BlockPurchaseCost, ClaimBlockLimit, InsufficientFunds, PurchaseConfirmation, OnlyPurchaseBlocks, BlockSaleValue, NotEnoughBlocksForSale, BlockSaleConfirmation, AdminClaimsMode, BasicClaimsMode, SubdivisionMode, SubdivisionDemo, DeleteClaimMissing, DeletionSubdivisionWarning, DeleteSuccess, CantDeleteAdminClaim, DeleteAllSuccess, NoDeletePermission, AllAdminDeleted, AdjustBlocksSuccess, NotTrappedHere, TrappedOnCooldown, RescuePending, NonSiegeWorld, AlreadySieging, NotSiegableThere, SiegeTooFarAway, NoSiegeDefenseless, AlreadyUnderSiegePlayer, AlreadyUnderSiegeArea, NoSiegeAdminClaim, SiegeOnCooldown, SiegeAlert, SiegeConfirmed, AbandonClaimMissing, NotYourClaim, DeleteTopLevelClaim, AbandonSuccess, CantGrantThatPermission, GrantPermissionNoClaim, GrantPermissionConfirmation, ManageUniversalPermissionsInstruction, ManageOneClaimPermissionsInstruction, CollectivePublic, BuildPermission, ContainersPermission, AccessPermission, PermissionsPermission, LocationCurrentClaim, LocationAllClaims, PvPImmunityStart, SiegeNoDrop, DonateItemsInstruction, ChestFull, DonationSuccess, PlayerTooCloseForFire, TooDeepToClaim, ChestClaimConfirmation, AutomaticClaimNotification, TrustCommandAdvertisement, GoldenShovelAdvertisement, UnprotectedChestWarning, ThatPlayerPvPImmune, CantFightWhileImmune, NoDamageClaimedEntity, ShovelBasicClaimMode, RemainingBlocks, CreativeBasicsDemoAdvertisement, SurvivalBasicsDemoAdvertisement, TrappedChatKeyword, TrappedInstructions, PvPNoDrop, SiegeNoTeleport, BesiegedNoTeleport, SiegeNoContainers, PvPNoContainers, PvPImmunityEnd, NoBedPermission, NoWildernessBuckets, NoLavaNearOtherPlayer, TooFarAway, BlockNotClaimed, BlockClaimed, SiegeNoShovel, RestoreNaturePlayerInChunk, NoCreateClaimPermission, ResizeClaimTooSmall, ResizeNeedMoreBlocks, NoCreativeUnClaim, ClaimResizeSuccess, ResizeFailOverlap, ResizeStart, ResizeFailOverlapSubdivision, SubdivisionStart, CreateSubdivisionOverlap, SubdivisionSuccess, CreateClaimFailOverlap, CreateClaimFailOverlapOtherPlayer, ClaimsDisabledWorld, ClaimStart, NewClaimTooSmall, CreateClaimInsufficientBlocks, AbandonClaimAdvertisement, CreateClaimFailOverlapShort, CreateClaimSuccess, SiegeWinDoorsOpen, RescueAbortedMoved, SiegeDoorsLockedEjection, NoModifyDuringSiege, OnlyOwnersModifyClaims, NoBuildUnderSiege, NoBuildPvP, NoBuildPermission, NonSiegeMaterial, NoOwnerBuildUnderSiege, NoAccessPermission, NoContainersSiege, NoContainersPermission, OwnerNameForAdminClaims, ClaimTooSmallForEntities, TooManyEntitiesInClaim, YouHaveNoClaims, ConfirmFluidRemoval, AutoBanNotify, AdjustGroupBlocksSuccess, InvalidPermissionID, UntrustOwnerOnly, HowToClaimRegex, NoBuildOutsideClaims, PlayerOfflineTime, BuildingOutsideClaims, TrappedWontWorkHere, CommandBannedInPvP, UnclaimCleanupWarning, BuySellNotConfigured, NoTeleportPvPCombat, NoTNTDamageAboveSeaLevel, NoTNTDamageClaims, IgnoreClaimsAdvertisement, NoPermissionForCommand, ClaimsListNoPermission -} +/* + GriefPrevention Server Plugin for Minecraft + Copyright (C) 2012 Ryan Hamshire + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package me.ryanhamshire.GriefPrevention; + +public enum Messages +{ + RespectingClaims, IgnoringClaims, SuccessfulAbandon, RestoreNatureActivate, RestoreNatureAggressiveActivate, FillModeActive, TransferClaimPermission, TransferClaimMissing, TransferClaimAdminOnly, PlayerNotFound, TransferTopLevel, TransferSuccess, TrustListNoClaim, ClearPermsOwnerOnly, UntrustIndividualAllClaims, UntrustEveryoneAllClaims, NoPermissionTrust, ClearPermissionsOneClaim, UntrustIndividualSingleClaim, OnlySellBlocks, BlockPurchaseCost, ClaimBlockLimit, InsufficientFunds, PurchaseConfirmation, OnlyPurchaseBlocks, BlockSaleValue, NotEnoughBlocksForSale, BlockSaleConfirmation, AdminClaimsMode, BasicClaimsMode, SubdivisionMode, SubdivisionDemo, DeleteClaimMissing, DeletionSubdivisionWarning, DeleteSuccess, CantDeleteAdminClaim, DeleteAllSuccess, NoDeletePermission, AllAdminDeleted, AdjustBlocksSuccess, NotTrappedHere, TrappedOnCooldown, RescuePending, NonSiegeWorld, AlreadySieging, NotSiegableThere, SiegeTooFarAway, NoSiegeDefenseless, AlreadyUnderSiegePlayer, AlreadyUnderSiegeArea, NoSiegeAdminClaim, SiegeOnCooldown, SiegeAlert, SiegeConfirmed, AbandonClaimMissing, NotYourClaim, DeleteTopLevelClaim, AbandonSuccess, CantGrantThatPermission, GrantPermissionNoClaim, GrantPermissionConfirmation, ManageUniversalPermissionsInstruction, ManageOneClaimPermissionsInstruction, CollectivePublic, BuildPermission, ContainersPermission, AccessPermission, PermissionsPermission, LocationCurrentClaim, LocationAllClaims, PvPImmunityStart, SiegeNoDrop, DonateItemsInstruction, ChestFull, DonationSuccess, PlayerTooCloseForFire, TooDeepToClaim, ChestClaimConfirmation, AutomaticClaimNotification, TrustCommandAdvertisement, GoldenShovelAdvertisement, UnprotectedChestWarning, ThatPlayerPvPImmune, CantFightWhileImmune, NoDamageClaimedEntity, ShovelBasicClaimMode, RemainingBlocks, CreativeBasicsDemoAdvertisement, SurvivalBasicsDemoAdvertisement, TrappedChatKeyword, TrappedInstructions, PvPNoDrop, SiegeNoTeleport, BesiegedNoTeleport, SiegeNoContainers, PvPNoContainers, PvPImmunityEnd, NoBedPermission, NoWildernessBuckets, NoLavaNearOtherPlayer, TooFarAway, BlockNotClaimed, BlockClaimed, SiegeNoShovel, RestoreNaturePlayerInChunk, NoCreateClaimPermission, ResizeClaimTooSmall, ResizeNeedMoreBlocks, NoCreativeUnClaim, ClaimResizeSuccess, ResizeFailOverlap, ResizeStart, ResizeFailOverlapSubdivision, SubdivisionStart, CreateSubdivisionOverlap, SubdivisionSuccess, CreateClaimFailOverlap, CreateClaimFailOverlapOtherPlayer, ClaimsDisabledWorld, ClaimStart, NewClaimTooSmall, CreateClaimInsufficientBlocks, AbandonClaimAdvertisement, CreateClaimFailOverlapShort, CreateClaimSuccess, SiegeWinDoorsOpen, RescueAbortedMoved, SiegeDoorsLockedEjection, NoModifyDuringSiege, OnlyOwnersModifyClaims, NoBuildUnderSiege, NoBuildPvP, NoBuildPermission, NonSiegeMaterial, NoOwnerBuildUnderSiege, NoAccessPermission, NoContainersSiege, NoContainersPermission, OwnerNameForAdminClaims, ClaimTooSmallForEntities, TooManyEntitiesInClaim, YouHaveNoClaims, ConfirmFluidRemoval, AutoBanNotify, AdjustGroupBlocksSuccess, InvalidPermissionID, UntrustOwnerOnly, HowToClaimRegex, NoBuildOutsideClaims, PlayerOfflineTime, BuildingOutsideClaims, TrappedWontWorkHere, CommandBannedInPvP, UnclaimCleanupWarning, BuySellNotConfigured, NoTeleportPvPCombat, NoTNTDamageAboveSeaLevel, NoTNTDamageClaims, IgnoreClaimsAdvertisement, NoPermissionForCommand, ClaimsListNoPermission, ExplosivesDisabled, ExplosivesEnabled, ClaimExplosivesAdvertisement, PlayerInPvPSafeZone +} diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerData.java b/src/me/ryanhamshire/GriefPrevention/PlayerData.java index 8703143..f4fe6fe 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerData.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerData.java @@ -22,6 +22,12 @@ import java.util.Calendar; import java.util.Date; import java.util.Vector; +import me.ryanhamshire.GriefPrevention.Claim; +import me.ryanhamshire.GriefPrevention.GriefPrevention; +import me.ryanhamshire.GriefPrevention.ShovelMode; +import me.ryanhamshire.GriefPrevention.SiegeData; +import me.ryanhamshire.GriefPrevention.Visualization; + import org.bukkit.Location; //holds all of GriefPrevention's player-tied data diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java index 8e29960..e0cceea 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java @@ -150,7 +150,7 @@ class PlayerEventHandler implements Listener long millisecondsSinceLastMessage = (new Date()).getTime() - playerData.lastMessageTimestamp.getTime(); //if the message came too close to the last one - if(millisecondsSinceLastMessage < 2000) + if(millisecondsSinceLastMessage < 1500) { //increment the spam counter playerData.spamCount++; @@ -355,8 +355,6 @@ class PlayerEventHandler implements Listener String logMessage = logMessageBuilder.toString(); - GriefPrevention.AddLogEntry(logMessage.toString()); - Player [] players = GriefPrevention.instance.getServer().getOnlinePlayers(); for(int i = 0; i < players.length; i++) { @@ -381,7 +379,17 @@ class PlayerEventHandler implements Listener if(!GriefPrevention.instance.config_spam_enabled) return; //if the slash command used is in the list of monitored commands, treat it like a chat message (see above) - if(GriefPrevention.instance.config_spam_monitorSlashCommands.contains(args[0])) + boolean isMonitoredCommand = false; + for(String monitoredCommand : GriefPrevention.instance.config_spam_monitorSlashCommands) + { + if(args[0].equalsIgnoreCase(monitoredCommand)) + { + isMonitoredCommand = true; + break; + } + } + + if(isMonitoredCommand) { event.setCancelled(this.handlePlayerChat(event.getPlayer(), event.getMessage(), event)); } @@ -417,82 +425,18 @@ class PlayerEventHandler implements Listener return; } } + + //if logging-in account is banned, remember IP address for later + long now = Calendar.getInstance().getTimeInMillis(); + if(GriefPrevention.instance.config_smartBan && event.getResult() == Result.KICK_BANNED) + { + this.tempBannedIps.add(new IpBanInfo(event.getAddress(), now + this.MILLISECONDS_IN_DAY, player.getName())); + } } //remember the player's ip address PlayerData playerData = this.dataStore.getPlayerData(player.getName()); playerData.ipAddress = event.getAddress(); - - //FEATURE: auto-ban accounts who use an IP address which was very recently used by another banned account - if(GriefPrevention.instance.config_smartBan && !player.hasPlayedBefore()) - { - //if logging-in account is banned, remember IP address for later - long now = Calendar.getInstance().getTimeInMillis(); - if(event.getResult() == Result.KICK_BANNED) - { - this.tempBannedIps.add(new IpBanInfo(event.getAddress(), now + this.MILLISECONDS_IN_DAY, player.getName())); - } - - //otherwise if not banned - else - { - //search temporarily banned IP addresses for this one - for(int i = 0; i < this.tempBannedIps.size(); i++) - { - IpBanInfo info = this.tempBannedIps.get(i); - String address = info.address.toString(); - - //eliminate any expired entries - if(now > info.expirationTimestamp) - { - this.tempBannedIps.remove(i--); - } - - //if we find a match - else if(address.equals(playerData.ipAddress.toString())) - { - //if the account associated with the IP ban has been pardoned, remove all ip bans for that ip and we're done - OfflinePlayer bannedPlayer = GriefPrevention.instance.getServer().getOfflinePlayer(info.bannedAccountName); - if(!bannedPlayer.isBanned()) - { - for(int j = 0; j < this.tempBannedIps.size(); j++) - { - IpBanInfo info2 = this.tempBannedIps.get(j); - if(info2.address.toString().equals(address)) - { - OfflinePlayer bannedAccount = GriefPrevention.instance.getServer().getOfflinePlayer(info2.bannedAccountName); - bannedAccount.setBanned(false); - this.tempBannedIps.remove(j--); - } - } - - break; - } - - //otherwise if that account is still banned, ban this account, too - else - { - player.setBanned(true); - event.setResult(Result.KICK_BANNED); - event.disallow(event.getResult(), ""); - GriefPrevention.AddLogEntry("Auto-banned " + player.getName() + " because that account is using an IP address very recently used by banned player " + info.bannedAccountName + " (" + info.address.toString() + ")."); - - //notify any online ops - Player [] players = GriefPrevention.instance.getServer().getOnlinePlayers(); - for(int k = 0; k < players.length; k++) - { - if(players[k].isOp()) - { - GriefPrevention.sendMessage(players[k], TextMode.Success, Messages.AutoBanNotify, player.getName(), info.bannedAccountName); - } - } - - break; - } - } - } - } - } } //when a player spawns, conditionally apply temporary pvp protection @@ -508,18 +452,20 @@ class PlayerEventHandler implements Listener @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) void onPlayerJoin(PlayerJoinEvent event) { - String playerName = event.getPlayer().getName(); + Player player = event.getPlayer(); + String playerName = player.getName(); //note login time + long now = Calendar.getInstance().getTimeInMillis(); PlayerData playerData = this.dataStore.getPlayerData(playerName); - playerData.lastSpawn = Calendar.getInstance().getTimeInMillis(); + playerData.lastSpawn = now; playerData.lastLogin = new Date(); this.dataStore.savePlayerData(playerName, playerData); //if player has never played on the server before, may need pvp protection - if(!event.getPlayer().hasPlayedBefore()) + if(!player.hasPlayedBefore()) { - GriefPrevention.instance.checkPvpProtectionNeeded(event.getPlayer()); + GriefPrevention.instance.checkPvpProtectionNeeded(player); } //silence notifications when they're coming too fast @@ -527,6 +473,70 @@ class PlayerEventHandler implements Listener { event.setJoinMessage(null); } + + //FEATURE: auto-ban accounts who use an IP address which was very recently used by another banned account + if(GriefPrevention.instance.config_smartBan && !player.hasPlayedBefore()) + { + //search temporarily banned IP addresses for this one + for(int i = 0; i < this.tempBannedIps.size(); i++) + { + IpBanInfo info = this.tempBannedIps.get(i); + String address = info.address.toString(); + + //eliminate any expired entries + if(now > info.expirationTimestamp) + { + this.tempBannedIps.remove(i--); + } + + //if we find a match + else if(address.equals(playerData.ipAddress.toString())) + { + //if the account associated with the IP ban has been pardoned, remove all ip bans for that ip and we're done + OfflinePlayer bannedPlayer = GriefPrevention.instance.getServer().getOfflinePlayer(info.bannedAccountName); + if(!bannedPlayer.isBanned()) + { + for(int j = 0; j < this.tempBannedIps.size(); j++) + { + IpBanInfo info2 = this.tempBannedIps.get(j); + if(info2.address.toString().equals(address)) + { + OfflinePlayer bannedAccount = GriefPrevention.instance.getServer().getOfflinePlayer(info2.bannedAccountName); + bannedAccount.setBanned(false); + this.tempBannedIps.remove(j--); + } + } + + break; + } + + //otherwise if that account is still banned, ban this account, too + else + { + GriefPrevention.AddLogEntry("Auto-banned " + player.getName() + " because that account is using an IP address very recently used by banned player " + info.bannedAccountName + " (" + info.address.toString() + ")."); + + //notify any online ops + Player [] players = GriefPrevention.instance.getServer().getOnlinePlayers(); + for(int k = 0; k < players.length; k++) + { + if(players[k].isOp()) + { + GriefPrevention.sendMessage(players[k], TextMode.Success, Messages.AutoBanNotify, player.getName(), info.bannedAccountName); + } + } + + //ban player + PlayerKickBanTask task = new PlayerKickBanTask(player, ""); + GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 10L); + + //silence join message + event.setJoinMessage(""); + + break; + } + } + } + } } //when a player dies... @@ -576,6 +586,12 @@ class PlayerEventHandler implements Listener String playerName = player.getName(); PlayerData playerData = this.dataStore.getPlayerData(playerName); + //FEATURE: claims where players have allowed explosions will revert back to not allowing them when the owner logs out + for(Claim claim : playerData.claims) + { + claim.areExplosivesAllowed = false; + } + //FEATURE: players in pvp combat when they log out will die if(GriefPrevention.instance.config_pvp_punishLogout && playerData.inPvpCombat()) { @@ -658,14 +674,30 @@ class PlayerEventHandler implements Listener @EventHandler(priority = EventPriority.LOWEST) public void onPlayerTeleport(PlayerTeleportEvent event) { + Player player = event.getPlayer(); + PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + + //FEATURE: prevent players from using ender pearls to gain access to secured claims + if(event.getCause() == TeleportCause.ENDER_PEARL && GriefPrevention.instance.config_claims_enderPearlsRequireAccessTrust) + { + Claim toClaim = this.dataStore.getClaimAt(player.getLocation(), false, playerData.lastClaim); + if(toClaim != null) + { + playerData.lastClaim = toClaim; + String noAccessReason = toClaim.allowAccess(player); + if(noAccessReason != null) + { + GriefPrevention.sendMessage(player, TextMode.Err, noAccessReason); + event.setCancelled(true); + } + } + } + //FEATURE: prevent teleport abuse to win sieges //these rules only apply to non-ender-pearl teleportation if(event.getCause() == TeleportCause.ENDER_PEARL) return; - Player player = event.getPlayer(); - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); - Location source = event.getFrom(); Claim sourceClaim = this.dataStore.getClaimAt(source, false, playerData.lastClaim); if(sourceClaim != null && sourceClaim.siegeData != null) @@ -1526,7 +1558,7 @@ class PlayerEventHandler implements Listener } //if in a creative mode world and shrinking an existing claim, restore any unclaimed area - if(smaller && GriefPrevention.instance.creativeRulesApply(oldClaim.getLesserBoundaryCorner())) + if(smaller && GriefPrevention.instance.config_claims_autoRestoreUnclaimedCreativeLand && GriefPrevention.instance.creativeRulesApply(oldClaim.getLesserBoundaryCorner())) { GriefPrevention.sendMessage(player, TextMode.Warn, Messages.UnclaimCleanupWarning); GriefPrevention.instance.restoreClaim(oldClaim, 20L * 60 * 2); //2 minutes