diff --git a/plugin.yml b/plugin.yml index 79461ef..c100d7b 100644 --- a/plugin.yml +++ b/plugin.yml @@ -1,7 +1,7 @@ name: GriefPrevention main: me.ryanhamshire.GriefPrevention.GriefPrevention softdepend: [Vault, Multiverse-Core] -version: 4.5 +version: 4.6 commands: abandonclaim: description: Deletes a claim. @@ -83,7 +83,7 @@ commands: usage: /SellClaimBlocks aliases: sellclaim trapped: - description: Ejects you to nearby unclaimed land. Usable once per 8 hours. + description: Ejects you to nearby unclaimed land. Has a substantial cooldown period. usage: /Trapped trustlist: description: Lists permissions for the claim you're standing in. @@ -104,6 +104,10 @@ commands: description: Converts an administrative claim to a private claim. usage: /TransferClaim permission: griefprevention.adjustclaimblocks + deathblow: + description: Kills a player, optionally giving his inventory to another player. + usage: /DeathBlow [recipientPlayer] + permission: griefprevention.deathblow permissions: griefprevention.createclaims: description: Grants permission to create claims. @@ -120,6 +124,7 @@ permissions: griefprevention.spam: true griefprevention.lava: true griefprevention.eavesdrop: true + griefprevention.deathblow: true griefprevention.restorenature: description: Grants permission to use /RestoreNature. default: op @@ -146,4 +151,7 @@ permissions: default: op griefprevention.restorenatureaggressive: description: Grants access to /RestoreNatureAggressive and /RestoreNatureFill. + default: op + griefprevention.deathblow: + description: Grants access to /DeathBlow. default: op \ No newline at end of file diff --git a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java index 1b8a65d..1cdd341 100644 --- a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java @@ -94,7 +94,7 @@ public class BlockEventHandler implements Listener PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getName()); if(playerData.siegeData != null) { - GriefPrevention.sendMessage(player, TextMode.Err, "You can't give away items while involved in a siege."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeNoDrop); event.setCancelled(true); return; } @@ -109,7 +109,7 @@ public class BlockEventHandler implements Listener playerData.lastChestDamageLocation = block.getLocation(); //give the player instructions - GriefPrevention.sendMessage(player, TextMode.Instr, "To give away the item(s) in your hand, left-click the chest again."); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.DonateItemsInstruction); } //otherwise, try to donate the item stack in hand @@ -124,7 +124,7 @@ public class BlockEventHandler implements Listener if(availableSlot < 0) { //tell the player and stop here - GriefPrevention.sendMessage(player, TextMode.Err, "This chest is full."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.ChestFull); return; } @@ -135,7 +135,7 @@ public class BlockEventHandler implements Listener playerInventory.setItemInHand(new ItemStack(Material.AIR)); //and confirm for the player - GriefPrevention.sendMessage(player, TextMode.Success, "Item(s) transferred to chest!"); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.DonationSuccess); } } } @@ -162,8 +162,8 @@ public class BlockEventHandler implements Listener //if there's a claim here if(claim != null) { - //if breaking UNDER the claim - if(block.getY() < claim.lesserBoundaryCorner.getBlockY()) + //if breaking UNDER the claim and the player has permission to build in the claim + if(block.getY() < claim.lesserBoundaryCorner.getBlockY() && claim.allowBuild(player) == null) { //extend the claim downward beyond the breakage point this.dataStore.extendClaim(claim, claim.getLesserBoundaryCorner().getBlockY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance); @@ -226,27 +226,13 @@ public class BlockEventHandler implements Listener Location location = otherPlayer.getLocation(); if(!otherPlayer.equals(player) && location.distanceSquared(block.getLocation()) < 9) { - GriefPrevention.sendMessage(player, TextMode.Err, "You can't start a fire this close to " + otherPlayer.getName() + "."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerTooCloseForFire, otherPlayer.getName()); placeEvent.setCancelled(true); return; } } } - //FEATURE: limit tree planting to grass, and dirt with more earth beneath it - if(block.getType() == Material.SAPLING) - { - Block earthBlock = placeEvent.getBlockAgainst(); - if(earthBlock.getType() != Material.GRASS) - { - if(earthBlock.getRelative(BlockFace.DOWN).getType() == Material.AIR || - earthBlock.getRelative(BlockFace.DOWN).getRelative(BlockFace.DOWN).getType() == Material.AIR) - { - placeEvent.setCancelled(true); - } - } - } - //make sure the player is allowed to build at the location String noBuildReason = GriefPrevention.instance.allowBuild(player, block.getLocation()); if(noBuildReason != null) @@ -262,7 +248,7 @@ public class BlockEventHandler implements Listener if(claim != null) { //if the player has permission for the claim and he's placing UNDER the claim - if(block.getY() < claim.lesserBoundaryCorner.getBlockY()) + if(block.getY() < claim.lesserBoundaryCorner.getBlockY() && claim.allowBuild(player) == null) { //extend the claim downward this.dataStore.extendClaim(claim, claim.getLesserBoundaryCorner().getBlockY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance); @@ -277,7 +263,7 @@ public class BlockEventHandler implements Listener //if the chest is too deep underground, don't create the claim and explain why if(GriefPrevention.instance.config_claims_preventTheft && block.getY() < GriefPrevention.instance.config_claims_maxDepth) { - GriefPrevention.sendMessage(player, TextMode.Warn, "This chest can't be protected because it's too deep underground. Consider moving it."); + GriefPrevention.sendMessage(player, TextMode.Warn, Messages.TooDeepToClaim); return; } @@ -290,7 +276,7 @@ public class BlockEventHandler implements Listener if(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius == 0) { this.dataStore.createClaim(block.getWorld(), block.getX(), block.getX(), block.getY(), block.getY(), block.getZ(), block.getZ(), player.getName(), null); - GriefPrevention.sendMessage(player, TextMode.Success, "This chest is protected."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.ChestClaimConfirmation); } //otherwise, create a claim in the area around the chest @@ -309,7 +295,7 @@ public class BlockEventHandler implements Listener } //notify and explain to player - GriefPrevention.sendMessage(player, TextMode.Success, "This chest and nearby blocks are protected from breakage and theft. The gold and glowstone blocks mark the protected area."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.AutomaticClaimNotification); //show the player the protected area Claim newClaim = this.dataStore.getClaimAt(block.getLocation(), false, null); @@ -318,21 +304,35 @@ public class BlockEventHandler implements Listener } //instructions for using /trust - GriefPrevention.sendMessage(player, TextMode.Instr, "Use the /trust command to grant other players access."); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.TrustCommandAdvertisement); //unless special permission is required to create a claim with the shovel, educate the player about the shovel if(!GriefPrevention.instance.config_claims_creationRequiresPermission) { - GriefPrevention.sendMessage(player, TextMode.Instr, "To claim more land, use a golden shovel."); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.GoldenShovelAdvertisement); } } //check to see if this chest is in a claim, and warn when it isn't if(GriefPrevention.instance.config_claims_preventTheft && this.dataStore.getClaimAt(block.getLocation(), false, playerData.lastClaim) == null) { - GriefPrevention.sendMessage(player, TextMode.Warn, "This chest is NOT protected. Consider expanding an existing claim or creating a new one."); + GriefPrevention.sendMessage(player, TextMode.Warn, Messages.UnprotectedChestWarning); } } + + //FEATURE: limit wilderness tree planting to grass, or dirt with more blocks beneath it + else if(block.getType() == Material.SAPLING && GriefPrevention.instance.config_blockSkyTrees) + { + Block earthBlock = placeEvent.getBlockAgainst(); + if(earthBlock.getType() != Material.GRASS) + { + if(earthBlock.getRelative(BlockFace.DOWN).getType() == Material.AIR || + earthBlock.getRelative(BlockFace.DOWN).getRelative(BlockFace.DOWN).getType() == Material.AIR) + { + placeEvent.setCancelled(true); + } + } + } } //blocks "pushing" other players' blocks around (pistons) @@ -416,18 +416,29 @@ public class BlockEventHandler implements Listener } //ensures fluids don't flow out of claims, unless into another claim where the owner is trusted to build + private Claim lastSpreadClaim = null; @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onBlockFromTo (BlockFromToEvent spreadEvent) { + //always allow fluids to flow straight down + if(spreadEvent.getFace() == BlockFace.DOWN) return; + //from where? Block fromBlock = spreadEvent.getBlock(); - Claim fromClaim = this.dataStore.getClaimAt(fromBlock.getLocation(), false, null); + Claim fromClaim = this.dataStore.getClaimAt(fromBlock.getLocation(), false, this.lastSpreadClaim); + if(fromClaim != null) + { + this.lastSpreadClaim = fromClaim; + } //where to? Block toBlock = spreadEvent.getToBlock(); Claim toClaim = this.dataStore.getClaimAt(toBlock.getLocation(), false, fromClaim); - //block any spread into the wilderness + //if it's within the same claim or wilderness to wilderness, allow it + if(fromClaim == toClaim) return; + + //block any spread into the wilderness from a claim if(fromClaim != null && toClaim == null) { spreadEvent.setCancelled(true); @@ -440,10 +451,7 @@ public class BlockEventHandler implements Listener //who owns the spreading block, if anyone? OfflinePlayer fromOwner = null; if(fromClaim != null) - { - //if it's within the same claim, allow it - if(fromClaim == toClaim) return; - + { fromOwner = GriefPrevention.instance.getServer().getOfflinePlayer(fromClaim.ownerName); } diff --git a/src/me/ryanhamshire/GriefPrevention/Claim.java b/src/me/ryanhamshire/GriefPrevention/Claim.java index a2f6b71..eb7a137 100644 --- a/src/me/ryanhamshire/GriefPrevention/Claim.java +++ b/src/me/ryanhamshire/GriefPrevention/Claim.java @@ -24,6 +24,7 @@ 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; @@ -105,7 +106,7 @@ public class Claim //it may be null public void removeSurfaceFluids(Claim exclusionClaim) { - //don't do this automatically for administrative claims + //don't do this for administrative claims if(this.isAdminClaim()) return; Location lesser = this.getLesserBoundaryCorner(); @@ -134,8 +135,46 @@ public class Claim } } } + } + } + + //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(); + + int seaLevel = 0; //clean up all fluids in the end + + //respect sea level in normal worlds + if(lesser.getWorld().getEnvironment() == Environment.NORMAL) seaLevel = lesser.getWorld().getSeaLevel(); + + 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 @@ -245,7 +284,7 @@ public class Claim { if(this.siegeData != null) { - return "Claims can't be modified while under siege."; + return GriefPrevention.instance.dataStore.getMessage(Messages.NoModifyDuringSiege); } //otherwise, owners can do whatever @@ -257,7 +296,7 @@ public class Claim return this.parent.allowBuild(player); //error message if all else fails - return "Only " + this.getOwnerName() + " can modify this claim."; + return GriefPrevention.instance.dataStore.getMessage(Messages.OnlyOwnersModifyClaims, this.getOwnerName()); } //build permission check @@ -278,25 +317,24 @@ public class Claim //no building while under siege if(this.siegeData != null) { - return "This claim is under siege by " + this.siegeData.attacker.getName() + ". No one can build here."; + 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 "You can't build in claims during PvP combat."; + 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 - ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get(player.getName().toLowerCase()); - if(ClaimPermission.Build == permissionLevel) return null; + if(this.hasExplicitPermission(player, ClaimPermission.Build)) return null; //also everyone is a member of the "public", so check for public permission - permissionLevel = this.playerNameToClaimPermissionMap.get("public"); + ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get("public"); if(ClaimPermission.Build == permissionLevel) return null; //subdivision permission inheritance @@ -304,15 +342,38 @@ public class Claim return this.parent.allowBuild(player); //failure message for all other cases - return "You don't have " + this.getOwnerName() + "'s permission to build here."; + return GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildPermission, this.getOwnerName()); + } + + 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 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 ""; - //if under siege, some blocks will be breakable if(this.siegeData != null) { @@ -332,11 +393,11 @@ public class Claim //custom error messages for siege mode if(!breakable) { - return "That material is too tough to break."; + return GriefPrevention.instance.dataStore.getMessage(Messages.NonSiegeMaterial); } else if(this.ownerName.equals(player.getName())) { - return "You can't make changes while under siege."; + return GriefPrevention.instance.dataStore.getMessage(Messages.NoOwnerBuildUnderSiege); } else { @@ -351,9 +412,6 @@ public class Claim //access permission check public String allowAccess(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 ""; - //everyone always has access to admin claims if(this.isAdminClaim()) return null; @@ -364,19 +422,20 @@ public class Claim if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null; //look for explicit individual access, inventory, or build permission - ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get(player.getName().toLowerCase()); - if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel || ClaimPermission.Access == permissionLevel) return null; + 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 - permissionLevel = this.playerNameToClaimPermissionMap.get("public"); - if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel || ClaimPermission.Access == permissionLevel) return null; + ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get("public"); + if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel || ClaimPermission.Inventory == permissionLevel) return null; //permission inheritance for subdivisions if(this.parent != null) return this.parent.allowAccess(player); //catch-all error message for all other cases - return "You don't have " + this.getOwnerName() + "'s permission to use that."; + return GriefPrevention.instance.dataStore.getMessage(Messages.NoAccessPermission, this.getOwnerName()); } //inventory permission check @@ -391,7 +450,7 @@ public class Claim //if under siege, nobody accesses containers if(this.siegeData != null) { - return "This claim is under siege by " + siegeData.attacker.getName() + ". No one can access containers here right now."; + return GriefPrevention.instance.dataStore.getMessage(Messages.NoContainersSiege, siegeData.attacker.getName()); } //containers are always accessible in admin claims @@ -401,11 +460,11 @@ public class Claim if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null; //check for explicit individual container or build permission - ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get(player.getName().toLowerCase()); - if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel) return null; + if(this.hasExplicitPermission(player, ClaimPermission.Inventory)) return null; + if(this.hasExplicitPermission(player, ClaimPermission.Build)) return null; //check for public container or build permission - permissionLevel = this.playerNameToClaimPermissionMap.get("public"); + ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get("public"); if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel) return null; //permission inheritance for subdivisions @@ -413,7 +472,7 @@ public class Claim return this.parent.allowContainers(player); //error message for all other cases - return "You don't have " + this.getOwnerName() + "'s permission to use that."; + return GriefPrevention.instance.dataStore.getMessage(Messages.NoContainersPermission, this.getOwnerName()); } //grant permission check, relatively simple @@ -422,15 +481,29 @@ public class Claim //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, or who's explicitly in the managers (/PermissionTrust) list can do this - if(this.allowEdit(player) == null || this.managers.contains(player.getName())) return null; + //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 - return "You don't have " + this.getOwnerName() + "'s permission to grant permission here."; + return GriefPrevention.instance.dataStore.getMessage(Messages.NoPermissionTrust, this.getOwnerName()); } //grants a permission for a player or the public @@ -503,7 +576,7 @@ public class Claim return this.parent.getOwnerName(); if(this.ownerName.length() == 0) - return "an administrator"; + return GriefPrevention.instance.dataStore.getMessage(Messages.OwnerNameForAdminClaims); return this.ownerName; } @@ -612,7 +685,7 @@ public class Claim //determine maximum allowable entity count, based on claim size int maxEntities = this.getArea() / 50; - if(maxEntities == 0) return "This claim isn't big enough for that. Try enlarging it."; + if(maxEntities == 0) return GriefPrevention.instance.dataStore.getMessage(Messages.ClaimTooSmallForEntities); //count current entities (ignoring players) Chunk lesserChunk = this.getLesserBoundaryCorner().getChunk(); @@ -627,11 +700,15 @@ public class Claim for(int i = 0; i < entities.length; i++) { Entity entity = entities[i]; - if(!(entity instanceof Player) && this.contains(entity.getLocation(), false, false)) totalEntities++; + if(!(entity instanceof Player) && this.contains(entity.getLocation(), false, false)) + { + totalEntities++; + if(totalEntities > maxEntities) entity.remove(); + } } } - if(totalEntities > maxEntities) return "This claim has too many entities already. Try enlarging the claim or removing some animals, monsters, or minecarts."; + if(totalEntities > maxEntities) return GriefPrevention.instance.dataStore.getMessage(Messages.TooManyEntitiesInClaim); return null; } diff --git a/src/me/ryanhamshire/GriefPrevention/DataStore.java b/src/me/ryanhamshire/GriefPrevention/DataStore.java index 24c17bb..934bc37 100644 --- a/src/me/ryanhamshire/GriefPrevention/DataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DataStore.java @@ -25,6 +25,8 @@ import java.text.SimpleDateFormat; import java.util.*; import org.bukkit.*; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -34,14 +36,21 @@ public class DataStore //in-memory cache for player data private HashMap playerNameToPlayerDataMap = new HashMap(); + //in-memory cache for group (permission-based) data + private HashMap permissionToBonusBlocksMap = new HashMap(); + //in-memory cache for claim data - private ArrayList claims = new ArrayList(); + ArrayList claims = new ArrayList(); + + //in-memory cache for messages + private String [] messages; //path information, for where stuff stored on disk is well... stored private final static String dataLayerFolderPath = "plugins" + File.separator + "GriefPreventionData"; private final static String playerDataFolderPath = dataLayerFolderPath + File.separator + "PlayerData"; private final static String claimDataFolderPath = dataLayerFolderPath + File.separator + "ClaimData"; final static String configFilePath = dataLayerFolderPath + File.separator + "config.yml"; + final static String messagesFilePath = dataLayerFolderPath + File.separator + "messages.yml"; //initialization! DataStore() @@ -50,9 +59,45 @@ public class DataStore new File(playerDataFolderPath).mkdirs(); new File(claimDataFolderPath).mkdirs(); + //load group data into memory + File playerDataFolder = new File(playerDataFolderPath); + File [] files = playerDataFolder.listFiles(); + for(int i = 0; i < files.length; i++) + { + File file = files[i]; + if(!file.isFile()) continue; //avoids folders + + //all group data files start with an underscore. ignoring the rest, which are player data files. + if(!file.getName().startsWith("_")) continue; + + String groupName = file.getName().substring(1); + if(groupName == null || groupName.isEmpty()) continue; //defensive coding, avoid unlikely cases + + BufferedReader inStream = null; + try + { + inStream = new BufferedReader(new FileReader(file.getAbsolutePath())); + String line = inStream.readLine(); + + int groupBonusBlocks = Integer.parseInt(line); + + this.permissionToBonusBlocksMap.put(groupName, groupBonusBlocks); + } + catch(Exception e) + { + GriefPrevention.AddLogEntry("Unable to load group bonus block data from file \"" + file.getName() + "\": " + e.getMessage()); + } + + try + { + if(inStream != null) inStream.close(); + } + catch(IOException exception) {} + } + //load claims data into memory File claimDataFolder = new File(claimDataFolderPath); - File [] files = claimDataFolder.listFiles(); + files = claimDataFolder.listFiles(); int loadedClaimCount = 0; @@ -239,6 +284,9 @@ public class DataStore this.clearCachedPlayerData(playerName); } + //load up all the messages from messages.yml + this.loadMessages(); + //collect garbage, since lots of stuff was loaded into memory and then tossed out System.gc(); } @@ -249,6 +297,67 @@ public class DataStore this.playerNameToPlayerDataMap.remove(playerName); } + //gets the number of bonus blocks a player has from his permissions + int getGroupBonusBlocks(String playerName) + { + int bonusBlocks = 0; + Set keys = permissionToBonusBlocksMap.keySet(); + Iterator iterator = keys.iterator(); + while(iterator.hasNext()) + { + String groupName = iterator.next(); + Player player = GriefPrevention.instance.getServer().getPlayer(playerName); + if(player.hasPermission(groupName)) + { + bonusBlocks += this.permissionToBonusBlocksMap.get(groupName); + } + } + + return bonusBlocks; + } + + //grants a group (players with a specific permission) bonus claim blocks as long as they're still members of the group + public int adjustGroupBonusBlocks(String groupName, int amount) + { + Integer currentValue = this.permissionToBonusBlocksMap.get(groupName); + if(currentValue == null) currentValue = 0; + + currentValue += amount; + this.permissionToBonusBlocksMap.put(groupName, currentValue); + + //write changes to file to ensure they don't get lost + BufferedWriter outStream = null; + try + { + //open the group's file + File groupDataFile = new File(playerDataFolderPath + File.separator + "_" + groupName); + groupDataFile.createNewFile(); + outStream = new BufferedWriter(new FileWriter(groupDataFile)); + + //first line is number of bonus blocks + outStream.write(currentValue.toString()); + outStream.newLine(); + } + + //if any problem, log it + catch(Exception e) + { + GriefPrevention.AddLogEntry("Unexpected exception saving data for group \"" + groupName + "\": " + e.getMessage()); + } + + try + { + //close the file + if(outStream != null) + { + outStream.close(); + } + } + catch(IOException exception){} + + return currentValue; + } + public void changeClaimOwner(Claim claim, String newOwnerName) throws Exception { //if it's a subdivision, throw an exception @@ -482,6 +591,7 @@ public class DataStore File playerFile = new File(playerDataFolderPath + File.separator + playerName); playerData = new PlayerData(); + playerData.playerName = playerName; //if it doesn't exist as a file if(!playerFile.exists()) @@ -741,6 +851,12 @@ public class DataStore bigz = z1; } + //creative mode claims always go to bedrock + if(GriefPrevention.instance.config_claims_enabledCreativeWorlds.contains(world)) + { + smally = 2; + } + //create a new claim instance (but don't save it, yet) Claim newClaim = new Claim( new Location(world, smallx, smally, smallz), @@ -972,7 +1088,7 @@ public class DataStore if(winner != null) { //notify the winner - GriefPrevention.sendMessage(winner, TextMode.Success, "Congratulations! Buttons and levers are temporarily unlocked (five minutes)."); + GriefPrevention.sendMessage(winner, TextMode.Success, Messages.SiegeWinDoorsOpen); //schedule a task to secure the claims in about 5 minutes SecureClaimTask task = new SecureClaimTask(siegeData); @@ -1146,4 +1262,223 @@ public class DataStore return result; } + + private void loadMessages() + { + Messages [] messageIDs = Messages.values(); + this.messages = new String[Messages.values().length]; + + HashMap defaults = new HashMap(); + + //initialize defaults + this.addDefault(defaults, Messages.RespectingClaims, "Now respecting claims.", null); + this.addDefault(defaults, Messages.IgnoringClaims, "Now ignoring claims.", null); + this.addDefault(defaults, Messages.NoCreativeUnClaim, "You can't unclaim this land. You can only make this claim larger or create additional claims.", null); + this.addDefault(defaults, Messages.SuccessfulAbandon, "Claims abandoned. You now have {0} available claim blocks.", "0: remaining blocks"); + this.addDefault(defaults, Messages.RestoreNatureActivate, "Ready to restore some nature! Right click to restore nature, and use /BasicClaims to stop.", null); + this.addDefault(defaults, Messages.RestoreNatureAggressiveActivate, "Aggressive mode activated. Do NOT use this underneath anything you want to keep! Right click to aggressively restore nature, and use /BasicClaims to stop.", null); + this.addDefault(defaults, Messages.FillModeActive, "Fill mode activated with radius {0}. Right click an area to fill.", "0: fill radius"); + this.addDefault(defaults, Messages.TransferClaimPermission, "That command requires the administrative claims permission.", null); + this.addDefault(defaults, Messages.TransferClaimMissing, "There's no claim here. Stand in the administrative claim you want to transfer.", null); + this.addDefault(defaults, Messages.TransferClaimAdminOnly, "Only administrative claims may be transferred to a player.", null); + this.addDefault(defaults, Messages.PlayerNotFound, "Player not found.", null); + this.addDefault(defaults, Messages.TransferTopLevel, "Only top level claims (not subdivisions) may be transferred. Stand outside of the subdivision and try again.", null); + this.addDefault(defaults, Messages.TransferSuccess, "Claim transferred.", null); + this.addDefault(defaults, Messages.TrustListNoClaim, "Stand inside the claim you're curious about.", null); + this.addDefault(defaults, Messages.ClearPermsOwnerOnly, "Only the claim owner can clear all permissions.", null); + this.addDefault(defaults, Messages.UntrustIndividualAllClaims, "Revoked {0}'s access to ALL your claims. To set permissions for a single claim, stand inside it.", "0: untrusted player"); + this.addDefault(defaults, Messages.UntrustEveryoneAllClaims, "Cleared permissions in ALL your claims. To set permissions for a single claim, stand inside it.", null); + this.addDefault(defaults, Messages.NoPermissionTrust, "You don't have {0}'s permission to manage permissions here.", "0: claim owner's name"); + this.addDefault(defaults, Messages.ClearPermissionsOneClaim, "Cleared permissions in this claim. To set permission for ALL your claims, stand outside them.", null); + this.addDefault(defaults, Messages.UntrustIndividualSingleClaim, "Revoked {0}'s access to this claim. To set permissions for a ALL your claims, stand outside them.", "0: untrusted player"); + this.addDefault(defaults, Messages.OnlySellBlocks, "Claim blocks may only be sold, not purchased.", null); + this.addDefault(defaults, Messages.BlockPurchaseCost, "Each claim block costs {0}. Your balance is {1}.", "0: cost of one block; 1: player's account balance"); + this.addDefault(defaults, Messages.ClaimBlockLimit, "You've reached your claim block limit. You can't purchase more.", null); + this.addDefault(defaults, Messages.InsufficientFunds, "You don't have enough money. You need {0}, but you only have {1}.", "0: total cost; 1: player's account balance"); + this.addDefault(defaults, Messages.PurchaseConfirmation, "Withdrew {0} from your account. You now have {1} available claim blocks.", "0: total cost; 1: remaining blocks"); + this.addDefault(defaults, Messages.OnlyPurchaseBlocks, "Claim blocks may only be purchased, not sold.", null); + this.addDefault(defaults, Messages.BlockSaleValue, "Each claim block is worth {0}. You have {1} available for sale.", "0: block value; 1: available blocks"); + this.addDefault(defaults, Messages.NotEnoughBlocksForSale, "You don't have that many claim blocks available for sale.", null); + this.addDefault(defaults, Messages.BlockSaleConfirmation, "Deposited {0} in your account. You now have {1} available claim blocks.", "0: amount deposited; 1: remaining blocks"); + this.addDefault(defaults, Messages.AdminClaimsMode, "Administrative claims mode active. Any claims created will be free and editable by other administrators.", null); + this.addDefault(defaults, Messages.BasicClaimsMode, "Returned to basic claim creation mode.", null); + this.addDefault(defaults, Messages.SubdivisionMode, "Subdivision mode. Use your shovel to create subdivisions in your existing claims. Use /basicclaims to exit.", null); + this.addDefault(defaults, Messages.SubdivisionDemo, "Want a demonstration? http://tinyurl.com/7urdtue", null); + this.addDefault(defaults, Messages.DeleteClaimMissing, "There's no claim here.", null); + this.addDefault(defaults, Messages.DeletionSubdivisionWarning, "This claim includes subdivisions. If you're sure you want to delete it, use /DeleteClaim again.", null); + this.addDefault(defaults, Messages.DeleteSuccess, "Claim deleted.", null); + this.addDefault(defaults, Messages.CantDeleteAdminClaim, "You don't have permission to delete administrative claims.", null); + this.addDefault(defaults, Messages.DeleteAllSuccess, "Deleted all of {0}'s claims.", "0: owner's name"); + this.addDefault(defaults, Messages.NoDeletePermission, "You don't have permission to delete claims.", null); + this.addDefault(defaults, Messages.AllAdminDeleted, "Deleted all administrative claims.", null); + this.addDefault(defaults, Messages.AdjustBlocksSuccess, "Adjusted {0}'s bonus claim blocks by {1}. New total bonus blocks: {2}.", "0: player; 1: adjustment; 2: new total"); + this.addDefault(defaults, Messages.NotTrappedHere, "You can build here. Save yourself.", null); + this.addDefault(defaults, Messages.TrappedOnCooldown, "You used /trapped within the last {0} hours. You have to wait about {1} more minutes before using it again.", "0: default cooldown hours; 1: remaining minutes"); + this.addDefault(defaults, Messages.RescuePending, "If you stay put for 10 seconds, you'll be teleported out. Please wait.", null); + this.addDefault(defaults, Messages.NonSiegeWorld, "Siege is disabled here.", null); + this.addDefault(defaults, Messages.AlreadySieging, "You're already involved in a siege.", null); + this.addDefault(defaults, Messages.AlreadyUnderSiegePlayer, "{0} is already under siege. Join the party!", "0: defending player"); + this.addDefault(defaults, Messages.NotSiegableThere, "{0} isn't protected there.", "0: defending player"); + this.addDefault(defaults, Messages.SiegeTooFarAway, "You're too far away to siege.", null); + this.addDefault(defaults, Messages.NoSiegeDefenseless, "That player is defenseless. Go pick on somebody else.", null); + this.addDefault(defaults, Messages.AlreadyUnderSiegeArea, "That area is already under siege. Join the party!", null); + this.addDefault(defaults, Messages.NoSiegeAdminClaim, "Siege is disabled in this area.", null); + this.addDefault(defaults, Messages.SiegeOnCooldown, "You're still on siege cooldown for this defender or claim. Find another victim.", null); + this.addDefault(defaults, Messages.SiegeAlert, "You're under siege! If you log out now, you will die. You must defeat {0}, wait for him to give up, or escape.", "0: attacker name"); + this.addDefault(defaults, Messages.SiegeConfirmed, "The siege has begun! If you log out now, you will die. You must defeat {0}, chase him away, or admit defeat and walk away.", "0: defender name"); + this.addDefault(defaults, Messages.AbandonClaimMissing, "Stand in the claim you want to delete, or consider /AbandonAllClaims.", null); + this.addDefault(defaults, Messages.NotYourClaim, "This isn't your claim.", null); + this.addDefault(defaults, Messages.DeleteTopLevelClaim, "To delete a subdivision, stand inside it. Otherwise, use /AbandonTopLevelClaim to delete this claim and all subdivisions.", null); + this.addDefault(defaults, Messages.AbandonSuccess, "Claim abandoned. You now have {0} available claim blocks.", "0: remaining claim blocks"); + this.addDefault(defaults, Messages.CantGrantThatPermission, "You can't grant a permission you don't have yourself.", null); + this.addDefault(defaults, Messages.GrantPermissionNoClaim, "Stand inside the claim where you want to grant permission.", null); + this.addDefault(defaults, Messages.GrantPermissionConfirmation, "Granted {0} permission to {1} {2}.", "0: target player; 1: permission description; 2: scope (changed claims)"); + this.addDefault(defaults, Messages.ManageUniversalPermissionsInstruction, "To manage permissions for ALL your claims, stand outside them.", null); + this.addDefault(defaults, Messages.ManageOneClaimPermissionsInstruction, "To manage permissions for a specific claim, stand inside it.", null); + this.addDefault(defaults, Messages.CollectivePublic, "the public", "as in 'granted the public permission to...'"); + this.addDefault(defaults, Messages.BuildPermission, "build", null); + this.addDefault(defaults, Messages.ContainersPermission, "access containers and animals", null); + this.addDefault(defaults, Messages.AccessPermission, "use buttons and levers", null); + this.addDefault(defaults, Messages.PermissionsPermission, "manage permissions", null); + this.addDefault(defaults, Messages.LocationCurrentClaim, "in this claim", null); + this.addDefault(defaults, Messages.LocationAllClaims, "in all your claims", null); + this.addDefault(defaults, Messages.PvPImmunityStart, "You're protected from attack by other players as long as your inventory is empty.", null); + this.addDefault(defaults, Messages.SiegeNoDrop, "You can't give away items while involved in a siege.", null); + this.addDefault(defaults, Messages.DonateItemsInstruction, "To give away the item(s) in your hand, left-click the chest again.", null); + this.addDefault(defaults, Messages.ChestFull, "This chest is full.", null); + this.addDefault(defaults, Messages.DonationSuccess, "Item(s) transferred to chest!", null); + this.addDefault(defaults, Messages.PlayerTooCloseForFire, "You can't start a fire this close to {0}.", "0: other player's name"); + this.addDefault(defaults, Messages.TooDeepToClaim, "This chest can't be protected because it's too deep underground. Consider moving it.", null); + this.addDefault(defaults, Messages.ChestClaimConfirmation, "This chest is protected.", null); + this.addDefault(defaults, Messages.AutomaticClaimNotification, "This chest and nearby blocks are protected from breakage and theft. The gold and glowstone blocks mark the protected area.", null); + this.addDefault(defaults, Messages.TrustCommandAdvertisement, "Use the /trust command to grant other players access.", null); + this.addDefault(defaults, Messages.GoldenShovelAdvertisement, "To claim more land, use a golden shovel.", null); + this.addDefault(defaults, Messages.UnprotectedChestWarning, "This chest is NOT protected. Consider expanding an existing claim or creating a new one.", null); + this.addDefault(defaults, Messages.ThatPlayerPvPImmune, "You can't injure defenseless players.", null); + this.addDefault(defaults, Messages.CantFightWhileImmune, "You can't fight someone while you're protected from PvP.", null); + this.addDefault(defaults, Messages.NoDamageClaimedEntity, "That belongs to {0}.", "0: owner name"); + this.addDefault(defaults, Messages.ShovelBasicClaimMode, "Shovel returned to basic claims mode.", null); + this.addDefault(defaults, Messages.RemainingBlocks, "You may claim up to {0} more blocks.", "0: remaining blocks"); + this.addDefault(defaults, Messages.CreativeBasicsDemoAdvertisement, "Want a demonstration? http://tinyurl.com/c7bajb8", null); + this.addDefault(defaults, Messages.SurvivalBasicsDemoAdvertisement, "Want a demonstration? http://tinyurl.com/6nkwegj", null); + this.addDefault(defaults, Messages.TrappedChatKeyword, "trapped", "When mentioned in chat, players get information about the /trapped command."); + this.addDefault(defaults, Messages.TrappedInstructions, "Are you trapped in someone's claim? Consider the /trapped command.", null); + this.addDefault(defaults, Messages.PvPNoDrop, "You can't drop items while in PvP combat.", null); + this.addDefault(defaults, Messages.SiegeNoTeleport, "You can't teleport out of a besieged area.", null); + this.addDefault(defaults, Messages.BesiegedNoTeleport, "You can't teleport into a besieged area.", null); + this.addDefault(defaults, Messages.SiegeNoContainers, "You can't access containers while involved in a siege.", null); + this.addDefault(defaults, Messages.PvPNoContainers, "You can't access containers during PvP combat.", null); + this.addDefault(defaults, Messages.PvPImmunityEnd, "Now you can fight with other players.", null); + this.addDefault(defaults, Messages.NoBedPermission, "{0} hasn't given you permission to sleep here.", "0: claim owner"); + this.addDefault(defaults, Messages.NoWildernessBuckets, "You may only dump buckets inside your claim(s) or underground.", null); + this.addDefault(defaults, Messages.NoLavaNearOtherPlayer, "You can't place lava this close to {0}.", "0: nearby player"); + this.addDefault(defaults, Messages.TooFarAway, "That's too far away.", null); + this.addDefault(defaults, Messages.BlockNotClaimed, "No one has claimed this block.", null); + this.addDefault(defaults, Messages.BlockClaimed, "That block has been claimed by {0}.", "0: claim owner"); + this.addDefault(defaults, Messages.SiegeNoShovel, "You can't use your shovel tool while involved in a siege.", null); + this.addDefault(defaults, Messages.RestoreNaturePlayerInChunk, "Unable to restore. {0} is in that chunk.", "0: nearby player"); + this.addDefault(defaults, Messages.NoCreateClaimPermission, "You don't have permission to claim land.", null); + this.addDefault(defaults, Messages.ResizeClaimTooSmall, "This new size would be too small. Claims must be at least {0} x {0}.", "0: minimum claim size"); + this.addDefault(defaults, Messages.ResizeNeedMoreBlocks, "You don't have enough blocks for this size. You need {0} more.", "0: how many needed"); + this.addDefault(defaults, Messages.ClaimResizeSuccess, "Claim resized. You now have {0} available claim blocks.", "0: remaining blocks"); + this.addDefault(defaults, Messages.ResizeFailOverlap, "Can't resize here because it would overlap another nearby claim.", null); + this.addDefault(defaults, Messages.ResizeStart, "Resizing claim. Use your shovel again at the new location for this corner.", null); + this.addDefault(defaults, Messages.ResizeFailOverlapSubdivision, "You can't create a subdivision here because it would overlap another subdivision. Consider /abandonclaim to delete it, or use your shovel at a corner to resize it.", null); + this.addDefault(defaults, Messages.SubdivisionStart, "Subdivision corner set! Use your shovel at the location for the opposite corner of this new subdivision.", null); + this.addDefault(defaults, Messages.CreateSubdivisionOverlap, "Your selected area overlaps another subdivision.", null); + this.addDefault(defaults, Messages.SubdivisionSuccess, "Subdivision created! Use /trust to share it with friends.", null); + this.addDefault(defaults, Messages.CreateClaimFailOverlap, "You can't create a claim here because it would overlap your other claim. Use /abandonclaim to delete it, or use your shovel at a corner to resize it.", null); + this.addDefault(defaults, Messages.CreateClaimFailOverlapOtherPlayer, "You can't create a claim here because it would overlap {0}'s claim.", "0: other claim owner"); + this.addDefault(defaults, Messages.ClaimsDisabledWorld, "Land claims are disabled in this world.", null); + this.addDefault(defaults, Messages.ClaimStart, "Claim corner set! Use the shovel again at the opposite corner to claim a rectangle of land. To cancel, put your shovel away.", null); + this.addDefault(defaults, Messages.NewClaimTooSmall, "This claim would be too small. Any claim must be at least {0} x {0}.", "0: minimum claim size"); + this.addDefault(defaults, Messages.CreateClaimInsufficientBlocks, "You don't have enough blocks to claim that entire area. You need {0} more blocks.", "0: additional blocks needed"); + this.addDefault(defaults, Messages.AbandonClaimAdvertisement, "To delete another claim and free up some blocks, use /AbandonClaim.", null); + this.addDefault(defaults, Messages.CreateClaimFailOverlapShort, "Your selected area overlaps an existing claim.", null); + this.addDefault(defaults, Messages.CreateClaimSuccess, "Claim created! Use /trust to share it with friends.", null); + this.addDefault(defaults, Messages.SiegeWinDoorsOpen, "Congratulations! Buttons and levers are temporarily unlocked (five minutes).", null); + this.addDefault(defaults, Messages.RescueAbortedMoved, "You moved! Rescue cancelled.", null); + this.addDefault(defaults, Messages.SiegeDoorsLockedEjection, "Looting time is up! Ejected from the claim.", null); + this.addDefault(defaults, Messages.NoModifyDuringSiege, "Claims can't be modified while under siege.", null); + this.addDefault(defaults, Messages.OnlyOwnersModifyClaims, "Only {0} can modify this claim.", "0: owner name"); + this.addDefault(defaults, Messages.NoBuildUnderSiege, "This claim is under siege by {0}. No one can build here.", "0: attacker name"); + this.addDefault(defaults, Messages.NoBuildPvP, "You can't build in claims during PvP combat.", null); + this.addDefault(defaults, Messages.NoBuildPermission, "You don't have {0}'s permission to build here.", "0: owner name"); + this.addDefault(defaults, Messages.NonSiegeMaterial, "That material is too tough to break.", null); + this.addDefault(defaults, Messages.NoOwnerBuildUnderSiege, "You can't make changes while under siege.", null); + this.addDefault(defaults, Messages.NoAccessPermission, "You don't have {0}'s permission to use that.", "0: owner name. access permission controls buttons, levers, and beds"); + this.addDefault(defaults, Messages.NoContainersSiege, "This claim is under siege by {0}. No one can access containers here right now.", "0: attacker name"); + this.addDefault(defaults, Messages.NoContainersPermission, "You don't have {0}'s permission to use that.", "0: owner's name. containers also include crafting blocks"); + this.addDefault(defaults, Messages.OwnerNameForAdminClaims, "an administrator", "as in 'You don't have an administrator's permission to build here.'"); + this.addDefault(defaults, Messages.ClaimTooSmallForEntities, "This claim isn't big enough for that. Try enlarging it.", null); + this.addDefault(defaults, Messages.TooManyEntitiesInClaim, "This claim has too many entities already. Try enlarging the claim or removing some animals, monsters, paintings, or minecarts.", null); + this.addDefault(defaults, Messages.YouHaveNoClaims, "You don't have any land claims.", null); + this.addDefault(defaults, Messages.ConfirmFluidRemoval, "Abandoning this claim will remove all your lava and water. If you're sure, use /AbandonClaim again.", null); + this.addDefault(defaults, Messages.AutoBanNotify, "Auto-banned {0}({1}). See logs for details.", null); + this.addDefault(defaults, Messages.AdjustGroupBlocksSuccess, "Adjusted bonus claim blocks for players with the {0} permission by {1}. New total: {2}.", "0: permission; 1: adjustment amount; 2: new total bonus"); + this.addDefault(defaults, Messages.InvalidPermissionID, "Please specify a player name, or a permission in [brackets].", null); + this.addDefault(defaults, Messages.UntrustOwnerOnly, "Only {0} can revoke permissions here.", "0: claim owner's name"); + + //load the config file + FileConfiguration config = YamlConfiguration.loadConfiguration(new File(messagesFilePath)); + + //for each message ID + for(int i = 0; i < messageIDs.length; i++) + { + //get default for this message + Messages messageID = messageIDs[i]; + CustomizableMessage messageData = defaults.get(messageID.name()); + + //if default is missing, log an error and use some fake data for now so that the plugin can run + if(messageData == null) + { + GriefPrevention.AddLogEntry("Missing message for " + messageID.name() + ". Please contact the developer."); + messageData = new CustomizableMessage(messageID, "Missing message! ID: " + messageID.name() + ". Please contact a server admin.", null); + } + + //read the message from the file, use default if necessary + this.messages[messageID.ordinal()] = config.getString("Messages." + messageID.name() + ".Text", messageData.text); + config.set("Messages." + messageID.name() + ".Text", this.messages[messageID.ordinal()]); + + if(messageData.notes != null) + { + messageData.notes = config.getString("Messages." + messageID.name() + ".Notes", messageData.notes); + config.set("Messages." + messageID.name() + ".Notes", messageData.notes); + } + } + + //save any changes + try + { + config.save(DataStore.messagesFilePath); + } + catch(IOException exception) + { + GriefPrevention.AddLogEntry("Unable to write to the configuration file at \"" + DataStore.messagesFilePath + "\""); + } + + defaults.clear(); + System.gc(); + } + + private void addDefault(HashMap defaults, + Messages id, String text, String notes) + { + CustomizableMessage message = new CustomizableMessage(id, text, notes); + defaults.put(id.name(), message); + } + + public String getMessage(Messages messageID, String... args) + { + String message = messages[messageID.ordinal()]; + + for(int i = 0; i < args.length; i++) + { + String param = args[i]; + message = message.replace("{" + i + "}", param); + } + + return message; + + } } diff --git a/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java b/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java index 3ebd584..2b90a09 100644 --- a/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java @@ -25,11 +25,12 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World.Environment; import org.bukkit.block.Block; -import org.bukkit.entity.Animals; import org.bukkit.entity.Arrow; +import org.bukkit.entity.Creature; import org.bukkit.entity.Enderman; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Monster; import org.bukkit.entity.Player; import org.bukkit.entity.ThrownPotion; import org.bukkit.entity.Vehicle; @@ -44,6 +45,7 @@ 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.ExpBottleEvent; import org.bukkit.event.entity.ItemSpawnEvent; import org.bukkit.event.painting.PaintingBreakByEntityEvent; import org.bukkit.event.painting.PaintingBreakEvent; @@ -118,6 +120,17 @@ class EntityEventHandler implements Listener } } + //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) @@ -129,7 +142,7 @@ class EntityEventHandler implements Listener //chicken eggs and breeding could potentially make a mess in the wilderness, once griefers get involved SpawnReason reason = event.getSpawnReason(); - if(reason == SpawnReason.EGG || reason == SpawnReason.BREEDING) + if(reason != SpawnReason.SPAWNER_EGG && reason != SpawnReason.BUILD_IRONGOLEM && reason != SpawnReason.BUILD_SNOWMAN) { event.setCancelled(true); return; @@ -241,7 +254,24 @@ class EntityEventHandler implements Listener { 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.getPainting().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 @@ -251,6 +281,9 @@ class EntityEventHandler implements Listener //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 @@ -300,14 +333,14 @@ class EntityEventHandler implements Listener if(defenderData.pvpImmune) { event.setCancelled(true); - GriefPrevention.sendMessage(attacker, TextMode.Err, "You can't injure defenseless players."); + GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.ThatPlayerPvPImmune); return; } if(attackerData.pvpImmune) { event.setCancelled(true); - GriefPrevention.sendMessage(attacker, TextMode.Err, "You can't fight someone while you're protected from PvP."); + GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune); return; } } @@ -327,12 +360,21 @@ class EntityEventHandler implements Listener //so unless precautions are taken by the owner, a resourceful thief might find ways to steal anyway //if theft protection is enabled - if(GriefPrevention.instance.config_claims_preventTheft && event instanceof EntityDamageByEntityEvent) + if(event instanceof EntityDamageByEntityEvent) { - //if the entity is an animal or a vehicle - if (subEvent.getEntity() instanceof Animals || subEvent.getEntity() instanceof Vehicle) + //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) || + (subEvent.getEntity() instanceof Vehicle && GriefPrevention.instance.config_claims_preventTheft)) { - Claim claim = this.dataStore.getClaimAt(event.getEntity().getLocation(), false, null); + 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) @@ -350,8 +392,14 @@ class EntityEventHandler implements Listener if(noContainersReason != null) { event.setCancelled(true); - GriefPrevention.sendMessage(attacker, TextMode.Err, "That belongs to " + claim.getOwnerName() + "."); + 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/EquipShovelProcessingTask.java b/src/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java index 1a7d26c..a79870c 100644 --- a/src/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java +++ b/src/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java @@ -53,13 +53,22 @@ class EquipShovelProcessingTask implements Runnable if(playerData.shovelMode != ShovelMode.Basic) { playerData.shovelMode = ShovelMode.Basic; - GriefPrevention.sendMessage(player, TextMode.Info, "Shovel returned to basic claims mode."); + GriefPrevention.sendMessage(player, TextMode.Info, Messages.ShovelBasicClaimMode); } int remainingBlocks = playerData.getRemainingClaimBlocks(); //instruct him in the steps to create a claim - GriefPrevention.sendMessage(player, TextMode.Instr, "You may claim up to " + String.valueOf(remainingBlocks) + " more blocks."); - GriefPrevention.sendMessage(player, TextMode.Instr, "Want a demonstration? http://tinyurl.com/6nkwegj"); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.RemainingBlocks, String.valueOf(remainingBlocks)); + + //demo link changes based on game mode + if(GriefPrevention.instance.creativeRulesApply(player.getLocation())) + { + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.CreativeBasicsDemoAdvertisement); + } + else + { + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsDemoAdvertisement); + } } } diff --git a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java index 53e2c9a..bf3b3ad 100644 --- a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java +++ b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java @@ -35,6 +35,7 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.OfflinePlayer; import org.bukkit.World; +import org.bukkit.World.Environment; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.command.*; @@ -63,6 +64,7 @@ public class GriefPrevention extends JavaPlugin public ArrayList config_claims_enabledCreativeWorlds; //list of worlds where additional creative mode anti-grief rules apply public boolean config_claims_preventTheft; //whether containers and crafting blocks are protectable + public boolean config_claims_protectCreatures; //whether claimed animals may be injured by players without permission public boolean config_claims_preventButtonsSwitches; //whether buttons and switches are protectable public int config_claims_initialBlocks; //the number of claim blocks a new player starts with @@ -101,6 +103,8 @@ public class GriefPrevention extends JavaPlugin public double config_economy_claimBlocksSellValue; //return on a sold claim block. set to zero to disable sale. public boolean config_blockSurfaceExplosions; //whether creeper/TNT explosions near or above the surface destroy blocks + public boolean config_blockWildernessWaterBuckets; //whether players can dump water buckets outside their claims + public boolean config_blockSkyTrees; //whether players can build trees on platforms in the sky public boolean config_fireSpreads; //whether fire spreads outside of claims public boolean config_fireDestroys; //whether fire destroys blocks outside of claims @@ -204,6 +208,7 @@ public class GriefPrevention extends JavaPlugin } this.config_claims_preventTheft = config.getBoolean("GriefPrevention.Claims.PreventTheft", true); + this.config_claims_protectCreatures = config.getBoolean("GriefPrevention.Claims.ProtectCreatures", true); this.config_claims_preventButtonsSwitches = config.getBoolean("GriefPrevention.Claims.PreventButtonsSwitches", true); this.config_claims_initialBlocks = config.getInt("GriefPrevention.Claims.InitialBlocks", 100); this.config_claims_blocksAccruedPerHour = config.getInt("GriefPrevention.Claims.BlocksAccruedPerHour", 100); @@ -236,7 +241,9 @@ public class GriefPrevention extends JavaPlugin this.config_economy_claimBlocksSellValue = config.getDouble("GriefPrevention.Economy.ClaimBlocksSellValue", 0); this.config_blockSurfaceExplosions = config.getBoolean("GriefPrevention.BlockSurfaceExplosions", true); - + this.config_blockWildernessWaterBuckets = config.getBoolean("GriefPrevention.LimitSurfaceWaterBuckets", true); + this.config_blockSkyTrees = config.getBoolean("GriefPrevention.LimitSkyTrees", true); + this.config_fireSpreads = config.getBoolean("GriefPrevention.FireSpreads", false); this.config_fireDestroys = config.getBoolean("GriefPrevention.FireDestroys", false); @@ -320,6 +327,7 @@ public class GriefPrevention extends JavaPlugin 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.InitialBlocks", this.config_claims_initialBlocks); config.set("GriefPrevention.Claims.BlocksAccruedPerHour", this.config_claims_blocksAccruedPerHour); @@ -352,6 +360,8 @@ public class GriefPrevention extends JavaPlugin config.set("GriefPrevention.Economy.ClaimBlocksSellValue", this.config_economy_claimBlocksSellValue); config.set("GriefPrevention.BlockSurfaceExplosions", this.config_blockSurfaceExplosions); + config.set("GriefPrevention.LimitSurfaceWaterBuckets", this.config_blockWildernessWaterBuckets); + config.set("GriefPrevention.LimitSkyTrees", this.config_blockSkyTrees); config.set("GriefPrevention.FireSpreads", this.config_fireSpreads); config.set("GriefPrevention.FireDestroys", this.config_fireDestroys); @@ -392,6 +402,10 @@ public class GriefPrevention extends JavaPlugin this.getServer().getScheduler().scheduleSyncRepeatingTask(this, task, 20L * 60 * 5, 20L * 60 * 5); } + //start the recurring cleanup event for entities in creative worlds + EntityCleanupTask task = new EntityCleanupTask(0); + this.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 20L); + //register for events PluginManager pluginManager = this.getServer().getPluginManager(); @@ -475,11 +489,11 @@ public class GriefPrevention extends JavaPlugin //toggle ignore claims mode on or off if(!playerData.ignoreClaims) { - GriefPrevention.sendMessage(player, TextMode.Success, "Now respecting claims."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.RespectingClaims); } else { - GriefPrevention.sendMessage(player, TextMode.Success, "Now ignoring claims."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.IgnoringClaims); } return true; @@ -492,7 +506,7 @@ public class GriefPrevention extends JavaPlugin if(creativeRulesApply(player.getLocation())) { - GriefPrevention.sendMessage(player, TextMode.Err, "Creative mode claims can't be abandoned."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoCreativeUnClaim); return true; } @@ -503,7 +517,7 @@ public class GriefPrevention extends JavaPlugin //check count if(originalClaimCount == 0) { - GriefPrevention.sendMessage(player, TextMode.Err, "You haven't claimed any land."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.YouHaveNoClaims); return true; } @@ -512,7 +526,7 @@ public class GriefPrevention extends JavaPlugin //inform the player int remainingBlocks = playerData.getRemainingClaimBlocks(); - GriefPrevention.sendMessage(player, TextMode.Success, "Claims abandoned. You now have " + String.valueOf(remainingBlocks) + " available claim blocks."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.SuccessfulAbandon, String.valueOf(remainingBlocks)); //revert any current visualization Visualization.Revert(player); @@ -526,7 +540,7 @@ public class GriefPrevention extends JavaPlugin //change shovel mode PlayerData playerData = this.dataStore.getPlayerData(player.getName()); playerData.shovelMode = ShovelMode.RestoreNature; - GriefPrevention.sendMessage(player, TextMode.Instr, "Ready to restore some nature! Right click to restore nature, and use /BasicClaims to stop."); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.RestoreNatureActivate); return true; } @@ -536,7 +550,7 @@ public class GriefPrevention extends JavaPlugin //change shovel mode PlayerData playerData = this.dataStore.getPlayerData(player.getName()); playerData.shovelMode = ShovelMode.RestoreNatureAggressive; - GriefPrevention.sendMessage(player, TextMode.Warn, "Aggressive mode activated. Do NOT use this underneath anything you want to keep! Right click to aggressively restore nature, and use /BasicClaims to stop."); + GriefPrevention.sendMessage(player, TextMode.Warn, Messages.RestoreNatureAggressiveActivate); return true; } @@ -560,7 +574,7 @@ public class GriefPrevention extends JavaPlugin if(playerData.fillRadius < 0) playerData.fillRadius = 2; - GriefPrevention.sendMessage(player, TextMode.Success, "Fill mode activated with radius " + playerData.fillRadius + ". Right-click an area to fill."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.FillModeActive, String.valueOf(playerData.fillRadius)); return true; } @@ -585,7 +599,7 @@ public class GriefPrevention extends JavaPlugin //check additional permission if(!player.hasPermission("griefprevention.adminclaims")) { - GriefPrevention.sendMessage(player, TextMode.Err, "That command requires the administrative claims permission."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.TransferClaimPermission); return true; } @@ -593,19 +607,19 @@ public class GriefPrevention extends JavaPlugin Claim claim = this.dataStore.getClaimAt(player.getLocation(), true, null); if(claim == null) { - GriefPrevention.sendMessage(player, TextMode.Instr, "There's no claim here. Stand in the administrative claim you want to transfer."); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.TransferClaimMissing); return true; } else if(!claim.isAdminClaim()) { - GriefPrevention.sendMessage(player, TextMode.Err, "Only administrative claims may be transferred to a player."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.TransferClaimAdminOnly); return true; } OfflinePlayer targetPlayer = this.resolvePlayer(args[0]); if(targetPlayer == null) { - GriefPrevention.sendMessage(player, TextMode.Err, "Player not found."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound); return true; } @@ -616,12 +630,12 @@ public class GriefPrevention extends JavaPlugin } catch(Exception e) { - GriefPrevention.sendMessage(player, TextMode.Instr, "Only top level claims (not subdivisions) may be transferred. Stand outside of the subdivision and try again."); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.TransferTopLevel); return true; } //confirm - GriefPrevention.sendMessage(player, TextMode.Success, "Claim transferred."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.TransferSuccess); GriefPrevention.AddLogEntry(player.getName() + " transferred a claim at " + GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner()) + " to " + targetPlayer.getName() + "."); return true; @@ -635,7 +649,7 @@ public class GriefPrevention extends JavaPlugin //if no claim here, error message if(claim == null) { - GriefPrevention.sendMessage(player, TextMode.Err, "Stand inside the claim you're curious about."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.TrustListNoClaim); return true; } @@ -703,7 +717,7 @@ public class GriefPrevention extends JavaPlugin return true; } - //untrust + //untrust or untrust [] else if(cmd.getName().equalsIgnoreCase("untrust") && player != null) { //requires exactly one parameter, the other player's name @@ -723,24 +737,27 @@ public class GriefPrevention extends JavaPlugin } else { - GriefPrevention.sendMessage(player, TextMode.Err, "Only the claim owner can clear all permissions."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.ClearPermsOwnerOnly); return true; } } else { - //validate player argument - otherPlayer = this.resolvePlayer(args[0]); - if(!clearPermissions && otherPlayer == null && !args[0].equals("public")) + //validate player argument or group argument + if(!args[0].startsWith("[") || !args[0].endsWith("]")) { - GriefPrevention.sendMessage(player, TextMode.Err, "Player not found."); - return true; + otherPlayer = this.resolvePlayer(args[0]); + if(!clearPermissions && otherPlayer == null && !args[0].equals("public")) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound); + return true; + } + + //correct to proper casing + if(otherPlayer != null) + args[0] = otherPlayer.getName(); } - - //correct to proper casing - if(otherPlayer != null) - args[0] = otherPlayer.getName(); } //if no claim here, apply changes to all his claims @@ -777,18 +794,18 @@ public class GriefPrevention extends JavaPlugin //confirmation message if(!clearPermissions) { - GriefPrevention.sendMessage(player, TextMode.Success, "Revoked " + args[0] + "'s access to ALL your claims. To set permissions for a single claim, stand inside it."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.UntrustIndividualAllClaims, args[0]); } else { - GriefPrevention.sendMessage(player, TextMode.Success, "Cleared permissions in ALL your claims. To set permissions for a single claim, stand inside it."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.UntrustEveryoneAllClaims); } } //otherwise, apply changes to only this claim else if(claim.allowGrantPermission(player) != null) { - GriefPrevention.sendMessage(player, TextMode.Err, "You don't have " + claim.getOwnerName() + "'s permission to manage permissions here."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoPermissionTrust, claim.getOwnerName()); } else { @@ -796,7 +813,7 @@ public class GriefPrevention extends JavaPlugin if(clearPermissions) { claim.clearPermissions(); - GriefPrevention.sendMessage(player, TextMode.Success, "Cleared permissions in this claim. To set permission for ALL your claims, stand outside them."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.ClearPermissionsOneClaim); } //otherwise individual permission drop @@ -813,7 +830,11 @@ public class GriefPrevention extends JavaPlugin args[0] = "the public"; } - GriefPrevention.sendMessage(player, TextMode.Success, "Revoked " + args[0] + "'s access to this claim. To set permissions for a ALL your claims, stand outside them."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.UntrustIndividualSingleClaim, args[0]); + } + else + { + GriefPrevention.sendMessage(player, TextMode.Success, Messages.UntrustOwnerOnly, claim.getOwnerName()); } } @@ -866,14 +887,14 @@ public class GriefPrevention extends JavaPlugin //if purchase disabled, send error message if(GriefPrevention.instance.config_economy_claimBlocksPurchaseCost == 0) { - GriefPrevention.sendMessage(player, TextMode.Err, "Claim blocks may only be sold, not purchased."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.OnlySellBlocks); return true; } //if no parameter, just tell player cost per block and balance if(args.length != 1) { - GriefPrevention.sendMessage(player, TextMode.Info, "Each claim block costs " + GriefPrevention.instance.config_economy_claimBlocksPurchaseCost + ". Your balance is " + GriefPrevention.economy.getBalance(player.getName()) + "."); + GriefPrevention.sendMessage(player, TextMode.Info, Messages.BlockPurchaseCost, String.valueOf(GriefPrevention.instance.config_economy_claimBlocksPurchaseCost), String.valueOf(GriefPrevention.economy.getBalance(player.getName()))); return false; } @@ -886,7 +907,7 @@ public class GriefPrevention extends JavaPlugin //if the player is at his max, tell him so if(maxPurchasable <= 0) { - GriefPrevention.sendMessage(player, TextMode.Err, "You've reached your claim block limit. You can't purchase more."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.ClaimBlockLimit); return true; } @@ -912,7 +933,7 @@ public class GriefPrevention extends JavaPlugin double totalCost = blockCount * GriefPrevention.instance.config_economy_claimBlocksPurchaseCost; if(totalCost > balance) { - GriefPrevention.sendMessage(player, TextMode.Err, "You don't have enough money. You need " + totalCost + ", but you only have " + balance + "."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.InsufficientFunds, String.valueOf(totalCost), String.valueOf(balance)); } //otherwise carry out transaction @@ -926,7 +947,7 @@ public class GriefPrevention extends JavaPlugin this.dataStore.savePlayerData(player.getName(), playerData); //inform player - GriefPrevention.sendMessage(player, TextMode.Success, "Withdrew " + totalCost + " from your account. You now have " + playerData.getRemainingClaimBlocks() + " available claim blocks."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.PurchaseConfirmation, String.valueOf(totalCost), String.valueOf(playerData.getRemainingClaimBlocks())); } return true; @@ -942,7 +963,7 @@ public class GriefPrevention extends JavaPlugin //if disabled, error message if(GriefPrevention.instance.config_economy_claimBlocksSellValue == 0) { - GriefPrevention.sendMessage(player, TextMode.Err, "Claim blocks may only be purchased, not sold."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.OnlyPurchaseBlocks); return true; } @@ -953,7 +974,7 @@ public class GriefPrevention extends JavaPlugin //if no amount provided, just tell player value per block sold, and how many he can sell if(args.length != 1) { - GriefPrevention.sendMessage(player, TextMode.Info, "Each claim block is worth " + GriefPrevention.instance.config_economy_claimBlocksSellValue + ". You have " + availableBlocks + " available for sale."); + GriefPrevention.sendMessage(player, TextMode.Info, Messages.BlockSaleValue, String.valueOf(GriefPrevention.instance.config_economy_claimBlocksSellValue), String.valueOf(availableBlocks)); return false; } @@ -971,7 +992,7 @@ public class GriefPrevention extends JavaPlugin //if he doesn't have enough blocks, tell him so if(blockCount > availableBlocks) { - GriefPrevention.sendMessage(player, TextMode.Err, "You don't have that many claim blocks available for sale."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NotEnoughBlocksForSale); } //otherwise carry out the transaction @@ -986,7 +1007,7 @@ public class GriefPrevention extends JavaPlugin this.dataStore.savePlayerData(player.getName(), playerData); //inform player - GriefPrevention.sendMessage(player, TextMode.Success, "Deposited " + totalValue + " in your account. You now have " + playerData.getRemainingClaimBlocks() + " available claim blocks."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.BlockSaleConfirmation, String.valueOf(totalValue), String.valueOf(playerData.getRemainingClaimBlocks())); } return true; @@ -997,7 +1018,7 @@ public class GriefPrevention extends JavaPlugin { PlayerData playerData = this.dataStore.getPlayerData(player.getName()); playerData.shovelMode = ShovelMode.Admin; - GriefPrevention.sendMessage(player, TextMode.Success, "Administrative claims mode active. Any claims created will be free and editable by other administrators."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.AdminClaimsMode); return true; } @@ -1008,7 +1029,7 @@ public class GriefPrevention extends JavaPlugin PlayerData playerData = this.dataStore.getPlayerData(player.getName()); playerData.shovelMode = ShovelMode.Basic; playerData.claimSubdividing = null; - GriefPrevention.sendMessage(player, TextMode.Success, "Returned to basic claim creation mode."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.BasicClaimsMode); return true; } @@ -1019,7 +1040,8 @@ public class GriefPrevention extends JavaPlugin PlayerData playerData = this.dataStore.getPlayerData(player.getName()); playerData.shovelMode = ShovelMode.Subdivide; playerData.claimSubdividing = null; - GriefPrevention.sendMessage(player, TextMode.Instr, "Subdivision mode. Use your shovel to create subdivisions in your existing claims. Use /basicclaims to exit."); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SubdivisionMode); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SubdivisionDemo); return true; } @@ -1032,7 +1054,7 @@ public class GriefPrevention extends JavaPlugin if(claim == null) { - GriefPrevention.sendMessage(player, TextMode.Err, "There's no claim here."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.DeleteClaimMissing); } else @@ -1043,14 +1065,14 @@ public class GriefPrevention extends JavaPlugin PlayerData playerData = this.dataStore.getPlayerData(player.getName()); if(claim.children.size() > 0 && !playerData.warnedAboutMajorDeletion) { - GriefPrevention.sendMessage(player, TextMode.Warn, "This claim includes subdivisions. If you're sure you want to delete it, use /DeleteClaim again."); + GriefPrevention.sendMessage(player, TextMode.Warn, Messages.DeletionSubdivisionWarning); playerData.warnedAboutMajorDeletion = true; } else { claim.removeSurfaceFluids(null); this.dataStore.deleteClaim(claim); - GriefPrevention.sendMessage(player, TextMode.Success, "Claim deleted."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.DeleteSuccess); GriefPrevention.AddLogEntry(player.getName() + " deleted " + claim.getOwnerName() + "'s claim at " + GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner())); //revert any current visualization @@ -1061,7 +1083,7 @@ public class GriefPrevention extends JavaPlugin } else { - GriefPrevention.sendMessage(player, TextMode.Err, "You don't have permission to delete administrative claims."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.CantDeleteAdminClaim); } } @@ -1069,7 +1091,7 @@ public class GriefPrevention extends JavaPlugin } //deleteallclaims - else if(cmd.getName().equalsIgnoreCase("deleteallclaims") && player != null) + else if(cmd.getName().equalsIgnoreCase("deleteallclaims")) { //requires exactly one parameter, the other player's name if(args.length != 1) return false; @@ -1078,55 +1100,115 @@ public class GriefPrevention extends JavaPlugin OfflinePlayer otherPlayer = this.resolvePlayer(args[0]); if(otherPlayer == null) { - GriefPrevention.sendMessage(player, TextMode.Err, "Player not found."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound); return true; } //delete all that player's claims this.dataStore.deleteClaimsForPlayer(otherPlayer.getName(), true); - GriefPrevention.sendMessage(player, TextMode.Success, "Deleted all of " + otherPlayer.getName() + "'s claims."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.DeleteAllSuccess, otherPlayer.getName()); + if(player != null) + { + GriefPrevention.AddLogEntry(player.getName() + " deleted all claims belonging to " + otherPlayer.getName() + "."); - //revert any current visualization - Visualization.Revert(player); + //revert any current visualization + Visualization.Revert(player); + } + + return true; + } + + //deathblow [recipientPlayer] + else if(cmd.getName().equalsIgnoreCase("deathblow")) + { + //requires at least one parameter, the target player's name + if(args.length < 1) return false; + + //try to find that player + Player targetPlayer = this.getServer().getPlayer(args[0]); + if(targetPlayer == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound); + return true; + } + + //try to find the recipient player, if specified + Player recipientPlayer = null; + if(args.length > 1) + { + recipientPlayer = this.getServer().getPlayer(args[1]); + if(recipientPlayer == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound); + return true; + } + } + + //if giving inventory to another player, teleport the target player to that receiving player + if(recipientPlayer != null) + { + targetPlayer.teleport(recipientPlayer); + } + + //otherwise, plan to "pop" the player in place + else + { + //if in a normal world, shoot him up to the sky first, so his items will fall on the surface. + if(targetPlayer.getWorld().getEnvironment() == Environment.NORMAL) + { + Location location = targetPlayer.getLocation(); + location.setY(location.getWorld().getMaxHeight()); + targetPlayer.teleport(location); + } + } + + //kill target player + targetPlayer.setHealth(0); + + //log entry + if(player != null) + { + GriefPrevention.AddLogEntry(player.getName() + " used /DeathBlow to kill " + targetPlayer.getName() + "."); + } + else + { + GriefPrevention.AddLogEntry("Killed " + targetPlayer.getName() + "."); + } return true; } //deletealladminclaims - else if(cmd.getName().equalsIgnoreCase("deletealladminclaims") && player != null) + else if(cmd.getName().equalsIgnoreCase("deletealladminclaims")) { if(!player.hasPermission("griefprevention.deleteclaims")) { - GriefPrevention.sendMessage(player, TextMode.Err, "You don't have permission to delete claims."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoDeletePermission); return true; } //delete all admin claims this.dataStore.deleteClaimsForPlayer("", true); //empty string for owner name indicates an administrative claim - GriefPrevention.sendMessage(player, TextMode.Success, "Deleted all administrative claims."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.AllAdminDeleted); + if(player != null) + { + GriefPrevention.AddLogEntry(player.getName() + " deleted all administrative claims."); - //revert any current visualization - Visualization.Revert(player); + //revert any current visualization + Visualization.Revert(player); + } return true; } - //adjustbonusclaimblocks - else if(cmd.getName().equalsIgnoreCase("adjustbonusclaimblocks") && player != null) + //adjustbonusclaimblocks or [] amount + else if(cmd.getName().equalsIgnoreCase("adjustbonusclaimblocks")) { - //requires exactly two parameters, the other player's name and the adjustment + //requires exactly two parameters, the other player or group's name and the adjustment if(args.length != 2) return false; - //find the specified player - OfflinePlayer targetPlayer = this.resolvePlayer(args[0]); - if(targetPlayer == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, "Player \"" + args[0] + "\" not found."); - return true; - } - //parse the adjustment amount int adjustment; try @@ -1138,13 +1220,33 @@ public class GriefPrevention extends JavaPlugin return false; //causes usage to be displayed } + //if granting blocks to all players with a specific permission + if(args[0].startsWith("[") && args[0].endsWith("]")) + { + String permissionIdentifier = args[0].substring(1, args[0].length() - 1); + int newTotal = this.dataStore.adjustGroupBonusBlocks(permissionIdentifier, adjustment); + + GriefPrevention.sendMessage(player, TextMode.Success, Messages.AdjustGroupBlocksSuccess, permissionIdentifier, String.valueOf(adjustment), String.valueOf(newTotal)); + if(player != null) GriefPrevention.AddLogEntry(player.getName() + " adjusted " + permissionIdentifier + "'s bonus claim blocks by " + adjustment + "."); + + return true; + } + + //otherwise, find the specified player + OfflinePlayer targetPlayer = this.resolvePlayer(args[0]); + if(targetPlayer == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound); + return true; + } + //give blocks to player PlayerData playerData = this.dataStore.getPlayerData(targetPlayer.getName()); playerData.bonusClaimBlocks += adjustment; this.dataStore.savePlayerData(targetPlayer.getName(), playerData); - GriefPrevention.sendMessage(player, TextMode.Success, "Adjusted " + targetPlayer.getName() + "'s bonus claim blocks by " + adjustment + ". New total bonus blocks: " + playerData.bonusClaimBlocks + "."); - GriefPrevention.AddLogEntry(player.getName() + " adjusted " + targetPlayer.getName() + "'s bonus claim blocks by " + adjustment + "."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.AdjustBlocksSuccess, targetPlayer.getName(), String.valueOf(adjustment), String.valueOf(playerData.bonusClaimBlocks)); + if(player != null) GriefPrevention.AddLogEntry(player.getName() + " adjusted " + targetPlayer.getName() + "'s bonus claim blocks by " + adjustment + "."); return true; } @@ -1166,7 +1268,7 @@ public class GriefPrevention extends JavaPlugin //if the player isn't in a claim or has permission to build, tell him to man up if(claim == null || claim.allowBuild(player) == null) { - GriefPrevention.sendMessage(player, TextMode.Err, "You can build here. Save yourself."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NotTrappedHere); return true; } @@ -1176,12 +1278,12 @@ public class GriefPrevention extends JavaPlugin long now = Calendar.getInstance().getTimeInMillis(); if(now < nextTrappedUsage) { - GriefPrevention.sendMessage(player, TextMode.Err, "You used /trapped within the last " + this.config_claims_trappedCooldownHours + " hours. You have to wait about " + ((nextTrappedUsage - now) / (1000 * 60) + 1) + " more minutes before using it again."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.TrappedOnCooldown, String.valueOf(this.config_claims_trappedCooldownHours), String.valueOf((nextTrappedUsage - now) / (1000 * 60) + 1)); return true; } //send instructions - GriefPrevention.sendMessage(player, TextMode.Instr, "If you stay put for 10 seconds, you'll be teleported out. Please wait."); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.RescuePending); //create a task to rescue this player in a little while PlayerRescueTask task = new PlayerRescueTask(player, player.getLocation()); @@ -1196,7 +1298,7 @@ public class GriefPrevention extends JavaPlugin //error message for when siege mode is disabled if(!this.siegeEnabledForWorld(player.getWorld())) { - GriefPrevention.sendMessage(player, TextMode.Err, "Siege is disabled here."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NonSiegeWorld); return true; } @@ -1211,7 +1313,7 @@ public class GriefPrevention extends JavaPlugin PlayerData attackerData = this.dataStore.getPlayerData(attacker.getName()); if(attackerData.siegeData != null) { - GriefPrevention.sendMessage(player, TextMode.Err, "You're already involved in a siege."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.AlreadySieging); return true; } @@ -1222,7 +1324,7 @@ public class GriefPrevention extends JavaPlugin defender = this.getServer().getPlayer(args[0]); if(defender == null) { - GriefPrevention.sendMessage(player, TextMode.Err, "Player not found."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound); return true; } } @@ -1246,14 +1348,14 @@ public class GriefPrevention extends JavaPlugin PlayerData defenderData = this.dataStore.getPlayerData(defender.getName()); if(defenderData.siegeData != null) { - GriefPrevention.sendMessage(player, TextMode.Err, defender.getName() + " is already under siege. Join the party!"); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.AlreadyUnderSiegePlayer); return true; } //victim must not be pvp immune if(defenderData.pvpImmune) { - GriefPrevention.sendMessage(player, TextMode.Err, defender.getName() + " is defenseless. Go pick on somebody else."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoSiegeDefenseless); return true; } @@ -1262,35 +1364,35 @@ public class GriefPrevention extends JavaPlugin //defender must have some level of permission there to be protected if(defenderClaim == null || defenderClaim.allowAccess(defender) != null) { - GriefPrevention.sendMessage(player, TextMode.Err, defender.getName() + " isn't protected there."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NotSiegableThere); return true; } //attacker must be close to the claim he wants to siege if(!defenderClaim.isNear(attacker.getLocation(), 25)) { - GriefPrevention.sendMessage(player, TextMode.Err, "You're too far away from " + defender.getName() + " to siege."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeTooFarAway); return true; } //claim can't be under siege already if(defenderClaim.siegeData != null) { - GriefPrevention.sendMessage(player, TextMode.Err, "That area is already under siege. Join the party!"); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.AlreadyUnderSiegeArea); return true; } //can't siege admin claims if(defenderClaim.isAdminClaim()) { - GriefPrevention.sendMessage(player, TextMode.Err, "Siege is disabled in this area."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoSiegeAdminClaim); return true; } //can't be on cooldown if(dataStore.onCooldown(attacker, defender, defenderClaim)) { - GriefPrevention.sendMessage(player, TextMode.Err, "You're still on siege cooldown for this defender or claim. Find another victim."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeOnCooldown); return true; } @@ -1298,8 +1400,8 @@ public class GriefPrevention extends JavaPlugin dataStore.startSiege(attacker, defender, defenderClaim); //confirmation message for attacker, warning message for defender - GriefPrevention.sendMessage(defender, TextMode.Warn, "You're under siege! If you log out now, you will die. You must defeat " + attacker.getName() + ", wait for him to give up, or escape."); - GriefPrevention.sendMessage(player, TextMode.Success, "The siege has begun! If you log out now, you will die. You must defeat " + defender.getName() + ", chase him away, or admit defeat and walk away."); + GriefPrevention.sendMessage(defender, TextMode.Warn, Messages.SiegeAlert, attacker.getName()); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.SiegeConfirmed, defender.getName()); } return false; @@ -1312,31 +1414,41 @@ public class GriefPrevention extends JavaPlugin private boolean abandonClaimHandler(Player player, boolean deleteTopLevelClaim) { + PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + //which claim is being abandoned? Claim claim = this.dataStore.getClaimAt(player.getLocation(), true /*ignore height*/, null); if(claim == null) { - GriefPrevention.sendMessage(player, TextMode.Instr, "Stand in the claim you want to delete, or consider /AbandonAllClaims."); - } - - else if(this.creativeRulesApply(player.getLocation())) - { - GriefPrevention.sendMessage(player, TextMode.Err, "Creative-mode claims can't be abandoned."); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.AbandonClaimMissing); } //verify ownership else if(claim.allowEdit(player) != null) { - GriefPrevention.sendMessage(player, TextMode.Err, "This isn't your claim."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NotYourClaim); + } + + //don't allow abandon of creative mode claims + else if(this.creativeRulesApply(player.getLocation())) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoCreativeUnClaim); } //warn if has children and we're not explicitly deleting a top level claim else if(claim.children.size() > 0 && !deleteTopLevelClaim) { - GriefPrevention.sendMessage(player, TextMode.Instr, "To delete a subdivision, stand inside it. Otherwise, use /AbandonTopLevelClaim to delete this claim and all subdivisions."); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.DeleteTopLevelClaim); return true; } + //if the claim has lots of surface water or some surface lava, warn the player it will be cleaned up + else if(!playerData.warnedAboutMajorDeletion && claim.hasSurfaceFluids()) + { + GriefPrevention.sendMessage(player, TextMode.Warn, Messages.ConfirmFluidRemoval); + playerData.warnedAboutMajorDeletion = true; + } + else { //delete it @@ -1344,12 +1456,13 @@ public class GriefPrevention extends JavaPlugin this.dataStore.deleteClaim(claim); //tell the player how many claim blocks he has left - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); int remainingBlocks = playerData.getRemainingClaimBlocks(); - GriefPrevention.sendMessage(player, TextMode.Success, "Claim abandoned. You now have " + String.valueOf(remainingBlocks) + " available claim blocks."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.AbandonSuccess, String.valueOf(remainingBlocks)); //revert any current visualization Visualization.Revert(player); + + playerData.warnedAboutMajorDeletion = false; } return true; @@ -1362,21 +1475,36 @@ public class GriefPrevention extends JavaPlugin //determine which claim the player is standing in Claim claim = this.dataStore.getClaimAt(player.getLocation(), true /*ignore height*/, null); - //validate player argument - OfflinePlayer otherPlayer = this.resolvePlayer(recipientName); - if(otherPlayer == null && !recipientName.equals("public")) + //validate player or group argument + String permission = null; + OfflinePlayer otherPlayer = null; + if(recipientName.startsWith("[") && recipientName.endsWith("]")) { - GriefPrevention.sendMessage(player, TextMode.Err, "Player not found."); - return; + permission = recipientName.substring(1, recipientName.length() - 1); + if(permission == null || permission.isEmpty()) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.InvalidPermissionID); + return; + } } - if(otherPlayer != null) - { - recipientName = otherPlayer.getName(); - } else - { - recipientName = "public"; + { + otherPlayer = this.resolvePlayer(recipientName); + if(otherPlayer == null && !recipientName.equals("public")) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound); + return; + } + + if(otherPlayer != null) + { + recipientName = otherPlayer.getName(); + } + else + { + recipientName = "public"; + } } //determine which claims should be modified @@ -1394,7 +1522,7 @@ public class GriefPrevention extends JavaPlugin //check permission here if(claim.allowGrantPermission(player) != null) { - GriefPrevention.sendMessage(player, TextMode.Err, "You don't have " + claim.getOwnerName() + "'s permission to grant permissions here."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoPermissionTrust, claim.getOwnerName()); return; } @@ -1430,7 +1558,7 @@ public class GriefPrevention extends JavaPlugin //error message for trying to grant a permission the player doesn't have if(errorMessage != null) { - GriefPrevention.sendMessage(player, TextMode.Err, errorMessage + " You can't grant a permission you don't have yourself."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.CantGrantThatPermission); return; } @@ -1440,7 +1568,7 @@ public class GriefPrevention extends JavaPlugin //if we didn't determine which claims to modify, tell the player to be specific if(targetClaims.size() == 0) { - GriefPrevention.sendMessage(player, TextMode.Err, "Stand inside the claim where you want to grant permission."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.GrantPermissionNoClaim); return; } @@ -1463,36 +1591,36 @@ public class GriefPrevention extends JavaPlugin } //notify player - if(recipientName.equals("public")) recipientName = "the public"; - StringBuilder resultString = new StringBuilder(); - resultString.append("Granted " + recipientName + " "); + if(recipientName.equals("public")) recipientName = this.dataStore.getMessage(Messages.CollectivePublic); + String permissionDescription; if(permissionLevel == null) { - resultString.append("manager status"); + permissionDescription = this.dataStore.getMessage(Messages.PermissionsPermission); } else if(permissionLevel == ClaimPermission.Build) { - resultString.append("permission to build in"); + permissionDescription = this.dataStore.getMessage(Messages.BuildPermission); } else if(permissionLevel == ClaimPermission.Access) { - resultString.append("permission to use buttons and levers in"); + permissionDescription = this.dataStore.getMessage(Messages.AccessPermission); } - else if(permissionLevel == ClaimPermission.Inventory) + else //ClaimPermission.Inventory { - resultString.append("permission to access containers in"); + permissionDescription = this.dataStore.getMessage(Messages.ContainersPermission); } + String location; if(claim == null) { - resultString.append(" ALL your claims. To modify only one claim, stand inside it."); + location = this.dataStore.getMessage(Messages.LocationAllClaims); } else { - resultString.append(" this claim. To modify ALL your claims, stand outside them."); + location = this.dataStore.getMessage(Messages.LocationCurrentClaim); } - GriefPrevention.sendMessage(player, TextMode.Success, resultString.toString()); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.GrantPermissionConfirmation, recipientName, permissionDescription, location); } //helper method to resolve a player by name @@ -1552,7 +1680,7 @@ public class GriefPrevention extends JavaPlugin playerData.pvpImmune = true; //inform the player - GriefPrevention.sendMessage(player, TextMode.Success, "You're protected from attack by other players as long as your inventory is empty."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.PvPImmunityStart); } //checks whether players can create claims in a world @@ -1797,10 +1925,24 @@ public class GriefPrevention extends JavaPlugin while(!chunk.isLoaded() || !chunk.load(true)); } + //sends a color-coded message to a player + static void sendMessage(Player player, ChatColor color, Messages messageID, String... args) + { + String message = GriefPrevention.instance.dataStore.getMessage(messageID, args); + sendMessage(player, color, message); + } + //sends a color-coded message to a player static void sendMessage(Player player, ChatColor color, String message) { - player.sendMessage(color + message); + if(player == null) + { + GriefPrevention.AddLogEntry(color + message); + } + else + { + player.sendMessage(color + message); + } } //determines whether creative anti-grief rules apply at a location @@ -1825,19 +1967,20 @@ public class GriefPrevention extends JavaPlugin return "You can't build here. Use the golden shovel to claim some land first."; } - - //but it's fine in survival mode else { - //cache the claim for later reference - playerData.lastClaim = claim; - + //but it's fine in survival mode return null; - } + } } //if not in the wilderness, then apply claim rules (permissions, etc) - return claim.allowBuild(player); + else + { + //cache the claim for later reference + playerData.lastClaim = claim; + return claim.allowBuild(player); + } } public String allowBreak(Player player, Location location) @@ -1854,20 +1997,22 @@ public class GriefPrevention extends JavaPlugin //exception: administrators in ignore claims mode if(playerData.ignoreClaims) return null; - return "You can't build here. Use the golden shovel to claim some land first."; + return "You can only build where you have claimed land. To claim, watch this: http://tinyurl.com/c7bajb8"; } //but it's fine in survival mode else { - //cache the claim for later reference - playerData.lastClaim = claim; - return null; } } + else + { + //cache the claim for later reference + playerData.lastClaim = claim; - //if not in the wilderness, then apply claim rules (permissions, etc) - return claim.allowBreak(player, location.getBlock().getType()); + //if not in the wilderness, then apply claim rules (permissions, etc) + return claim.allowBreak(player, location.getBlock().getType()); + } } } \ No newline at end of file diff --git a/src/me/ryanhamshire/GriefPrevention/Messages.java b/src/me/ryanhamshire/GriefPrevention/Messages.java index 0b7964d..f65a29c 100644 --- a/src/me/ryanhamshire/GriefPrevention/Messages.java +++ b/src/me/ryanhamshire/GriefPrevention/Messages.java @@ -2,5 +2,5 @@ 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 + 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 } diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerData.java b/src/me/ryanhamshire/GriefPrevention/PlayerData.java index ac3c624..f1237f5 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerData.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerData.java @@ -27,6 +27,9 @@ import org.bukkit.Location; //holds all of GriefPrevention's player-tied data public class PlayerData { + //the player's name + public String playerName; + //the player's claims public Vector claims = new Vector(); @@ -71,9 +74,6 @@ public class PlayerData public int spamCount = 0; //number of consecutive "spams" public boolean spamWarned = false; //whether the player recently received a warning - //last logout timestamp, default to long enough to trigger a join message, see player join event - public long lastLogout = Calendar.getInstance().getTimeInMillis() - GriefPrevention.NOTIFICATION_SECONDS * 2000; - //visualization public Visualization currentVisualization = null; @@ -138,6 +138,9 @@ public class PlayerData remainingBlocks -= claim.getArea(); } + //add any blocks this player might have based on group membership (permissions) + remainingBlocks += GriefPrevention.instance.dataStore.getGroupBonusBlocks(this.playerName); + return remainingBlocks; } } \ No newline at end of file diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java index 78212e0..37723aa 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java @@ -20,6 +20,7 @@ package me.ryanhamshire.GriefPrevention; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -59,6 +60,9 @@ class PlayerEventHandler implements Listener //number of milliseconds in a day private final long MILLISECONDS_IN_DAY = 1000 * 60 * 60 * 24; + //timestamps of login and logout notifications in the last minute + private ArrayList recentLoginLogoutNotifications = new ArrayList(); + //typical constructor, yawn PlayerEventHandler(DataStore dataStore, GriefPrevention plugin) { @@ -75,9 +79,9 @@ class PlayerEventHandler implements Listener //FEATURE: automatically educate players about the /trapped command //check for "trapped" or "stuck" to educate players about the /trapped command - if(message.contains("trapped") || message.contains("stuck")) + if(message.contains("trapped") || message.contains("stuck") || message.contains(this.dataStore.getMessage(Messages.TrappedChatKeyword))) { - GriefPrevention.sendMessage(player, TextMode.Info, "Are you trapped in someone's claim? Consider the /trapped command."); + GriefPrevention.sendMessage(player, TextMode.Info, Messages.TrappedInstructions); } //FEATURE: monitor for chat and command spam @@ -101,8 +105,27 @@ class PlayerEventHandler implements Listener boolean spam = false; boolean muted = false; + //check message content and timing + long millisecondsSinceLastMessage = (new Date()).getTime() - playerData.lastMessageTimestamp.getTime(); + + //if the message came too close to the last one + if(millisecondsSinceLastMessage < 3000) + { + //increment the spam counter + playerData.spamCount++; + spam = true; + } + + //if it's very similar to the last message + if(!muted && this.stringsAreSimilar(message, playerData.lastMessage)) + { + playerData.spamCount++; + spam = true; + muted = true; + } + //filter IP addresses - if(!(event instanceof PlayerCommandPreprocessEvent)) + if(!muted && !(event instanceof PlayerCommandPreprocessEvent)) { Pattern ipAddressPattern = Pattern.compile("\\d+\\.\\d+\\.\\d+\\.\\d+"); Matcher matcher = ipAddressPattern.matcher(event.getMessage()); @@ -126,27 +149,8 @@ class PlayerEventHandler implements Listener } } - //check message content and timing - long millisecondsSinceLastMessage = (new Date()).getTime() - playerData.lastMessageTimestamp.getTime(); - - //if the message came too close to the last one - if(millisecondsSinceLastMessage < 3000) - { - //increment the spam counter - playerData.spamCount++; - spam = true; - } - - //if it's very similar to the last message - if(this.stringsAreSimilar(message, playerData.lastMessage)) - { - playerData.spamCount++; - spam = true; - muted = true; - } - //if the message was mostly non-alpha-numerics or doesn't include much whitespace, consider it a spam (probably ansi art or random text gibberish) - if(message.length() > 5) + if(!muted && message.length() > 5) { int symbolsCount = 0; int whitespaceCount = 0; @@ -167,6 +171,7 @@ class PlayerEventHandler implements Listener if(symbolsCount > message.length() / 2 || (message.length() > 15 && whitespaceCount < message.length() / 10)) { spam = true; + if(playerData.spamCount > 0) muted = true; playerData.spamCount++; } } @@ -347,7 +352,7 @@ class PlayerEventHandler implements Listener 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) + if(GriefPrevention.instance.config_smartBan && !player.hasPlayedBefore()) { //if logging-in account is banned, remember IP address for later long now = Calendar.getInstance().getTimeInMillis(); @@ -399,6 +404,16 @@ class PlayerEventHandler implements Listener 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); + } + } } } } @@ -430,42 +445,34 @@ class PlayerEventHandler implements Listener //check inventory, may need pvp protection GriefPrevention.instance.checkPvpProtectionNeeded(event.getPlayer()); - //how long since the last logout? - long elapsed = Calendar.getInstance().getTimeInMillis() - playerData.lastLogout; - - //remember message, then silence it. may broadcast it later - String message = event.getJoinMessage(); - event.setJoinMessage(null); - - if(message != null && elapsed >= GriefPrevention.NOTIFICATION_SECONDS * 1000) + //silence notifications when they're coming too fast + if(event.getJoinMessage() != null && this.shouldSilenceNotification()) { - //start a timer for a delayed join notification message (will only show if player is still online in 30 seconds) - JoinLeaveAnnouncementTask task = new JoinLeaveAnnouncementTask(event.getPlayer(), message, true); - GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 20L * GriefPrevention.NOTIFICATION_SECONDS); + event.setJoinMessage(null); } } - //when a player quits... - @EventHandler(priority = EventPriority.HIGHEST) - void onPlayerKicked(PlayerKickEvent event) - { - Player player = event.getPlayer(); - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); - if(player.isBanned()) - { - long now = Calendar.getInstance().getTimeInMillis(); - this.tempBannedIps.add(new IpBanInfo(playerData.ipAddress, now + this.MILLISECONDS_IN_DAY, player.getName())); - } - } - //when a player quits... @EventHandler(priority = EventPriority.HIGHEST) void onPlayerQuit(PlayerQuitEvent event) { - this.onPlayerDisconnect(event.getPlayer(), event.getQuitMessage()); + Player player = event.getPlayer(); + PlayerData playerData = this.dataStore.getPlayerData(player.getName()); - //silence the leave message (may be broadcast later, if the player stays offline) - event.setQuitMessage(null); + //if banned, add IP to the temporary IP ban list + if(player.isBanned()) + { + long now = Calendar.getInstance().getTimeInMillis(); + this.tempBannedIps.add(new IpBanInfo(playerData.ipAddress, now + this.MILLISECONDS_IN_DAY, player.getName())); + } + + //silence notifications when they're coming too fast + if(event.getQuitMessage() != null && this.shouldSilenceNotification()) + { + event.setQuitMessage(null); + } + + this.onPlayerDisconnect(event.getPlayer(), event.getQuitMessage()); } //helper for above @@ -487,21 +494,33 @@ class PlayerEventHandler implements Listener { if(player.getHealth() > 0) player.setHealth(0); //might already be zero from above, this avoids a double death message } + } + + //determines whether or not a login or logout notification should be silenced, depending on how many there have been in the last minute + private boolean shouldSilenceNotification() + { + final long ONE_MINUTE = 60000; + final int MAX_ALLOWED = 20; + Long now = Calendar.getInstance().getTimeInMillis(); - //how long was the player online? - long now = Calendar.getInstance().getTimeInMillis(); - long elapsed = now - playerData.lastLogin.getTime(); - - //remember logout time - playerData.lastLogout = Calendar.getInstance().getTimeInMillis(); - - //if notification message isn't null and the player has been online for at least 30 seconds... - if(notificationMessage != null && elapsed >= 1000 * GriefPrevention.NOTIFICATION_SECONDS) + //eliminate any expired entries (longer than a minute ago) + for(int i = 0; i < this.recentLoginLogoutNotifications.size(); i++) { - //start a timer for a delayed leave notification message (will only show if player is still offline in 30 seconds) - JoinLeaveAnnouncementTask task = new JoinLeaveAnnouncementTask(player, notificationMessage, false); - GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 20L * GriefPrevention.NOTIFICATION_SECONDS); + Long notificationTimestamp = this.recentLoginLogoutNotifications.get(i); + if(now - notificationTimestamp > ONE_MINUTE) + { + this.recentLoginLogoutNotifications.remove(i--); + } + else + { + break; + } } + + //add the new entry + this.recentLoginLogoutNotifications.add(now); + + return this.recentLoginLogoutNotifications.size() > MAX_ALLOWED; } //when a player drops an item @@ -525,14 +544,14 @@ class PlayerEventHandler implements Listener //if in combat, don't let him drop it if(!GriefPrevention.instance.config_pvp_allowCombatItemDrop && playerData.inPvpCombat()) { - GriefPrevention.sendMessage(player, TextMode.Err, "You can't drop items while in PvP combat."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PvPNoDrop); event.setCancelled(true); } //if he's under siege, don't let him drop it else if(playerData.siegeData != null) { - GriefPrevention.sendMessage(player, TextMode.Err, "You can't drop items while involved in a siege."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeNoDrop); event.setCancelled(true); } } @@ -552,7 +571,7 @@ class PlayerEventHandler implements Listener Claim sourceClaim = this.dataStore.getClaimAt(source, false, null); if(sourceClaim != null && sourceClaim.siegeData != null) { - GriefPrevention.sendMessage(player, TextMode.Err, "You can't teleport out of a besieged area."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeNoTeleport); event.setCancelled(true); return; } @@ -561,7 +580,7 @@ class PlayerEventHandler implements Listener Claim destinationClaim = this.dataStore.getClaimAt(destination, false, null); if(destinationClaim != null && destinationClaim.siegeData != null) { - GriefPrevention.sendMessage(player, TextMode.Err, "You can't teleport into a besieged area."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.BesiegedNoTeleport); event.setCancelled(true); return; } @@ -580,14 +599,14 @@ class PlayerEventHandler implements Listener { if(playerData.siegeData != null) { - GriefPrevention.sendMessage(player, TextMode.Err, "You can't access containers while under siege."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeNoContainers); event.setCancelled(true); return; } if(playerData.inPvpCombat()) { - GriefPrevention.sendMessage(player, TextMode.Err, "You can't access containers during PvP combat."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PvPNoContainers); event.setCancelled(true); return; } @@ -627,7 +646,7 @@ class PlayerEventHandler implements Listener { if(claim.allowContainers(player) != null) { - GriefPrevention.sendMessage(player, TextMode.Err, "That animal belongs to " + claim.getOwnerName() + "."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoDamageClaimedEntity); event.setCancelled(true); } } @@ -661,7 +680,7 @@ class PlayerEventHandler implements Listener //otherwise take away his immunity. he may be armed now. at least, he's worth killing for some loot playerData.pvpImmune = false; - GriefPrevention.sendMessage(player, TextMode.Warn, "Now you can fight with other players."); + GriefPrevention.sendMessage(player, TextMode.Warn, Messages.PvPImmunityEnd); } } } @@ -698,7 +717,7 @@ class PlayerEventHandler implements Listener if(claim.allowAccess(player) != null) { bedEvent.setCancelled(true); - GriefPrevention.sendMessage(player, TextMode.Err, claim.getOwnerName() + " hasn't given you permission to sleep here."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoBedPermission, claim.getOwnerName()); } } } @@ -727,15 +746,18 @@ class PlayerEventHandler implements Listener minLavaDistance = 3; } - //otherwise no dumping anything unless underground - else + //otherwise no wilderness dumping (unless underground) in worlds where claims are enabled + else if(GriefPrevention.instance.config_claims_enabledWorlds.contains(block.getWorld())) { if(block.getY() >= block.getWorld().getSeaLevel() - 5 && !player.hasPermission("griefprevention.lava")) { - GriefPrevention.sendMessage(player, TextMode.Err, "You may only dump buckets inside your claim(s) or underground."); - bucketEvent.setCancelled(true); - return; - } + if(bucketEvent.getBucket() == Material.LAVA_BUCKET || GriefPrevention.instance.config_blockWildernessWaterBuckets) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoWildernessBuckets); + bucketEvent.setCancelled(true); + return; + } + } } //lava buckets can't be dumped near other players unless pvp is on @@ -750,7 +772,7 @@ class PlayerEventHandler implements Listener Location location = otherPlayer.getLocation(); if(!otherPlayer.equals(player) && block.getY() >= location.getBlockY() - 1 && location.distanceSquared(block.getLocation()) < minLavaDistance * minLavaDistance) { - GriefPrevention.sendMessage(player, TextMode.Err, "You can't place lava this close to " + otherPlayer.getName() + "."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoLavaNearOtherPlayer, otherPlayer.getName()); bucketEvent.setCancelled(true); return; } @@ -791,6 +813,9 @@ class PlayerEventHandler implements Listener if(clickedBlock == null) { //try to find a far away non-air block along line of sight + HashSet transparentMaterials = new HashSet(); + transparentMaterials.add(Byte.valueOf((byte)Material.AIR.getId())); + transparentMaterials.add(Byte.valueOf((byte)Material.SNOW.getId())); clickedBlock = player.getTargetBlock(null, 250); } } @@ -807,23 +832,8 @@ class PlayerEventHandler implements Listener Material clickedBlockType = clickedBlock.getType(); - //apply rules for buttons and switches - if(GriefPrevention.instance.config_claims_preventButtonsSwitches && (clickedBlockType == null || clickedBlockType == Material.STONE_BUTTON || clickedBlockType == Material.LEVER)) - { - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, null); - if(claim != null) - { - String noAccessReason = claim.allowAccess(player); - if(noAccessReason != null) - { - event.setCancelled(true); - GriefPrevention.sendMessage(player, TextMode.Err, noAccessReason); - } - } - } - - //otherwise apply rules for containers and crafting blocks - else if( GriefPrevention.instance.config_claims_preventTheft && ( + //apply rules for containers and crafting blocks + if( GriefPrevention.instance.config_claims_preventTheft && ( event.getAction() == Action.RIGHT_CLICK_BLOCK && ( clickedBlock.getState() instanceof InventoryHolder || clickedBlockType == Material.BREWING_STAND || @@ -835,7 +845,7 @@ class PlayerEventHandler implements Listener PlayerData playerData = this.dataStore.getPlayerData(player.getName()); if(playerData.siegeData != null) { - GriefPrevention.sendMessage(player, TextMode.Err, "You can't access containers while involved in a siege."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeNoContainers); event.setCancelled(true); return; } @@ -843,7 +853,7 @@ class PlayerEventHandler implements Listener //block container use during pvp combat, same reason if(playerData.inPvpCombat()) { - GriefPrevention.sendMessage(player, TextMode.Err, "You can't access containers during PvP combat."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PvPNoContainers); event.setCancelled(true); return; } @@ -857,6 +867,7 @@ class PlayerEventHandler implements Listener { event.setCancelled(true); GriefPrevention.sendMessage(player, TextMode.Err, noContainersReason); + return; } } @@ -865,15 +876,48 @@ class PlayerEventHandler implements Listener if(playerData.pvpImmune) { playerData.pvpImmune = false; - GriefPrevention.sendMessage(player, TextMode.Warn, "Now you can fight with other players."); + GriefPrevention.sendMessage(player, TextMode.Warn, Messages.PvPImmunityEnd); } } + //otherwise apply rules for buttons and switches + else if(GriefPrevention.instance.config_claims_preventButtonsSwitches && (clickedBlockType == null || clickedBlockType == Material.STONE_BUTTON || clickedBlockType == Material.LEVER)) + { + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, null); + if(claim != null) + { + String noAccessReason = claim.allowAccess(player); + if(noAccessReason != null) + { + event.setCancelled(true); + GriefPrevention.sendMessage(player, TextMode.Err, noAccessReason); + return; + } + } + } + //apply rule for players trampling tilled soil back to dirt (never allow it) //NOTE: that this event applies only to players. monsters and animals can still trample. else if(event.getAction() == Action.PHYSICAL && clickedBlockType == Material.SOIL) { event.setCancelled(true); + return; + } + + //apply rule for note blocks + else if(clickedBlockType == Material.NOTE_BLOCK) + { + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, null); + if(claim != null) + { + String noBuildReason = claim.allowBuild(player); + if(noBuildReason != null) + { + event.setCancelled(true); + GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason); + return; + } + } } //otherwise handle right click (shovel, string, bonemeal) @@ -886,8 +930,8 @@ class PlayerEventHandler implements Listener //what's the player holding? Material materialInHand = player.getItemInHand().getType(); - //if it's bonemeal, check for build permission (ink sac == bone meal, must be a Bukkit bug?) - if(materialInHand == Material.INK_SACK) + //if it's bonemeal or a boat, check for build permission (ink sac == bone meal, must be a Bukkit bug?) + if(materialInHand == Material.INK_SACK || materialInHand == Material.BOAT) { String noBuildReason = GriefPrevention.instance.allowBuild(player, clickedBlock.getLocation()); if(noBuildReason != null) @@ -899,8 +943,8 @@ class PlayerEventHandler implements Listener return; } - //if it's a spawn egg or minecart and this is a creative world, apply special rules - else if((materialInHand == Material.MONSTER_EGG || materialInHand == Material.MINECART || materialInHand == Material.POWERED_MINECART || materialInHand == Material.STORAGE_MINECART) && GriefPrevention.instance.creativeRulesApply(clickedBlock.getLocation())) + //if it's a spawn egg, minecart, or boat, and this is a creative world, apply special rules + else if((materialInHand == Material.MONSTER_EGG || materialInHand == Material.MINECART || materialInHand == Material.POWERED_MINECART || materialInHand == Material.STORAGE_MINECART || materialInHand == Material.BOAT) && GriefPrevention.instance.creativeRulesApply(clickedBlock.getLocation())) { //player needs build permission at this location String noBuildReason = GriefPrevention.instance.allowBuild(player, clickedBlock.getLocation()); @@ -933,7 +977,7 @@ class PlayerEventHandler implements Listener //air indicates too far away if(clickedBlockType == Material.AIR) { - GriefPrevention.sendMessage(player, TextMode.Err, "That's too far away."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.TooFarAway); return; } @@ -942,14 +986,14 @@ class PlayerEventHandler implements Listener //no claim case if(claim == null) { - GriefPrevention.sendMessage(player, TextMode.Info, "No one has claimed this block."); + GriefPrevention.sendMessage(player, TextMode.Info, Messages.BlockNotClaimed); Visualization.Revert(player); } //claim case else { - GriefPrevention.sendMessage(player, TextMode.Info, "This block has been claimed by " + claim.getOwnerName() + "."); + GriefPrevention.sendMessage(player, TextMode.Info, Messages.BlockClaimed, claim.getOwnerName()); //visualize boundary Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.Claim); @@ -967,7 +1011,7 @@ class PlayerEventHandler implements Listener //disable golden shovel while under siege if(playerData.siegeData != null) { - GriefPrevention.sendMessage(player, TextMode.Err, "You can't use your shovel tool while involved in a siege."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeNoShovel); event.setCancelled(true); return; } @@ -975,7 +1019,7 @@ class PlayerEventHandler implements Listener //can't use the shovel from too far away if(clickedBlockType == Material.AIR) { - GriefPrevention.sendMessage(player, TextMode.Err, "That's too far away!"); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.TooFarAway); return; } @@ -988,7 +1032,7 @@ class PlayerEventHandler implements Listener Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); if(claim != null) { - GriefPrevention.sendMessage(player, TextMode.Err, claim.getOwnerName() + " claimed that block."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.BlockClaimed, claim.getOwnerName()); Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.ErrorClaim); Visualization.Apply(player, visualization); @@ -1005,7 +1049,7 @@ class PlayerEventHandler implements Listener if(entities[i] instanceof Player) { Player otherPlayer = (Player)entities[i]; - GriefPrevention.sendMessage(player, TextMode.Err, "Unable to restore. " + otherPlayer.getName() + " is in that chunk."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.RestoreNaturePlayerInChunk, otherPlayer.getName()); return; } } @@ -1072,9 +1116,17 @@ class PlayerEventHandler implements Listener allowedFillBlocks.add(Material.SANDSTONE); allowedFillBlocks.add(Material.DIRT); allowedFillBlocks.add(Material.GRASS); + allowedFillBlocks.add(Material.ICE); } Block centerBlock = clickedBlock; + + //sink through a snow layer + if(centerBlock.getType() == Material.SNOW) + { + centerBlock = centerBlock.getRelative(BlockFace.DOWN); + } + int maxHeight = centerBlock.getY(); int minx = centerBlock.getX() - playerData.fillRadius; int maxx = centerBlock.getX() + playerData.fillRadius; @@ -1105,8 +1157,8 @@ class PlayerEventHandler implements Listener break; } - //only replace air and spilling water - if(block.getType() == Material.AIR || block.getType() == Material.STATIONARY_WATER && block.getData() != 0) + //only replace air, spilling water, snow + if(block.getType() == Material.AIR || block.getType() == Material.SNOW || (block.getType() == Material.STATIONARY_WATER && block.getData() != 0)) { //look to neighbors for an appropriate fill block Block eastBlock = block.getRelative(BlockFace.EAST); @@ -1155,7 +1207,7 @@ class PlayerEventHandler implements Listener //if the player doesn't have claims permission, don't do anything if(GriefPrevention.instance.config_claims_creationRequiresPermission && !player.hasPermission("griefprevention.createclaims")) { - GriefPrevention.sendMessage(player, TextMode.Err, "You don't have permission to claim land."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoCreateClaimPermission); return; } @@ -1214,7 +1266,7 @@ class PlayerEventHandler implements Listener if(!playerData.claimResizing.isAdminClaim() && (newWidth < GriefPrevention.instance.config_claims_minSize || newHeight < GriefPrevention.instance.config_claims_minSize)) { - GriefPrevention.sendMessage(player, TextMode.Err, "This new size would be too small. Claims must be at least " + GriefPrevention.instance.config_claims_minSize + " x " + GriefPrevention.instance.config_claims_minSize + "."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.ResizeClaimTooSmall, String.valueOf(GriefPrevention.instance.config_claims_minSize)); return; } @@ -1226,7 +1278,7 @@ class PlayerEventHandler implements Listener if(blocksRemainingAfter < 0) { - GriefPrevention.sendMessage(player, TextMode.Err, "You don't have enough blocks for this size. You need " + Math.abs(blocksRemainingAfter) + " more."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.ResizeNeedMoreBlocks, String.valueOf(Math.abs(blocksRemainingAfter))); return; } } @@ -1250,7 +1302,7 @@ class PlayerEventHandler implements Listener //enforce creative mode rule if(!player.hasPermission("griefprevention.deleteclaims") && GriefPrevention.instance.creativeRulesApply(player.getLocation())) { - GriefPrevention.sendMessage(player, TextMode.Err, "You can't un-claim creative mode land. You can only make this claim larger or create additional claims."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoCreativeUnClaim); return; } @@ -1265,7 +1317,7 @@ class PlayerEventHandler implements Listener if(result.succeeded) { //inform and show the player - GriefPrevention.sendMessage(player, TextMode.Success, "Claim resized. You now have " + playerData.getRemainingClaimBlocks() + " available claim blocks."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.ClaimResizeSuccess, String.valueOf(playerData.getRemainingClaimBlocks())); Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.Claim); Visualization.Apply(player, visualization); @@ -1282,7 +1334,7 @@ class PlayerEventHandler implements Listener else { //inform player - GriefPrevention.sendMessage(player, TextMode.Err, "Can't resize here because it would overlap another nearby claim."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.ResizeFailOverlap); //show the player the conflicting claim Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.ErrorClaim); @@ -1307,7 +1359,7 @@ class PlayerEventHandler implements Listener { playerData.claimResizing = claim; playerData.lastShovelLocation = clickedBlock.getLocation(); - player.sendMessage("Resizing claim. Use your shovel again at the new location for this corner."); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.ResizeStart); } //if he didn't click on a corner and is in subdivision mode, he's creating a new subdivision @@ -1319,13 +1371,13 @@ class PlayerEventHandler implements Listener //if the clicked claim was a subdivision, tell him he can't start a new subdivision here if(claim.parent != null) { - GriefPrevention.sendMessage(player, TextMode.Err, "You can't create a subdivision here because it would overlap another subdivision. Consider /abandonclaim to delete it, or use your shovel at a corner to resize it."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.ResizeFailOverlapSubdivision); } //otherwise start a new subdivision else { - GriefPrevention.sendMessage(player, TextMode.Instr, "Subdivision corner set! Use your shovel at the location for the opposite corner of this new subdivision."); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SubdivisionStart); playerData.lastShovelLocation = clickedBlock.getLocation(); playerData.claimSubdividing = claim; } @@ -1354,7 +1406,7 @@ class PlayerEventHandler implements Listener //if it didn't succeed, tell the player why if(!result.succeeded) { - GriefPrevention.sendMessage(player, TextMode.Err, "Your selected area overlaps another subdivision."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.CreateSubdivisionOverlap); Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.ErrorClaim); Visualization.Apply(player, visualization); @@ -1365,7 +1417,7 @@ class PlayerEventHandler implements Listener //otherwise, advise him on the /trust command and show him his new subdivision else { - GriefPrevention.sendMessage(player, TextMode.Success, "Subdivision created! Use /trust to share it with friends."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.SubdivisionSuccess); Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.Claim); Visualization.Apply(player, visualization); playerData.lastShovelLocation = null; @@ -1378,7 +1430,7 @@ class PlayerEventHandler implements Listener //also advise him to consider /abandonclaim or resizing the existing claim else { - GriefPrevention.sendMessage(player, TextMode.Err, "You can't create a claim here because it would overlap your other claim. Use /abandonclaim to delete it, or use your shovel at a corner to resize it."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlap); Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.Claim); Visualization.Apply(player, visualization); } @@ -1387,7 +1439,7 @@ class PlayerEventHandler implements Listener //otherwise tell the player he can't claim here because it's someone else's claim, and show him the claim else { - GriefPrevention.sendMessage(player, TextMode.Err, "You can't create a claim here because it would overlap " + claim.getOwnerName() + "'s claim."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlapOtherPlayer, claim.getOwnerName()); Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.ErrorClaim); Visualization.Apply(player, visualization); } @@ -1404,13 +1456,13 @@ class PlayerEventHandler implements Listener //if claims are not enabled in this world and it's not an administrative claim, display an error message and stop if(!GriefPrevention.instance.claimsEnabledForWorld(player.getWorld()) && playerData.shovelMode != ShovelMode.Admin) { - GriefPrevention.sendMessage(player, TextMode.Err, "Land claims are disabled in this world."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.ClaimsDisabledWorld); return; } //remember it, and start him on the new claim playerData.lastShovelLocation = clickedBlock.getLocation(); - GriefPrevention.sendMessage(player, TextMode.Instr, "Claim corner set! Use the shovel again at the opposite corner to claim a rectangle of land. To cancel, put your shovel away."); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.ClaimStart); } //otherwise, he's trying to finish creating a claim by setting the other boundary corner @@ -1430,7 +1482,7 @@ class PlayerEventHandler implements Listener if(playerData.shovelMode != ShovelMode.Admin && (newClaimWidth < GriefPrevention.instance.config_claims_minSize || newClaimHeight < GriefPrevention.instance.config_claims_minSize)) { - GriefPrevention.sendMessage(player, TextMode.Err, "This claim would be too small. Any claim must be at least " + GriefPrevention.instance.config_claims_minSize + " x " + GriefPrevention.instance.config_claims_minSize + "."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NewClaimTooSmall, String.valueOf(GriefPrevention.instance.config_claims_minSize)); return; } @@ -1441,8 +1493,8 @@ class PlayerEventHandler implements Listener int remainingBlocks = playerData.getRemainingClaimBlocks(); if(newClaimArea > remainingBlocks) { - GriefPrevention.sendMessage(player, TextMode.Err, "You don't have enough blocks to claim that entire area. You need " + (newClaimArea - remainingBlocks) + " more blocks."); - GriefPrevention.sendMessage(player, TextMode.Instr, "To delete another claim and free up some blocks, use /AbandonClaim."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.CreateClaimInsufficientBlocks, String.valueOf(newClaimArea - remainingBlocks)); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.AbandonClaimAdvertisement); return; } } @@ -1463,7 +1515,7 @@ class PlayerEventHandler implements Listener //if it didn't succeed, tell the player why if(!result.succeeded) { - GriefPrevention.sendMessage(player, TextMode.Err, "Your selected area overlaps an existing claim."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlapShort); Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.ErrorClaim); Visualization.Apply(player, visualization); @@ -1474,7 +1526,7 @@ class PlayerEventHandler implements Listener //otherwise, advise him on the /trust command and show him his new claim else { - GriefPrevention.sendMessage(player, TextMode.Success, "Claim created! Use /trust to share it with friends."); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.CreateClaimSuccess); Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.Claim); Visualization.Apply(player, visualization); playerData.lastShovelLocation = null; diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerRescueTask.java b/src/me/ryanhamshire/GriefPrevention/PlayerRescueTask.java index e73bd88..a4be573 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerRescueTask.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerRescueTask.java @@ -53,7 +53,7 @@ class PlayerRescueTask implements Runnable //if the player moved three or more blocks from where he used /trapped, admonish him and don't save him if(player.getLocation().distance(this.location) > 3) { - GriefPrevention.sendMessage(player, TextMode.Err, "You moved! Rescue cancelled."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.RescueAbortedMoved); return; } diff --git a/src/me/ryanhamshire/GriefPrevention/RestoreNatureProcessingTask.java b/src/me/ryanhamshire/GriefPrevention/RestoreNatureProcessingTask.java index 9deb386..529d48a 100644 --- a/src/me/ryanhamshire/GriefPrevention/RestoreNatureProcessingTask.java +++ b/src/me/ryanhamshire/GriefPrevention/RestoreNatureProcessingTask.java @@ -84,7 +84,6 @@ class RestoreNatureProcessingTask implements Runnable this.playerBlocks.add(Material.BREWING_STAND.getId()); this.playerBlocks.add(Material.BRICK.getId()); this.playerBlocks.add(Material.COBBLESTONE.getId()); - this.playerBlocks.add(Material.OBSIDIAN.getId()); this.playerBlocks.add(Material.GLASS.getId()); this.playerBlocks.add(Material.LAPIS_BLOCK.getId()); this.playerBlocks.add(Material.DISPENSER.getId()); @@ -148,7 +147,9 @@ class RestoreNatureProcessingTask implements Runnable this.playerBlocks.add(Material.BREWING_STAND.getId()); this.playerBlocks.add(Material.CAULDRON.getId()); this.playerBlocks.add(Material.DIODE_BLOCK_ON.getId()); - this.playerBlocks.add(Material.DIODE_BLOCK_ON.getId()); + this.playerBlocks.add(Material.DIODE_BLOCK_ON.getId()); + this.playerBlocks.add(Material.WEB.getId()); + this.playerBlocks.add(Material.SPONGE.getId()); //these are unnatural in the standard world, but not in the nether if(this.environment != Environment.NETHER) @@ -161,6 +162,12 @@ class RestoreNatureProcessingTask implements Runnable this.playerBlocks.add(Material.NETHER_BRICK_STAIRS.getId()); } + //these are unnatural in the standard and nether worlds, but not in the end + if(this.environment != Environment.THE_END) + { + this.playerBlocks.add(Material.OBSIDIAN.getId()); + } + //these are unnatural in sandy biomes, but not elsewhere if(this.biome == Biome.DESERT || this.biome == Biome.DESERT_HILLS || this.biome == Biome.BEACH || this.aggressiveMode) { @@ -178,6 +185,9 @@ class RestoreNatureProcessingTask implements Runnable this.playerBlocks.add(Material.PUMPKIN_STEM.getId()); this.playerBlocks.add(Material.MELON_BLOCK.getId()); this.playerBlocks.add(Material.MELON_STEM.getId()); + this.playerBlocks.add(Material.BEDROCK.getId()); + this.playerBlocks.add(Material.GRAVEL.getId()); + this.playerBlocks.add(Material.SANDSTONE.getId()); } } diff --git a/src/me/ryanhamshire/GriefPrevention/SecureClaimTask.java b/src/me/ryanhamshire/GriefPrevention/SecureClaimTask.java index 72d659d..d6f48e6 100644 --- a/src/me/ryanhamshire/GriefPrevention/SecureClaimTask.java +++ b/src/me/ryanhamshire/GriefPrevention/SecureClaimTask.java @@ -47,7 +47,7 @@ class SecureClaimTask implements Runnable Player player = onlinePlayers[j]; if(claim.contains(player.getLocation(), false, false) && claim.allowAccess(player) != null) { - GriefPrevention.sendMessage(player, TextMode.Err, "Looting time is up! Ejected from the claim."); + GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeDoorsLockedEjection); GriefPrevention.instance.ejectPlayer(player); } }