diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/AutoExtendClaimTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/AutoExtendClaimTask.java index 60f7ee8..97e5443 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/AutoExtendClaimTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/AutoExtendClaimTask.java @@ -1,13 +1,13 @@ package me.ryanhamshire.GriefPrevention; -import java.util.ArrayList; - import org.bukkit.Bukkit; import org.bukkit.ChunkSnapshot; import org.bukkit.Material; import org.bukkit.World.Environment; import org.bukkit.block.Biome; +import java.util.ArrayList; + //automatically extends a claim downward based on block types detected class AutoExtendClaimTask implements Runnable { @@ -26,7 +26,7 @@ class AutoExtendClaimTask implements Runnable public void run() { int newY = this.getLowestBuiltY(); - if(newY < this.claim.getLesserBoundaryCorner().getBlockY()) + if (newY < this.claim.getLesserBoundaryCorner().getBlockY()) { Bukkit.getScheduler().runTask(GriefPrevention.instance, new ExecuteExtendClaimTask(claim, newY)); } @@ -35,73 +35,73 @@ class AutoExtendClaimTask implements Runnable private int getLowestBuiltY() { int y = this.claim.getLesserBoundaryCorner().getBlockY(); - - if(this.yTooSmall(y)) return y; + + if (this.yTooSmall(y)) return y; try { - for(ChunkSnapshot chunk : this.chunks) + for (ChunkSnapshot chunk : this.chunks) { - Biome biome = chunk.getBiome(0, 0); + Biome biome = chunk.getBiome(0, 0); ArrayList playerBlockIDs = RestoreNatureProcessingTask.getPlayerBlocks(this.worldType, biome); boolean ychanged = true; - while(!this.yTooSmall(y) && ychanged) + while (!this.yTooSmall(y) && ychanged) { ychanged = false; - for(int x = 0; x < 16; x++) + for (int x = 0; x < 16; x++) { - for(int z = 0; z < 16; z++) + for (int z = 0; z < 16; z++) { Material blockType = chunk.getBlockType(x, y, z); - while(!this.yTooSmall(y) && playerBlockIDs.contains(blockType)) + while (!this.yTooSmall(y) && playerBlockIDs.contains(blockType)) { ychanged = true; blockType = chunk.getBlockType(x, --y, z); } - if(this.yTooSmall(y)) return y; + if (this.yTooSmall(y)) return y; } } } - if(this.yTooSmall(y)) return y; + if (this.yTooSmall(y)) return y; } } catch (NoSuchMethodError e) { GriefPrevention.instance.getLogger().severe("You are running an outdated build of Craftbukkit/Spigot/Paper. Please update."); - for(ChunkSnapshot chunk : this.chunks) + for (ChunkSnapshot chunk : this.chunks) { - Biome biome = chunk.getBiome(0, 0); + Biome biome = chunk.getBiome(0, 0); ArrayList playerBlockIDs = RestoreNatureProcessingTask.getPlayerBlocks(this.worldType, biome); boolean ychanged = true; - while(!this.yTooSmall(y) && ychanged) + while (!this.yTooSmall(y) && ychanged) { ychanged = false; - for(int x = 0; x < 16; x++) + for (int x = 0; x < 16; x++) { - for(int z = 0; z < 16; z++) + for (int z = 0; z < 16; z++) { - Material blockType = chunk.getBlockType(x, y, z); - while(!this.yTooSmall(y) && playerBlockIDs.contains(blockType)) - { - ychanged = true; - blockType = chunk.getBlockType(x, --y, z); - } + Material blockType = chunk.getBlockType(x, y, z); + while (!this.yTooSmall(y) && playerBlockIDs.contains(blockType)) + { + ychanged = true; + blockType = chunk.getBlockType(x, --y, z); + } - if(this.yTooSmall(y)) return y; + if (this.yTooSmall(y)) return y; } } } - if(this.yTooSmall(y)) return y; + if (this.yTooSmall(y)) return y; } } - + return y; } @@ -109,7 +109,7 @@ class AutoExtendClaimTask implements Runnable { return y == 0 || y <= GriefPrevention.instance.config_claims_maxDepth; } - + //runs in the main execution thread, where it can safely change claims and save those changes private class ExecuteExtendClaimTask implements Runnable { diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/BlockEventHandler.java b/src/main/java/me/ryanhamshire/GriefPrevention/BlockEventHandler.java index 6099ed1..533e3e9 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/BlockEventHandler.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/BlockEventHandler.java @@ -66,903 +66,908 @@ import java.util.List; import java.util.UUID; //event handlers related to blocks -public class BlockEventHandler implements Listener +public class BlockEventHandler implements Listener { - //convenience reference to singleton datastore - private DataStore dataStore; - - private ArrayList trashBlocks; - - //constructor - public BlockEventHandler(DataStore dataStore) - { - this.dataStore = dataStore; - - //create the list of blocks which will not trigger a warning when they're placed outside of land claims - this.trashBlocks = new ArrayList(); - this.trashBlocks.add(Material.COBBLESTONE); - this.trashBlocks.add(Material.TORCH); - this.trashBlocks.add(Material.DIRT); - this.trashBlocks.add(Material.OAK_SAPLING); - this.trashBlocks.add(Material.SPRUCE_SAPLING); - this.trashBlocks.add(Material.BIRCH_SAPLING); - this.trashBlocks.add(Material.JUNGLE_SAPLING); - this.trashBlocks.add(Material.ACACIA_SAPLING); - this.trashBlocks.add(Material.DARK_OAK_SAPLING); - this.trashBlocks.add(Material.GRAVEL); - this.trashBlocks.add(Material.SAND); - this.trashBlocks.add(Material.TNT); - this.trashBlocks.add(Material.CRAFTING_TABLE); - } - - //when a player breaks a block... - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onBlockBreak(BlockBreakEvent breakEvent) - { - Player player = breakEvent.getPlayer(); - Block block = breakEvent.getBlock(); - - //make sure the player is allowed to break at the location - String noBuildReason = GriefPrevention.instance.allowBreak(player, block, block.getLocation(), breakEvent); - if(noBuildReason != null) - { - GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason); - breakEvent.setCancelled(true); - return; - } - } - - //when a player places a sign... - @EventHandler(ignoreCancelled = true) - public void onSignChanged(SignChangeEvent event) - { - //send sign content to online administrators - if(!GriefPrevention.instance.config_signNotifications) return; - - Player player = event.getPlayer(); - if(player == null) return; - - StringBuilder lines = new StringBuilder(" placed a sign @ " + GriefPrevention.getfriendlyLocationString(event.getBlock().getLocation())); - boolean notEmpty = false; - for(int i = 0; i < event.getLines().length; i++) - { - String withoutSpaces = event.getLine(i).replace(" ", ""); - if(!withoutSpaces.isEmpty()) - { - notEmpty = true; - lines.append("\n " + event.getLine(i)); - } - } - - String signMessage = lines.toString(); - - //prevent signs with blocked IP addresses - if(!player.hasPermission("griefprevention.spam") && GriefPrevention.instance.containsBlockedIP(signMessage)) + //convenience reference to singleton datastore + private DataStore dataStore; + + private ArrayList trashBlocks; + + //constructor + public BlockEventHandler(DataStore dataStore) + { + this.dataStore = dataStore; + + //create the list of blocks which will not trigger a warning when they're placed outside of land claims + this.trashBlocks = new ArrayList(); + this.trashBlocks.add(Material.COBBLESTONE); + this.trashBlocks.add(Material.TORCH); + this.trashBlocks.add(Material.DIRT); + this.trashBlocks.add(Material.OAK_SAPLING); + this.trashBlocks.add(Material.SPRUCE_SAPLING); + this.trashBlocks.add(Material.BIRCH_SAPLING); + this.trashBlocks.add(Material.JUNGLE_SAPLING); + this.trashBlocks.add(Material.ACACIA_SAPLING); + this.trashBlocks.add(Material.DARK_OAK_SAPLING); + this.trashBlocks.add(Material.GRAVEL); + this.trashBlocks.add(Material.SAND); + this.trashBlocks.add(Material.TNT); + this.trashBlocks.add(Material.CRAFTING_TABLE); + } + + //when a player breaks a block... + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onBlockBreak(BlockBreakEvent breakEvent) + { + Player player = breakEvent.getPlayer(); + Block block = breakEvent.getBlock(); + + //make sure the player is allowed to break at the location + String noBuildReason = GriefPrevention.instance.allowBreak(player, block, block.getLocation(), breakEvent); + if (noBuildReason != null) + { + GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason); + breakEvent.setCancelled(true); + return; + } + } + + //when a player places a sign... + @EventHandler(ignoreCancelled = true) + public void onSignChanged(SignChangeEvent event) + { + //send sign content to online administrators + if (!GriefPrevention.instance.config_signNotifications) return; + + Player player = event.getPlayer(); + if (player == null) return; + + StringBuilder lines = new StringBuilder(" placed a sign @ " + GriefPrevention.getfriendlyLocationString(event.getBlock().getLocation())); + boolean notEmpty = false; + for (int i = 0; i < event.getLines().length; i++) + { + String withoutSpaces = event.getLine(i).replace(" ", ""); + if (!withoutSpaces.isEmpty()) + { + notEmpty = true; + lines.append("\n " + event.getLine(i)); + } + } + + String signMessage = lines.toString(); + + //prevent signs with blocked IP addresses + if (!player.hasPermission("griefprevention.spam") && GriefPrevention.instance.containsBlockedIP(signMessage)) { event.setCancelled(true); return; } - - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - //if not empty and wasn't the same as the last sign, log it and remember it for later - //This has been temporarily removed since `signMessage` includes location, not just the message. Waste of memory IMO - //if(notEmpty && (playerData.lastSignMessage == null || !playerData.lastSignMessage.equals(signMessage))) - if (notEmpty) - { - GriefPrevention.AddLogEntry(player.getName() + lines.toString().replace("\n ", ";"), null); - PlayerEventHandler.makeSocialLogEntry(player.getName(), signMessage); - //playerData.lastSignMessage = signMessage; - - if(!player.hasPermission("griefprevention.eavesdropsigns")) - { - @SuppressWarnings("unchecked") - Collection players = (Collection)GriefPrevention.instance.getServer().getOnlinePlayers(); - for(Player otherPlayer : players) - { - if(otherPlayer.hasPermission("griefprevention.eavesdropsigns")) - { - otherPlayer.sendMessage(ChatColor.GRAY + player.getName() + signMessage); - } - } - } - } - } - - //when a player places multiple blocks... - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onBlocksPlace(BlockMultiPlaceEvent placeEvent) - { - Player player = placeEvent.getPlayer(); - - //don't track in worlds where claims are not enabled - if(!GriefPrevention.instance.claimsEnabledForWorld(placeEvent.getBlock().getWorld())) return; - + + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + //if not empty and wasn't the same as the last sign, log it and remember it for later + //This has been temporarily removed since `signMessage` includes location, not just the message. Waste of memory IMO + //if(notEmpty && (playerData.lastSignMessage == null || !playerData.lastSignMessage.equals(signMessage))) + if (notEmpty) + { + GriefPrevention.AddLogEntry(player.getName() + lines.toString().replace("\n ", ";"), null); + PlayerEventHandler.makeSocialLogEntry(player.getName(), signMessage); + //playerData.lastSignMessage = signMessage; + + if (!player.hasPermission("griefprevention.eavesdropsigns")) + { + @SuppressWarnings("unchecked") + Collection players = (Collection) GriefPrevention.instance.getServer().getOnlinePlayers(); + for (Player otherPlayer : players) + { + if (otherPlayer.hasPermission("griefprevention.eavesdropsigns")) + { + otherPlayer.sendMessage(ChatColor.GRAY + player.getName() + signMessage); + } + } + } + } + } + + //when a player places multiple blocks... + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onBlocksPlace(BlockMultiPlaceEvent placeEvent) + { + Player player = placeEvent.getPlayer(); + + //don't track in worlds where claims are not enabled + if (!GriefPrevention.instance.claimsEnabledForWorld(placeEvent.getBlock().getWorld())) return; + //make sure the player is allowed to build at the location - for(BlockState block : placeEvent.getReplacedBlockStates()) + for (BlockState block : placeEvent.getReplacedBlockStates()) { String noBuildReason = GriefPrevention.instance.allowBuild(player, block.getLocation(), block.getType()); - if(noBuildReason != null) + if (noBuildReason != null) { GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason); placeEvent.setCancelled(true); return; } } - } - - private boolean doesAllowFireProximityInWorld(World world) { - if (GriefPrevention.instance.pvpRulesApply(world)) { - return GriefPrevention.instance.config_pvp_allowFireNearPlayers; - } else { - return GriefPrevention.instance.config_pvp_allowFireNearPlayers_NonPvp; - } - } - - //when a player places a block... - @SuppressWarnings("null") + } + + private boolean doesAllowFireProximityInWorld(World world) + { + if (GriefPrevention.instance.pvpRulesApply(world)) + { + return GriefPrevention.instance.config_pvp_allowFireNearPlayers; + } else + { + return GriefPrevention.instance.config_pvp_allowFireNearPlayers_NonPvp; + } + } + + //when a player places a block... + @SuppressWarnings("null") @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onBlockPlace(BlockPlaceEvent placeEvent) - { - Player player = placeEvent.getPlayer(); - Block block = placeEvent.getBlock(); - - //FEATURE: limit fire placement, to prevent PvP-by-fire - - //if placed block is fire and pvp is off, apply rules for proximity to other players - if(block.getType() == Material.FIRE && !doesAllowFireProximityInWorld(block.getWorld())) - { - List players = block.getWorld().getPlayers(); - for(int i = 0; i < players.size(); i++) - { - Player otherPlayer = players.get(i); + public void onBlockPlace(BlockPlaceEvent placeEvent) + { + Player player = placeEvent.getPlayer(); + Block block = placeEvent.getBlock(); - // Ignore players in creative or spectator mode to avoid users from checking if someone is spectating near them - if(otherPlayer.getGameMode() == GameMode.CREATIVE || otherPlayer.getGameMode() == GameMode.SPECTATOR) { - continue; - } + //FEATURE: limit fire placement, to prevent PvP-by-fire - Location location = otherPlayer.getLocation(); - if(!otherPlayer.equals(player) && location.distanceSquared(block.getLocation()) < 9 && player.canSee(otherPlayer)) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerTooCloseForFire2); - placeEvent.setCancelled(true); - return; - } - } - } - - //don't track in worlds where claims are not enabled - if(!GriefPrevention.instance.claimsEnabledForWorld(placeEvent.getBlock().getWorld())) return; - - //make sure the player is allowed to build at the location - String noBuildReason = GriefPrevention.instance.allowBuild(player, block.getLocation(), block.getType()); - if(noBuildReason != null) - { - // Allow players with container trust to place books in lecterns - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(block.getLocation(), true, playerData.lastClaim); - if (block.getType() == Material.LECTERN && placeEvent.getBlockReplacedState().getType() == Material.LECTERN) - { - if (claim != null) - { - playerData.lastClaim = claim; - String noContainerReason = claim.allowContainers(player); - if (noContainerReason == null) - return; + //if placed block is fire and pvp is off, apply rules for proximity to other players + if (block.getType() == Material.FIRE && !doesAllowFireProximityInWorld(block.getWorld())) + { + List players = block.getWorld().getPlayers(); + for (int i = 0; i < players.size(); i++) + { + Player otherPlayer = players.get(i); - placeEvent.setCancelled(true); - GriefPrevention.sendMessage(player, TextMode.Err, noContainerReason); - return; - } - } - GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason); - placeEvent.setCancelled(true); - return; - } - - //if the block is being placed within or under an existing claim - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(block.getLocation(), true, playerData.lastClaim); - if(claim != null) - { - playerData.lastClaim = claim; - - //warn about TNT not destroying claimed blocks - if(block.getType() == Material.TNT && !claim.areExplosivesAllowed && playerData.siegeData == null) + // Ignore players in creative or spectator mode to avoid users from checking if someone is spectating near them + if (otherPlayer.getGameMode() == GameMode.CREATIVE || otherPlayer.getGameMode() == GameMode.SPECTATOR) + { + continue; + } + + Location location = otherPlayer.getLocation(); + if (!otherPlayer.equals(player) && location.distanceSquared(block.getLocation()) < 9 && player.canSee(otherPlayer)) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerTooCloseForFire2); + placeEvent.setCancelled(true); + return; + } + } + } + + //don't track in worlds where claims are not enabled + if (!GriefPrevention.instance.claimsEnabledForWorld(placeEvent.getBlock().getWorld())) return; + + //make sure the player is allowed to build at the location + String noBuildReason = GriefPrevention.instance.allowBuild(player, block.getLocation(), block.getType()); + if (noBuildReason != null) + { + // Allow players with container trust to place books in lecterns + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = this.dataStore.getClaimAt(block.getLocation(), true, playerData.lastClaim); + if (block.getType() == Material.LECTERN && placeEvent.getBlockReplacedState().getType() == Material.LECTERN) + { + if (claim != null) + { + playerData.lastClaim = claim; + String noContainerReason = claim.allowContainers(player); + if (noContainerReason == null) + return; + + placeEvent.setCancelled(true); + GriefPrevention.sendMessage(player, TextMode.Err, noContainerReason); + return; + } + } + GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason); + placeEvent.setCancelled(true); + return; + } + + //if the block is being placed within or under an existing claim + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = this.dataStore.getClaimAt(block.getLocation(), true, playerData.lastClaim); + if (claim != null) + { + playerData.lastClaim = claim; + + //warn about TNT not destroying claimed blocks + if (block.getType() == Material.TNT && !claim.areExplosivesAllowed && playerData.siegeData == null) { GriefPrevention.sendMessage(player, TextMode.Warn, Messages.NoTNTDamageClaims); GriefPrevention.sendMessage(player, TextMode.Instr, Messages.ClaimExplosivesAdvertisement); } - - //if the player has permission for the claim and he's placing UNDER the claim - if(block.getY() <= claim.lesserBoundaryCorner.getBlockY() && claim.allowBuild(player, block.getType()) == null) - { - //extend the claim downward - this.dataStore.extendClaim(claim, block.getY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance); - } - - //allow for a build warning in the future - playerData.warnedAboutBuildingOutsideClaims = false; - } - - //FEATURE: automatically create a claim when a player who has no claims places a chest - - //otherwise if there's no claim, the player is placing a chest, and new player automatic claims are enabled - else if(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius > -1 && player.hasPermission("griefprevention.createclaims") && block.getType() == Material.CHEST) - { - //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, Messages.TooDeepToClaim); - return; - } - - int radius = GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius; - - //if the player doesn't have any claims yet, automatically create a claim centered at the chest - if(playerData.getClaims().size() == 0) - { - //radius == 0 means protect ONLY the chest - 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.getUniqueId(), null, null, player); - GriefPrevention.sendMessage(player, TextMode.Success, Messages.ChestClaimConfirmation); - } - - //otherwise, create a claim in the area around the chest - else - { - //if failure due to insufficient claim blocks available - if(playerData.getRemainingClaimBlocks() < 1) + + //if the player has permission for the claim and he's placing UNDER the claim + if (block.getY() <= claim.lesserBoundaryCorner.getBlockY() && claim.allowBuild(player, block.getType()) == null) + { + //extend the claim downward + this.dataStore.extendClaim(claim, block.getY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance); + } + + //allow for a build warning in the future + playerData.warnedAboutBuildingOutsideClaims = false; + } + + //FEATURE: automatically create a claim when a player who has no claims places a chest + + //otherwise if there's no claim, the player is placing a chest, and new player automatic claims are enabled + else if (GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius > -1 && player.hasPermission("griefprevention.createclaims") && block.getType() == Material.CHEST) + { + //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, Messages.TooDeepToClaim); + return; + } + + int radius = GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius; + + //if the player doesn't have any claims yet, automatically create a claim centered at the chest + if (playerData.getClaims().size() == 0) + { + //radius == 0 means protect ONLY the chest + 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.getUniqueId(), null, null, player); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.ChestClaimConfirmation); + } + + //otherwise, create a claim in the area around the chest + else + { + //if failure due to insufficient claim blocks available + if (playerData.getRemainingClaimBlocks() < 1) { GriefPrevention.sendMessage(player, TextMode.Warn, Messages.NoEnoughBlocksForChestClaim); return; } - - //as long as the automatic claim overlaps another existing claim, shrink it - //note that since the player had permission to place the chest, at the very least, the automatic claim will include the chest - CreateClaimResult result = null; - while(radius >= 0) - { - int area = (radius * 2 + 1) * (radius * 2 + 1); - if(playerData.getRemainingClaimBlocks() >= area) - { - result = this.dataStore.createClaim( - block.getWorld(), - block.getX() - radius, block.getX() + radius, - block.getY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance, block.getY(), - block.getZ() - radius, block.getZ() + radius, - player.getUniqueId(), - null, null, - player); - - if(result.succeeded) break; - } - - radius--; - } - - if(result != null && result.succeeded) - { - //notify and explain to player - GriefPrevention.sendMessage(player, TextMode.Success, Messages.AutomaticClaimNotification); - - //show the player the protected area - Visualization visualization = Visualization.FromClaim(result.claim, block.getY(), VisualizationType.Claim, player.getLocation()); - Visualization.Apply(player, visualization); - } - } - - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsVideo2, DataStore.SURVIVAL_VIDEO_URL); - } - - //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, Messages.UnprotectedChestWarning); - } - } - - //FEATURE: limit wilderness tree planting to grass, or dirt with more blocks beneath it - else if(Tag.SAPLINGS.isTagged(block.getType()) && GriefPrevention.instance.config_blockSkyTrees && GriefPrevention.instance.claimsEnabledForWorld(player.getWorld())) - { - 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); - } - } - } - - //FEATURE: warn players when they're placing non-trash blocks outside of their claimed areas - else if(!this.trashBlocks.contains(block.getType()) && GriefPrevention.instance.claimsEnabledForWorld(block.getWorld())) - { - if(!playerData.warnedAboutBuildingOutsideClaims && !player.hasPermission("griefprevention.adminclaims") - && player.hasPermission("griefprevention.createclaims") && ((playerData.lastClaim == null - && playerData.getClaims().size() == 0) || (playerData.lastClaim != null - && playerData.lastClaim.isNear(player.getLocation(), 15)))) - { - Long now = null; - if(playerData.buildWarningTimestamp == null || (now = System.currentTimeMillis()) - playerData.buildWarningTimestamp > 600000) //10 minute cooldown - { - GriefPrevention.sendMessage(player, TextMode.Warn, Messages.BuildingOutsideClaims); - playerData.warnedAboutBuildingOutsideClaims = true; - - if(now == null) now = System.currentTimeMillis(); - playerData.buildWarningTimestamp = now; - - if(playerData.getClaims().size() < 2) - { - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsVideo2, DataStore.SURVIVAL_VIDEO_URL); - } - - if(playerData.lastClaim != null) - { - Visualization visualization = Visualization.FromClaim(playerData.lastClaim, block.getY(), VisualizationType.Claim, player.getLocation()); - Visualization.Apply(player, visualization); - } - } - } - } - - //warn players when they place TNT above sea level, since it doesn't destroy blocks there - if( GriefPrevention.instance.config_blockSurfaceOtherExplosions && block.getType() == Material.TNT && - block.getWorld().getEnvironment() != Environment.NETHER && - block.getY() > GriefPrevention.instance.getSeaLevel(block.getWorld()) - 5 && - claim == null && - playerData.siegeData == null) - { - GriefPrevention.sendMessage(player, TextMode.Warn, Messages.NoTNTDamageAboveSeaLevel); - } - - //warn players about disabled pistons outside of land claims - if( GriefPrevention.instance.config_pistonsInClaimsOnly && - (block.getType() == Material.PISTON || block.getType() == Material.STICKY_PISTON) && - claim == null ) - { - GriefPrevention.sendMessage(player, TextMode.Warn, Messages.NoPistonsOutsideClaims); - } - - //limit active blocks in creative mode worlds - if(!player.hasPermission("griefprevention.adminclaims") && GriefPrevention.instance.creativeRulesApply(block.getLocation()) && isActiveBlock(block)) - { - String noPlaceReason = claim.allowMoreActiveBlocks(); - if(noPlaceReason != null) + + //as long as the automatic claim overlaps another existing claim, shrink it + //note that since the player had permission to place the chest, at the very least, the automatic claim will include the chest + CreateClaimResult result = null; + while (radius >= 0) + { + int area = (radius * 2 + 1) * (radius * 2 + 1); + if (playerData.getRemainingClaimBlocks() >= area) + { + result = this.dataStore.createClaim( + block.getWorld(), + block.getX() - radius, block.getX() + radius, + block.getY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance, block.getY(), + block.getZ() - radius, block.getZ() + radius, + player.getUniqueId(), + null, null, + player); + + if (result.succeeded) break; + } + + radius--; + } + + if (result != null && result.succeeded) + { + //notify and explain to player + GriefPrevention.sendMessage(player, TextMode.Success, Messages.AutomaticClaimNotification); + + //show the player the protected area + Visualization visualization = Visualization.FromClaim(result.claim, block.getY(), VisualizationType.Claim, player.getLocation()); + Visualization.Apply(player, visualization); + } + } + + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsVideo2, DataStore.SURVIVAL_VIDEO_URL); + } + + //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, Messages.UnprotectedChestWarning); + } + } + + //FEATURE: limit wilderness tree planting to grass, or dirt with more blocks beneath it + else if (Tag.SAPLINGS.isTagged(block.getType()) && GriefPrevention.instance.config_blockSkyTrees && GriefPrevention.instance.claimsEnabledForWorld(player.getWorld())) + { + 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); + } + } + } + + //FEATURE: warn players when they're placing non-trash blocks outside of their claimed areas + else if (!this.trashBlocks.contains(block.getType()) && GriefPrevention.instance.claimsEnabledForWorld(block.getWorld())) + { + if (!playerData.warnedAboutBuildingOutsideClaims && !player.hasPermission("griefprevention.adminclaims") + && player.hasPermission("griefprevention.createclaims") && ((playerData.lastClaim == null + && playerData.getClaims().size() == 0) || (playerData.lastClaim != null + && playerData.lastClaim.isNear(player.getLocation(), 15)))) + { + Long now = null; + if (playerData.buildWarningTimestamp == null || (now = System.currentTimeMillis()) - playerData.buildWarningTimestamp > 600000) //10 minute cooldown + { + GriefPrevention.sendMessage(player, TextMode.Warn, Messages.BuildingOutsideClaims); + playerData.warnedAboutBuildingOutsideClaims = true; + + if (now == null) now = System.currentTimeMillis(); + playerData.buildWarningTimestamp = now; + + if (playerData.getClaims().size() < 2) + { + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsVideo2, DataStore.SURVIVAL_VIDEO_URL); + } + + if (playerData.lastClaim != null) + { + Visualization visualization = Visualization.FromClaim(playerData.lastClaim, block.getY(), VisualizationType.Claim, player.getLocation()); + Visualization.Apply(player, visualization); + } + } + } + } + + //warn players when they place TNT above sea level, since it doesn't destroy blocks there + if (GriefPrevention.instance.config_blockSurfaceOtherExplosions && block.getType() == Material.TNT && + block.getWorld().getEnvironment() != Environment.NETHER && + block.getY() > GriefPrevention.instance.getSeaLevel(block.getWorld()) - 5 && + claim == null && + playerData.siegeData == null) + { + GriefPrevention.sendMessage(player, TextMode.Warn, Messages.NoTNTDamageAboveSeaLevel); + } + + //warn players about disabled pistons outside of land claims + if (GriefPrevention.instance.config_pistonsInClaimsOnly && + (block.getType() == Material.PISTON || block.getType() == Material.STICKY_PISTON) && + claim == null) + { + GriefPrevention.sendMessage(player, TextMode.Warn, Messages.NoPistonsOutsideClaims); + } + + //limit active blocks in creative mode worlds + if (!player.hasPermission("griefprevention.adminclaims") && GriefPrevention.instance.creativeRulesApply(block.getLocation()) && isActiveBlock(block)) + { + String noPlaceReason = claim.allowMoreActiveBlocks(); + if (noPlaceReason != null) { GriefPrevention.sendMessage(player, TextMode.Err, noPlaceReason); placeEvent.setCancelled(true); return; } - } - } - - static boolean isActiveBlock(Block block) - { - return isActiveBlock(block.getType()); - } - - static boolean isActiveBlock(BlockState state) - { - return isActiveBlock(state.getType()); - } - - static boolean isActiveBlock(Material type) - { - if(type == Material.HOPPER || type == Material.BEACON || type == Material.SPAWNER) return true; - return false; - } - - //blocks "pushing" other players' blocks around (pistons) - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onBlockPistonExtend (BlockPistonExtendEvent event) - { - //return if piston checks are not enabled - if(!GriefPrevention.instance.config_checkPistonMovement) return; - - //pushing down is ALWAYS safe - if(event.getDirection() == BlockFace.DOWN) return; - - //don't track in worlds where claims are not enabled - if(!GriefPrevention.instance.claimsEnabledForWorld(event.getBlock().getWorld())) return; - - Block pistonBlock = event.getBlock(); - List blocks = event.getBlocks(); - - //if no blocks moving, then only check to make sure we're not pushing into a claim from outside - //this avoids pistons breaking non-solids just inside a claim, like torches, doors, and touchplates - if(blocks.size() == 0) - { - Block invadedBlock = pistonBlock.getRelative(event.getDirection()); - - //pushing "air" is harmless - if(invadedBlock.getType() == Material.AIR) return; - - if( this.dataStore.getClaimAt(pistonBlock.getLocation(), false, null) == null && - this.dataStore.getClaimAt(invadedBlock.getLocation(), false, null) != null) - { - event.setCancelled(true); - } - - return; - } - - //who owns the piston, if anyone? - String pistonClaimOwnerName = "_"; - Claim claim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null); - if(claim != null) pistonClaimOwnerName = claim.getOwnerName(); - - //if pistons are limited to same-claim block movement - if(GriefPrevention.instance.config_pistonsInClaimsOnly) - { - //if piston is not in a land claim, cancel event - if(claim == null) - { - event.setCancelled(true); - return; - } - - for(Block pushedBlock : event.getBlocks()) - { - //if pushing blocks located outside the land claim it lives in, cancel the event - if(!claim.contains(pushedBlock.getLocation(), false, false)) - { - event.setCancelled(true); - return; - } - - //if pushing a block inside the claim out of the claim, cancel the event - //reason: could push into another land claim, don't want to spend CPU checking for that - //reason: push ice out, place torch, get water outside the claim - if(!claim.contains(pushedBlock.getRelative(event.getDirection()).getLocation(), false, false)) + } + } + + static boolean isActiveBlock(Block block) + { + return isActiveBlock(block.getType()); + } + + static boolean isActiveBlock(BlockState state) + { + return isActiveBlock(state.getType()); + } + + static boolean isActiveBlock(Material type) + { + if (type == Material.HOPPER || type == Material.BEACON || type == Material.SPAWNER) return true; + return false; + } + + //blocks "pushing" other players' blocks around (pistons) + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onBlockPistonExtend(BlockPistonExtendEvent event) + { + //return if piston checks are not enabled + if (!GriefPrevention.instance.config_checkPistonMovement) return; + + //pushing down is ALWAYS safe + if (event.getDirection() == BlockFace.DOWN) return; + + //don't track in worlds where claims are not enabled + if (!GriefPrevention.instance.claimsEnabledForWorld(event.getBlock().getWorld())) return; + + Block pistonBlock = event.getBlock(); + List blocks = event.getBlocks(); + + //if no blocks moving, then only check to make sure we're not pushing into a claim from outside + //this avoids pistons breaking non-solids just inside a claim, like torches, doors, and touchplates + if (blocks.size() == 0) + { + Block invadedBlock = pistonBlock.getRelative(event.getDirection()); + + //pushing "air" is harmless + if (invadedBlock.getType() == Material.AIR) return; + + if (this.dataStore.getClaimAt(pistonBlock.getLocation(), false, null) == null && + this.dataStore.getClaimAt(invadedBlock.getLocation(), false, null) != null) + { + event.setCancelled(true); + } + + return; + } + + //who owns the piston, if anyone? + String pistonClaimOwnerName = "_"; + Claim claim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null); + if (claim != null) pistonClaimOwnerName = claim.getOwnerName(); + + //if pistons are limited to same-claim block movement + if (GriefPrevention.instance.config_pistonsInClaimsOnly) + { + //if piston is not in a land claim, cancel event + if (claim == null) + { + event.setCancelled(true); + return; + } + + for (Block pushedBlock : event.getBlocks()) + { + //if pushing blocks located outside the land claim it lives in, cancel the event + if (!claim.contains(pushedBlock.getLocation(), false, false)) { event.setCancelled(true); return; } - } - } - - //otherwise, consider ownership of piston and EACH pushed block - else - { - //which blocks are being pushed? - Claim cachedClaim = claim; - for(int i = 0; i < blocks.size(); i++) - { - //if ANY of the pushed blocks are owned by someone other than the piston owner, cancel the event - Block block = blocks.get(i); - claim = this.dataStore.getClaimAt(block.getLocation(), false, cachedClaim); - if(claim != null) - { - cachedClaim = claim; - if(!claim.getOwnerName().equals(pistonClaimOwnerName)) - { - event.setCancelled(true); - pistonBlock.getWorld().createExplosion(pistonBlock.getLocation(), 0); - pistonBlock.getWorld().dropItem(pistonBlock.getLocation(), new ItemStack(pistonBlock.getType())); - pistonBlock.setType(Material.AIR); - return; - } - } - } - - //if any of the blocks are being pushed into a claim from outside, cancel the event - for(int i = 0; i < blocks.size(); i++) - { - Block block = blocks.get(i); - Claim originalClaim = this.dataStore.getClaimAt(block.getLocation(), false, cachedClaim); - String originalOwnerName = ""; - if(originalClaim != null) - { - cachedClaim = originalClaim; - originalOwnerName = originalClaim.getOwnerName(); - } - - Claim newClaim = this.dataStore.getClaimAt(block.getRelative(event.getDirection()).getLocation(), false, cachedClaim); - String newOwnerName = ""; - if(newClaim != null) - { - newOwnerName = newClaim.getOwnerName(); - } - - //if pushing this block will change ownership, cancel the event and take away the piston (for performance reasons) - if(!newOwnerName.equals(originalOwnerName) && !newOwnerName.isEmpty()) - { - event.setCancelled(true); - pistonBlock.getWorld().createExplosion(pistonBlock.getLocation(), 0); - pistonBlock.getWorld().dropItem(pistonBlock.getLocation(), new ItemStack(pistonBlock.getType())); - pistonBlock.setType(Material.AIR); - return; - } - } - } - } - - //blocks theft by pulling blocks out of a claim (again pistons) - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onBlockPistonRetract (BlockPistonRetractEvent event) - { - //return if piston checks are not enabled - if(!GriefPrevention.instance.config_checkPistonMovement) return; - - //pulling up is always safe - if(event.getDirection() == BlockFace.UP) return; - - try - { - //don't track in worlds where claims are not enabled - if(!GriefPrevention.instance.claimsEnabledForWorld(event.getBlock().getWorld())) return; - - //if pistons limited to only pulling blocks which are in the same claim the piston is in - if(GriefPrevention.instance.config_pistonsInClaimsOnly) - { - //if piston not in a land claim, cancel event - Claim pistonClaim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null); - if(pistonClaim == null && !event.getBlocks().isEmpty()) - { - event.setCancelled(true); - return; - } - - for(Block movedBlock : event.getBlocks()) - { - //if pulled block isn't in the same land claim, cancel the event - if(!pistonClaim.contains(movedBlock.getLocation(), false, false)) - { - event.setCancelled(true); - return; - } - } - } - - //otherwise, consider ownership of both piston and block - else - { - //who owns the piston, if anyone? + + //if pushing a block inside the claim out of the claim, cancel the event + //reason: could push into another land claim, don't want to spend CPU checking for that + //reason: push ice out, place torch, get water outside the claim + if (!claim.contains(pushedBlock.getRelative(event.getDirection()).getLocation(), false, false)) + { + event.setCancelled(true); + return; + } + } + } + + //otherwise, consider ownership of piston and EACH pushed block + else + { + //which blocks are being pushed? + Claim cachedClaim = claim; + for (int i = 0; i < blocks.size(); i++) + { + //if ANY of the pushed blocks are owned by someone other than the piston owner, cancel the event + Block block = blocks.get(i); + claim = this.dataStore.getClaimAt(block.getLocation(), false, cachedClaim); + if (claim != null) + { + cachedClaim = claim; + if (!claim.getOwnerName().equals(pistonClaimOwnerName)) + { + event.setCancelled(true); + pistonBlock.getWorld().createExplosion(pistonBlock.getLocation(), 0); + pistonBlock.getWorld().dropItem(pistonBlock.getLocation(), new ItemStack(pistonBlock.getType())); + pistonBlock.setType(Material.AIR); + return; + } + } + } + + //if any of the blocks are being pushed into a claim from outside, cancel the event + for (int i = 0; i < blocks.size(); i++) + { + Block block = blocks.get(i); + Claim originalClaim = this.dataStore.getClaimAt(block.getLocation(), false, cachedClaim); + String originalOwnerName = ""; + if (originalClaim != null) + { + cachedClaim = originalClaim; + originalOwnerName = originalClaim.getOwnerName(); + } + + Claim newClaim = this.dataStore.getClaimAt(block.getRelative(event.getDirection()).getLocation(), false, cachedClaim); + String newOwnerName = ""; + if (newClaim != null) + { + newOwnerName = newClaim.getOwnerName(); + } + + //if pushing this block will change ownership, cancel the event and take away the piston (for performance reasons) + if (!newOwnerName.equals(originalOwnerName) && !newOwnerName.isEmpty()) + { + event.setCancelled(true); + pistonBlock.getWorld().createExplosion(pistonBlock.getLocation(), 0); + pistonBlock.getWorld().dropItem(pistonBlock.getLocation(), new ItemStack(pistonBlock.getType())); + pistonBlock.setType(Material.AIR); + return; + } + } + } + } + + //blocks theft by pulling blocks out of a claim (again pistons) + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onBlockPistonRetract(BlockPistonRetractEvent event) + { + //return if piston checks are not enabled + if (!GriefPrevention.instance.config_checkPistonMovement) return; + + //pulling up is always safe + if (event.getDirection() == BlockFace.UP) return; + + try + { + //don't track in worlds where claims are not enabled + if (!GriefPrevention.instance.claimsEnabledForWorld(event.getBlock().getWorld())) return; + + //if pistons limited to only pulling blocks which are in the same claim the piston is in + if (GriefPrevention.instance.config_pistonsInClaimsOnly) + { + //if piston not in a land claim, cancel event + Claim pistonClaim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null); + if (pistonClaim == null && !event.getBlocks().isEmpty()) + { + event.setCancelled(true); + return; + } + + for (Block movedBlock : event.getBlocks()) + { + //if pulled block isn't in the same land claim, cancel the event + if (!pistonClaim.contains(movedBlock.getLocation(), false, false)) + { + event.setCancelled(true); + return; + } + } + } + + //otherwise, consider ownership of both piston and block + else + { + //who owns the piston, if anyone? String pistonOwnerName = "_"; Block block = event.getBlock(); - Location pistonLocation = block.getLocation(); + Location pistonLocation = block.getLocation(); Claim pistonClaim = this.dataStore.getClaimAt(pistonLocation, false, null); - if(pistonClaim != null) pistonOwnerName = pistonClaim.getOwnerName(); - - String movingBlockOwnerName = "_"; - for(Block movedBlock : event.getBlocks()) - { - //who owns the moving block, if anyone? - Claim movingBlockClaim = this.dataStore.getClaimAt(movedBlock.getLocation(), false, pistonClaim); - if(movingBlockClaim != null) movingBlockOwnerName = movingBlockClaim.getOwnerName(); - - //if there are owners for the blocks, they must be the same player - //otherwise cancel the event - if(!pistonOwnerName.equals(movingBlockOwnerName)) - { - event.setCancelled(true); - block.getWorld().createExplosion(block.getLocation(), 0); - block.getWorld().dropItem(block.getLocation(), new ItemStack(Material.STICKY_PISTON)); - block.setType(Material.AIR); - return; - } - } - } - } - catch(NoSuchMethodError exception) - { - GriefPrevention.AddLogEntry("Your server is running an outdated version of 1.8 which has a griefing vulnerability. Update your server (reruns buildtools.jar to get an updated server JAR file) to ensure players can't steal claimed blocks using pistons."); - } - } - - //blocks are ignited ONLY by flint and steel (not by being near lava, open flames, etc), unless configured otherwise - @EventHandler(priority = EventPriority.LOWEST) - public void onBlockIgnite (BlockIgniteEvent igniteEvent) - { - //don't track in worlds where claims are not enabled - if(!GriefPrevention.instance.claimsEnabledForWorld(igniteEvent.getBlock().getWorld())) return; + if (pistonClaim != null) pistonOwnerName = pistonClaim.getOwnerName(); - if(igniteEvent.getCause() == IgniteCause.LIGHTNING && GriefPrevention.instance.dataStore.getClaimAt(igniteEvent.getIgnitingEntity().getLocation(), false, null) != null){ + String movingBlockOwnerName = "_"; + for (Block movedBlock : event.getBlocks()) + { + //who owns the moving block, if anyone? + Claim movingBlockClaim = this.dataStore.getClaimAt(movedBlock.getLocation(), false, pistonClaim); + if (movingBlockClaim != null) movingBlockOwnerName = movingBlockClaim.getOwnerName(); + + //if there are owners for the blocks, they must be the same player + //otherwise cancel the event + if (!pistonOwnerName.equals(movingBlockOwnerName)) + { + event.setCancelled(true); + block.getWorld().createExplosion(block.getLocation(), 0); + block.getWorld().dropItem(block.getLocation(), new ItemStack(Material.STICKY_PISTON)); + block.setType(Material.AIR); + return; + } + } + } + } + catch (NoSuchMethodError exception) + { + GriefPrevention.AddLogEntry("Your server is running an outdated version of 1.8 which has a griefing vulnerability. Update your server (reruns buildtools.jar to get an updated server JAR file) to ensure players can't steal claimed blocks using pistons."); + } + } + + //blocks are ignited ONLY by flint and steel (not by being near lava, open flames, etc), unless configured otherwise + @EventHandler(priority = EventPriority.LOWEST) + public void onBlockIgnite(BlockIgniteEvent igniteEvent) + { + //don't track in worlds where claims are not enabled + if (!GriefPrevention.instance.claimsEnabledForWorld(igniteEvent.getBlock().getWorld())) return; + + if (igniteEvent.getCause() == IgniteCause.LIGHTNING && GriefPrevention.instance.dataStore.getClaimAt(igniteEvent.getIgnitingEntity().getLocation(), false, null) != null) + { // if(igniteEvent.getIgnitingEntity().hasMetadata("GP_TRIDENT")){ //BlockIgniteEvent is called before LightningStrikeEvent. See #532 - igniteEvent.setCancelled(true); + igniteEvent.setCancelled(true); // } } - - if(!GriefPrevention.instance.config_fireSpreads && igniteEvent.getCause() != IgniteCause.FLINT_AND_STEEL && igniteEvent.getCause() != IgniteCause.LIGHTNING) - { - igniteEvent.setCancelled(true); - } - } - - //fire doesn't spread unless configured to, but other blocks still do (mushrooms and vines, for example) - @EventHandler(priority = EventPriority.LOWEST) - public void onBlockSpread (BlockSpreadEvent spreadEvent) - { - if(spreadEvent.getSource().getType() != Material.FIRE) return; - - //don't track in worlds where claims are not enabled - if(!GriefPrevention.instance.claimsEnabledForWorld(spreadEvent.getBlock().getWorld())) return; - - if(!GriefPrevention.instance.config_fireSpreads) - { - spreadEvent.setCancelled(true); - - Block underBlock = spreadEvent.getSource().getRelative(BlockFace.DOWN); - if(underBlock.getType() != Material.NETHERRACK) - { - spreadEvent.getSource().setType(Material.AIR); - } - - return; - } - - //never spread into a claimed area, regardless of settings - if(this.dataStore.getClaimAt(spreadEvent.getBlock().getLocation(), false, null) != null) - { - if(GriefPrevention.instance.config_claims_firespreads) return; - spreadEvent.setCancelled(true); - - //if the source of the spread is not fire on netherrack, put out that source fire to save cpu cycles - Block source = spreadEvent.getSource(); - if(source.getRelative(BlockFace.DOWN).getType() != Material.NETHERRACK) - { - source.setType(Material.AIR); - } - } - } - - //blocks are not destroyed by fire, unless configured to do so - @EventHandler(priority = EventPriority.LOWEST) - public void onBlockBurn (BlockBurnEvent burnEvent) - { - //don't track in worlds where claims are not enabled - if(!GriefPrevention.instance.claimsEnabledForWorld(burnEvent.getBlock().getWorld())) return; - - if(!GriefPrevention.instance.config_fireDestroys) - { - burnEvent.setCancelled(true); - Block block = burnEvent.getBlock(); - Block [] adjacentBlocks = new Block [] - { - block.getRelative(BlockFace.UP), - block.getRelative(BlockFace.DOWN), - block.getRelative(BlockFace.NORTH), - block.getRelative(BlockFace.SOUTH), - block.getRelative(BlockFace.EAST), - block.getRelative(BlockFace.WEST) - }; - - //pro-actively put out any fires adjacent the burning block, to reduce future processing here - for(int i = 0; i < adjacentBlocks.length; i++) - { - Block adjacentBlock = adjacentBlocks[i]; - if(adjacentBlock.getType() == Material.FIRE && adjacentBlock.getRelative(BlockFace.DOWN).getType() != Material.NETHERRACK) - { - adjacentBlock.setType(Material.AIR); - } - } - - Block aboveBlock = block.getRelative(BlockFace.UP); - if(aboveBlock.getType() == Material.FIRE) - { - aboveBlock.setType(Material.AIR); - } - return; - } - - //never burn claimed blocks, regardless of settings - if(this.dataStore.getClaimAt(burnEvent.getBlock().getLocation(), false, null) != null) - { - if(GriefPrevention.instance.config_claims_firedamages) return; - burnEvent.setCancelled(true); - } - } + + if (!GriefPrevention.instance.config_fireSpreads && igniteEvent.getCause() != IgniteCause.FLINT_AND_STEEL && igniteEvent.getCause() != IgniteCause.LIGHTNING) + { + igniteEvent.setCancelled(true); + } + } + + //fire doesn't spread unless configured to, but other blocks still do (mushrooms and vines, for example) + @EventHandler(priority = EventPriority.LOWEST) + public void onBlockSpread(BlockSpreadEvent spreadEvent) + { + if (spreadEvent.getSource().getType() != Material.FIRE) return; + + //don't track in worlds where claims are not enabled + if (!GriefPrevention.instance.claimsEnabledForWorld(spreadEvent.getBlock().getWorld())) return; + + if (!GriefPrevention.instance.config_fireSpreads) + { + spreadEvent.setCancelled(true); + + Block underBlock = spreadEvent.getSource().getRelative(BlockFace.DOWN); + if (underBlock.getType() != Material.NETHERRACK) + { + spreadEvent.getSource().setType(Material.AIR); + } + + return; + } + + //never spread into a claimed area, regardless of settings + if (this.dataStore.getClaimAt(spreadEvent.getBlock().getLocation(), false, null) != null) + { + if (GriefPrevention.instance.config_claims_firespreads) return; + spreadEvent.setCancelled(true); + + //if the source of the spread is not fire on netherrack, put out that source fire to save cpu cycles + Block source = spreadEvent.getSource(); + if (source.getRelative(BlockFace.DOWN).getType() != Material.NETHERRACK) + { + source.setType(Material.AIR); + } + } + } + + //blocks are not destroyed by fire, unless configured to do so + @EventHandler(priority = EventPriority.LOWEST) + public void onBlockBurn(BlockBurnEvent burnEvent) + { + //don't track in worlds where claims are not enabled + if (!GriefPrevention.instance.claimsEnabledForWorld(burnEvent.getBlock().getWorld())) return; + + if (!GriefPrevention.instance.config_fireDestroys) + { + burnEvent.setCancelled(true); + Block block = burnEvent.getBlock(); + Block[] adjacentBlocks = new Block[] + { + block.getRelative(BlockFace.UP), + block.getRelative(BlockFace.DOWN), + block.getRelative(BlockFace.NORTH), + block.getRelative(BlockFace.SOUTH), + block.getRelative(BlockFace.EAST), + block.getRelative(BlockFace.WEST) + }; + + //pro-actively put out any fires adjacent the burning block, to reduce future processing here + for (int i = 0; i < adjacentBlocks.length; i++) + { + Block adjacentBlock = adjacentBlocks[i]; + if (adjacentBlock.getType() == Material.FIRE && adjacentBlock.getRelative(BlockFace.DOWN).getType() != Material.NETHERRACK) + { + adjacentBlock.setType(Material.AIR); + } + } + + Block aboveBlock = block.getRelative(BlockFace.UP); + if (aboveBlock.getType() == Material.FIRE) + { + aboveBlock.setType(Material.AIR); + } + return; + } + + //never burn claimed blocks, regardless of settings + if (this.dataStore.getClaimAt(burnEvent.getBlock().getLocation(), false, null) != null) + { + if (GriefPrevention.instance.config_claims_firedamages) return; + burnEvent.setCancelled(true); + } + } - - //ensures fluids don't flow into land claims from outside - 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; - - //don't track in worlds where claims are not enabled - if(!GriefPrevention.instance.claimsEnabledForWorld(spreadEvent.getBlock().getWorld())) return; - - //where to? + //ensures fluids don't flow into land claims from outside + 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; + + //don't track in worlds where claims are not enabled + if (!GriefPrevention.instance.claimsEnabledForWorld(spreadEvent.getBlock().getWorld())) return; + + //where to? Block toBlock = spreadEvent.getToBlock(); Location toLocation = toBlock.getLocation(); Claim toClaim = this.dataStore.getClaimAt(toLocation, false, lastSpreadClaim); - + //if into a land claim, it must be from the same land claim - if(toClaim != null) + if (toClaim != null) { this.lastSpreadClaim = toClaim; - if(!toClaim.contains(spreadEvent.getBlock().getLocation(), false, true)) + if (!toClaim.contains(spreadEvent.getBlock().getLocation(), false, true)) { //exception: from parent into subdivision - if(toClaim.parent == null || !toClaim.parent.contains(spreadEvent.getBlock().getLocation(), false, false)) + if (toClaim.parent == null || !toClaim.parent.contains(spreadEvent.getBlock().getLocation(), false, false)) { spreadEvent.setCancelled(true); } } } - + //otherwise if creative mode world, don't flow - else if(GriefPrevention.instance.creativeRulesApply(toLocation)) + else if (GriefPrevention.instance.creativeRulesApply(toLocation)) { spreadEvent.setCancelled(true); } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onForm(BlockFormEvent event) - { - Block block = event.getBlock(); - Location location = block.getLocation(); - - if(GriefPrevention.instance.creativeRulesApply(location)) - { - Material type = block.getType(); - if(type == Material.COBBLESTONE || type == Material.OBSIDIAN || type == Material.LAVA || type == Material.WATER) - { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(location, false, null); - if(claim == null) - { - event.setCancelled(true); - } - } - } - } + } - //Stop projectiles from destroying blocks that don't fire a proper event - @EventHandler(ignoreCancelled = true) - private void chorusFlower(ProjectileHitEvent event) - { - //don't track in worlds where claims are not enabled - if(!GriefPrevention.instance.claimsEnabledForWorld(event.getEntity().getWorld())) return; + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onForm(BlockFormEvent event) + { + Block block = event.getBlock(); + Location location = block.getLocation(); - if (event.getHitBlock() == null || event.getHitBlock().getType() != Material.CHORUS_FLOWER) - return; + if (GriefPrevention.instance.creativeRulesApply(location)) + { + Material type = block.getType(); + if (type == Material.COBBLESTONE || type == Material.OBSIDIAN || type == Material.LAVA || type == Material.WATER) + { + Claim claim = GriefPrevention.instance.dataStore.getClaimAt(location, false, null); + if (claim == null) + { + event.setCancelled(true); + } + } + } + } - Block block = event.getHitBlock(); + //Stop projectiles from destroying blocks that don't fire a proper event + @EventHandler(ignoreCancelled = true) + private void chorusFlower(ProjectileHitEvent event) + { + //don't track in worlds where claims are not enabled + if (!GriefPrevention.instance.claimsEnabledForWorld(event.getEntity().getWorld())) return; - Claim claim = dataStore.getClaimAt(block.getLocation(), false, null); - if (claim == null) - return; + if (event.getHitBlock() == null || event.getHitBlock().getType() != Material.CHORUS_FLOWER) + return; - Player shooter = null; - Projectile projectile = event.getEntity(); + Block block = event.getHitBlock(); - if (projectile.getShooter() instanceof Player) - shooter = (Player)projectile.getShooter(); + Claim claim = dataStore.getClaimAt(block.getLocation(), false, null); + if (claim == null) + return; - if (shooter == null) - { - event.getHitBlock().setType(Material.AIR); - Bukkit.getScheduler().runTask(GriefPrevention.instance, () -> event.getHitBlock().setBlockData(block.getBlockData())); - return; - } + Player shooter = null; + Projectile projectile = event.getEntity(); - String allowContainer = claim.allowContainers(shooter); + if (projectile.getShooter() instanceof Player) + shooter = (Player) projectile.getShooter(); - if (allowContainer != null) - { - event.getHitBlock().setType(Material.AIR); - Bukkit.getScheduler().runTask(GriefPrevention.instance, () -> event.getHitBlock().setBlockData(block.getBlockData())); - GriefPrevention.sendMessage(shooter, TextMode.Err, allowContainer); - return; - } - } - - //ensures dispensers can't be used to dispense a block(like water or lava) or item across a claim boundary - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onDispense(BlockDispenseEvent dispenseEvent) - { - //don't track in worlds where claims are not enabled - if(!GriefPrevention.instance.claimsEnabledForWorld(dispenseEvent.getBlock().getWorld())) return; - - //from where? - Block fromBlock = dispenseEvent.getBlock(); - BlockData fromData = fromBlock.getBlockData(); - if(!(fromData instanceof Dispenser)) return; - Dispenser dispenser = (Dispenser) fromData; - - //to where? - Block toBlock = fromBlock.getRelative(dispenser.getFacing()); - Claim fromClaim = this.dataStore.getClaimAt(fromBlock.getLocation(), false, null); - Claim toClaim = this.dataStore.getClaimAt(toBlock.getLocation(), false, fromClaim); - - //into wilderness is NOT OK in creative mode worlds - Material materialDispensed = dispenseEvent.getItem().getType(); - if((materialDispensed == Material.WATER_BUCKET || materialDispensed == Material.LAVA_BUCKET) && GriefPrevention.instance.creativeRulesApply(dispenseEvent.getBlock().getLocation()) && toClaim == null) - { - dispenseEvent.setCancelled(true); - return; - } - - //wilderness to wilderness is OK - if(fromClaim == null && toClaim == null) return; - - //within claim is OK - if(fromClaim == toClaim) return; - - //everything else is NOT OK - dispenseEvent.setCancelled(true); - } - - @EventHandler(ignoreCancelled = true) - public void onTreeGrow (StructureGrowEvent growEvent) + if (shooter == null) + { + event.getHitBlock().setType(Material.AIR); + Bukkit.getScheduler().runTask(GriefPrevention.instance, () -> event.getHitBlock().setBlockData(block.getBlockData())); + return; + } + + String allowContainer = claim.allowContainers(shooter); + + if (allowContainer != null) + { + event.getHitBlock().setType(Material.AIR); + Bukkit.getScheduler().runTask(GriefPrevention.instance, () -> event.getHitBlock().setBlockData(block.getBlockData())); + GriefPrevention.sendMessage(shooter, TextMode.Err, allowContainer); + return; + } + } + + //ensures dispensers can't be used to dispense a block(like water or lava) or item across a claim boundary + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onDispense(BlockDispenseEvent dispenseEvent) + { + //don't track in worlds where claims are not enabled + if (!GriefPrevention.instance.claimsEnabledForWorld(dispenseEvent.getBlock().getWorld())) return; + + //from where? + Block fromBlock = dispenseEvent.getBlock(); + BlockData fromData = fromBlock.getBlockData(); + if (!(fromData instanceof Dispenser)) return; + Dispenser dispenser = (Dispenser) fromData; + + //to where? + Block toBlock = fromBlock.getRelative(dispenser.getFacing()); + Claim fromClaim = this.dataStore.getClaimAt(fromBlock.getLocation(), false, null); + Claim toClaim = this.dataStore.getClaimAt(toBlock.getLocation(), false, fromClaim); + + //into wilderness is NOT OK in creative mode worlds + Material materialDispensed = dispenseEvent.getItem().getType(); + if ((materialDispensed == Material.WATER_BUCKET || materialDispensed == Material.LAVA_BUCKET) && GriefPrevention.instance.creativeRulesApply(dispenseEvent.getBlock().getLocation()) && toClaim == null) + { + dispenseEvent.setCancelled(true); + return; + } + + //wilderness to wilderness is OK + if (fromClaim == null && toClaim == null) return; + + //within claim is OK + if (fromClaim == toClaim) return; + + //everything else is NOT OK + dispenseEvent.setCancelled(true); + } + + @EventHandler(ignoreCancelled = true) + public void onTreeGrow(StructureGrowEvent growEvent) { //only take these potentially expensive steps if configured to do so - if(!GriefPrevention.instance.config_limitTreeGrowth) return; - - //don't track in worlds where claims are not enabled - if(!GriefPrevention.instance.claimsEnabledForWorld(growEvent.getWorld())) return; - - Location rootLocation = growEvent.getLocation(); + if (!GriefPrevention.instance.config_limitTreeGrowth) return; + + //don't track in worlds where claims are not enabled + if (!GriefPrevention.instance.claimsEnabledForWorld(growEvent.getWorld())) return; + + Location rootLocation = growEvent.getLocation(); Claim rootClaim = this.dataStore.getClaimAt(rootLocation, false, null); String rootOwnerName = null; - + //who owns the spreading block, if anyone? - if(rootClaim != null) + if (rootClaim != null) { //tree growth in subdivisions is dependent on who owns the top level claim - if(rootClaim.parent != null) rootClaim = rootClaim.parent; - + if (rootClaim.parent != null) rootClaim = rootClaim.parent; + //if an administrative claim, just let the tree grow where it wants - if(rootClaim.isAdminClaim()) return; - + if (rootClaim.isAdminClaim()) return; + //otherwise, note the owner of the claim rootOwnerName = rootClaim.getOwnerName(); } - + //for each block growing - for(int i = 0; i < growEvent.getBlocks().size(); i++) + for (int i = 0; i < growEvent.getBlocks().size(); i++) { BlockState block = growEvent.getBlocks().get(i); Claim blockClaim = this.dataStore.getClaimAt(block.getLocation(), false, rootClaim); - + //if it's growing into a claim - if(blockClaim != null) + if (blockClaim != null) { //if there's no owner for the new tree, or the owner for the new tree is different from the owner of the claim - if(rootOwnerName == null || !rootOwnerName.equals(blockClaim.getOwnerName())) + if (rootOwnerName == null || !rootOwnerName.equals(blockClaim.getOwnerName())) { growEvent.getBlocks().remove(i--); } } } } - - @EventHandler(ignoreCancelled = true) - public void onInventoryPickupItem (InventoryPickupItemEvent event) + + @EventHandler(ignoreCancelled = true) + public void onInventoryPickupItem(InventoryPickupItemEvent event) { - //prevent hoppers from picking-up items dropped by players on death + //prevent hoppers from picking-up items dropped by players on death - InventoryHolder holder = event.getInventory().getHolder(); - if(holder instanceof HopperMinecart || holder instanceof Hopper) - { - Item item = event.getItem(); - List data = item.getMetadata("GP_ITEMOWNER"); - //if this is marked as belonging to a player - if(data != null && data.size() > 0) - { - UUID ownerID = (UUID)data.get(0).value(); - - //has that player unlocked his drops? - OfflinePlayer owner = GriefPrevention.instance.getServer().getOfflinePlayer(ownerID); - if(owner.isOnline()) - { - PlayerData playerData = this.dataStore.getPlayerData(ownerID); + InventoryHolder holder = event.getInventory().getHolder(); + if (holder instanceof HopperMinecart || holder instanceof Hopper) + { + Item item = event.getItem(); + List data = item.getMetadata("GP_ITEMOWNER"); + //if this is marked as belonging to a player + if (data != null && data.size() > 0) + { + UUID ownerID = (UUID) data.get(0).value(); - //if locked, don't allow pickup - if(!playerData.dropsAreUnlocked) - { - event.setCancelled(true); - } - } - } - } - } + //has that player unlocked his drops? + OfflinePlayer owner = GriefPrevention.instance.getServer().getOfflinePlayer(ownerID); + if (owner.isOnline()) + { + PlayerData playerData = this.dataStore.getPlayerData(ownerID); + + //if locked, don't allow pickup + if (!playerData.dropsAreUnlocked) + { + event.setCancelled(true); + } + } + } + } + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/BlockSnapshot.java b/src/main/java/me/ryanhamshire/GriefPrevention/BlockSnapshot.java index 640f330..8f19893 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/BlockSnapshot.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/BlockSnapshot.java @@ -24,16 +24,16 @@ import org.bukkit.block.data.BlockData; //basically, just a few data points from a block conveniently encapsulated in a class //this is used only by the RestoreNature code -public class BlockSnapshot +public class BlockSnapshot { - public Location location; - public Material typeId; - public BlockData data; - - public BlockSnapshot(Location location, Material typeId, BlockData data) - { - this.location = location; - this.typeId = typeId; - this.data = data; - } + public Location location; + public Material typeId; + public BlockData data; + + public BlockSnapshot(Location location, Material typeId, BlockData data) + { + this.location = location; + this.typeId = typeId; + this.data = data; + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/BroadcastMessageTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/BroadcastMessageTask.java index 5079806..1608d37 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/BroadcastMessageTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/BroadcastMessageTask.java @@ -15,25 +15,25 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - + package me.ryanhamshire.GriefPrevention; import org.bukkit.Bukkit; //sends a message to all online players //used to send delayed messages, for example a quit message after the player has been gone a while -class BroadcastMessageTask implements Runnable +class BroadcastMessageTask implements Runnable { - private String message; - - public BroadcastMessageTask(String message) - { + private String message; + + public BroadcastMessageTask(String message) + { this.message = message; } @Override - public void run() - { - Bukkit.getServer().broadcastMessage(this.message); - } + public void run() + { + Bukkit.getServer().broadcastMessage(this.message); + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/CheckForPortalTrapTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/CheckForPortalTrapTask.java index 324be29..a06490c 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/CheckForPortalTrapTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/CheckForPortalTrapTask.java @@ -15,13 +15,10 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - + package me.ryanhamshire.GriefPrevention; import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; import org.bukkit.entity.Player; import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.scheduler.BukkitRunnable; @@ -31,30 +28,30 @@ import org.bukkit.scheduler.BukkitRunnable; //if that happens, we detect the problem and send them back through the portal. class CheckForPortalTrapTask extends BukkitRunnable { - GriefPrevention instance; - //player who recently teleported via nether portal - private Player player; - - //where to send the player back to if he hasn't left the portal frame - private Location returnLocation; - - public CheckForPortalTrapTask(Player player, GriefPrevention plugin, Location locationToReturn) - { - this.player = player; - this.instance = plugin; - this.returnLocation = locationToReturn; - player.setMetadata("GP_PORTALRESCUE", new FixedMetadataValue(instance, locationToReturn)); - } - - @Override - public void run() - { - if(player.isOnline() && player.getPortalCooldown() >= 10 && player.hasMetadata("GP_PORTALRESCUE")) - { - instance.AddLogEntry("Rescued " + player.getName() + " from a nether portal.\nTeleported from " + player.getLocation().toString() + " to " + returnLocation.toString(), CustomLogEntryTypes.Debug); - player.teleport(returnLocation); - player.removeMetadata("GP_PORTALRESCUE", instance); - } + GriefPrevention instance; + //player who recently teleported via nether portal + private Player player; + + //where to send the player back to if he hasn't left the portal frame + private Location returnLocation; + + public CheckForPortalTrapTask(Player player, GriefPrevention plugin, Location locationToReturn) + { + this.player = player; + this.instance = plugin; + this.returnLocation = locationToReturn; + player.setMetadata("GP_PORTALRESCUE", new FixedMetadataValue(instance, locationToReturn)); + } + + @Override + public void run() + { + if (player.isOnline() && player.getPortalCooldown() >= 10 && player.hasMetadata("GP_PORTALRESCUE")) + { + instance.AddLogEntry("Rescued " + player.getName() + " from a nether portal.\nTeleported from " + player.getLocation().toString() + " to " + returnLocation.toString(), CustomLogEntryTypes.Debug); + player.teleport(returnLocation); + player.removeMetadata("GP_PORTALRESCUE", instance); + } instance.portalReturnTaskMap.remove(player.getUniqueId()); - } + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/Claim.java b/src/main/java/me/ryanhamshire/GriefPrevention/Claim.java index 0d9f323..5d3c92a 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/Claim.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/Claim.java @@ -18,835 +18,851 @@ package me.ryanhamshire.GriefPrevention; -import java.util.*; - -import org.bukkit.*; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; import org.bukkit.World.Environment; import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.UUID; + //represents a player claim //creating an instance doesn't make an effective claim //only claims which have been added to the datastore have any effect public class Claim { - //two locations, which together define the boundaries of the claim - //note that the upper Y value is always ignored, because claims ALWAYS extend up to the sky - Location lesserBoundaryCorner; - Location greaterBoundaryCorner; - - //modification date. this comes from the file timestamp during load, and is updated with runtime changes - public Date modifiedDate; - - //id number. unique to this claim, never changes. - Long id = null; - - //ownerID. for admin claims, this is NULL - //use getOwnerName() to get a friendly name (will be "an administrator" for admin claims) - public UUID ownerID; - - //list of players who (beyond the claim owner) have permission to grant permissions in this claim - public ArrayList managers = new ArrayList(); - - //permissions for this claim, see ClaimPermission class - private HashMap playerIDToClaimPermissionMap = new HashMap(); - - //whether or not this claim is in the data store - //if a claim instance isn't in the data store, it isn't "active" - players can't interract with it - //why keep this? so that claims which have been removed from the data store can be correctly - //ignored even though they may have references floating around - public boolean inDataStore = false; - - public boolean areExplosivesAllowed = false; - - //parent claim - //only used for claim subdivisions. top level claims have null here - public Claim parent = null; - - // intended for subclaims - they inherit no permissions - private boolean inheritNothing = false; + //two locations, which together define the boundaries of the claim + //note that the upper Y value is always ignored, because claims ALWAYS extend up to the sky + Location lesserBoundaryCorner; + Location greaterBoundaryCorner; - //children (subdivisions) - //note subdivisions themselves never have children - public ArrayList children = new ArrayList(); - - //information about a siege involving this claim. null means no siege is impacting this claim - public SiegeData siegeData = null; - - //following a siege, buttons/levers are unlocked temporarily. this represents that state - public boolean doorsOpen = false; - - //whether or not this is an administrative claim - //administrative claims are created and maintained by players with the griefprevention.adminclaims permission. - public boolean isAdminClaim() - { - if(this.parent != null) return this.parent.isAdminClaim(); - - return (this.ownerID == null); - } - - //accessor for ID - public Long getID() - { - return this.id; - } - - //basic constructor, just notes the creation time - //see above declarations for other defaults - Claim() - { - this.modifiedDate = Calendar.getInstance().getTime(); - } - - //players may only siege someone when he's not in an admin claim - //and when he has some level of permission in the claim - public boolean canSiege(Player defender) - { - if(this.isAdminClaim()) return false; - - if(this.allowAccess(defender) != null) return false; - - return true; - } - - //removes any lava above sea level in a claim - //exclusionClaim is another claim indicating an sub-area to be excluded from this operation - //it may be null - public void removeSurfaceFluids(Claim exclusionClaim) - { - //don't do this for administrative claims - if(this.isAdminClaim()) return; - - //don't do it for very large claims - if(this.getArea() > 10000) return; - - //only in creative mode worlds - if(!GriefPrevention.instance.creativeRulesApply(this.lesserBoundaryCorner)) return; - - Location lesser = this.getLesserBoundaryCorner(); - Location greater = this.getGreaterBoundaryCorner(); + //modification date. this comes from the file timestamp during load, and is updated with runtime changes + public Date modifiedDate; - if(lesser.getWorld().getEnvironment() == Environment.NETHER) return; //don't clean up lava in the nether - - int seaLevel = 0; //clean up all fluids in the end - - //respect sea level in normal worlds - if(lesser.getWorld().getEnvironment() == Environment.NORMAL) seaLevel = GriefPrevention.instance.getSeaLevel(lesser.getWorld()); - - for(int x = lesser.getBlockX(); x <= greater.getBlockX(); x++) - { - for(int z = lesser.getBlockZ(); z <= greater.getBlockZ(); z++) - { - for(int y = seaLevel - 1; y <= lesser.getWorld().getMaxHeight(); y++) - { - //dodge the exclusion claim - Block block = lesser.getWorld().getBlockAt(x, y, z); - if(exclusionClaim != null && exclusionClaim.contains(block.getLocation(), true, false)) continue; - - if(block.getType() == Material.LAVA || block.getType() == Material.WATER) - { - block.setType(Material.AIR); - } - } - } - } - } - - //determines whether or not a claim has surface lava - //used to warn players when they abandon their claims about automatic fluid cleanup - boolean hasSurfaceFluids() - { - Location lesser = this.getLesserBoundaryCorner(); - Location greater = this.getGreaterBoundaryCorner(); + //id number. unique to this claim, never changes. + Long id = null; - //don't bother for very large claims, too expensive - if(this.getArea() > 10000) return false; - - int seaLevel = 0; //clean up all fluids in the end - - //respect sea level in normal worlds - if(lesser.getWorld().getEnvironment() == Environment.NORMAL) seaLevel = GriefPrevention.instance.getSeaLevel(lesser.getWorld()); - - 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.WATER || block.getType() == Material.LAVA) - { - return true; - } - } - } - } - - return false; - } - - //main constructor. note that only creating a claim instance does nothing - a claim must be added to the data store to be effective - Claim(Location lesserBoundaryCorner, Location greaterBoundaryCorner, UUID ownerID, List builderIDs, List containerIDs, List accessorIDs, List managerIDs, boolean inheritNothing, Long id) - { - //modification date - this.modifiedDate = Calendar.getInstance().getTime(); - - //id - this.id = id; - - //store corners - this.lesserBoundaryCorner = lesserBoundaryCorner; - this.greaterBoundaryCorner = greaterBoundaryCorner; - - //owner - this.ownerID = ownerID; - - //other permissions - for(String builderID : builderIDs) - { - this.setPermission(builderID, ClaimPermission.Build); - } - - for(String containerID : containerIDs) - { - this.setPermission(containerID, ClaimPermission.Inventory); - } - - for(String accessorID : accessorIDs) - { - this.setPermission(accessorID, ClaimPermission.Access); - } - - for(String managerID : managerIDs) - { - if(managerID != null && !managerID.isEmpty()) - { - this.managers.add(managerID); - } - } + //ownerID. for admin claims, this is NULL + //use getOwnerName() to get a friendly name (will be "an administrator" for admin claims) + public UUID ownerID; - this.inheritNothing = inheritNothing; - } + //list of players who (beyond the claim owner) have permission to grant permissions in this claim + public ArrayList managers = new ArrayList(); - Claim(Location lesserBoundaryCorner, Location greaterBoundaryCorner, UUID ownerID, List builderIDs, List containerIDs, List accessorIDs, List managerIDs, Long id) - { - this(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builderIDs, containerIDs, accessorIDs, managerIDs, false, id); - } - - //measurements. all measurements are in blocks - public int getArea() - { - int claimWidth = this.greaterBoundaryCorner.getBlockX() - this.lesserBoundaryCorner.getBlockX() + 1; - int claimHeight = this.greaterBoundaryCorner.getBlockZ() - this.lesserBoundaryCorner.getBlockZ() + 1; - - return claimWidth * claimHeight; - } - - public int getWidth() - { - return this.greaterBoundaryCorner.getBlockX() - this.lesserBoundaryCorner.getBlockX() + 1; - } - - public int getHeight() - { - return this.greaterBoundaryCorner.getBlockZ() - this.lesserBoundaryCorner.getBlockZ() + 1; - } + //permissions for this claim, see ClaimPermission class + private HashMap playerIDToClaimPermissionMap = new HashMap(); - public boolean getSubclaimRestrictions() - { - return inheritNothing; - } + //whether or not this claim is in the data store + //if a claim instance isn't in the data store, it isn't "active" - players can't interract with it + //why keep this? so that claims which have been removed from the data store can be correctly + //ignored even though they may have references floating around + public boolean inDataStore = false; - public void setSubclaimRestrictions(boolean inheritNothing) - { - this.inheritNothing = inheritNothing; - } + public boolean areExplosivesAllowed = false; + + //parent claim + //only used for claim subdivisions. top level claims have null here + public Claim parent = null; + + // intended for subclaims - they inherit no permissions + private boolean inheritNothing = false; + + //children (subdivisions) + //note subdivisions themselves never have children + public ArrayList children = new ArrayList(); + + //information about a siege involving this claim. null means no siege is impacting this claim + public SiegeData siegeData = null; + + //following a siege, buttons/levers are unlocked temporarily. this represents that state + public boolean doorsOpen = false; + + //whether or not this is an administrative claim + //administrative claims are created and maintained by players with the griefprevention.adminclaims permission. + public boolean isAdminClaim() + { + if (this.parent != null) return this.parent.isAdminClaim(); + + return (this.ownerID == null); + } + + //accessor for ID + public Long getID() + { + return this.id; + } + + //basic constructor, just notes the creation time + //see above declarations for other defaults + Claim() + { + this.modifiedDate = Calendar.getInstance().getTime(); + } + + //players may only siege someone when he's not in an admin claim + //and when he has some level of permission in the claim + public boolean canSiege(Player defender) + { + if (this.isAdminClaim()) return false; + + if (this.allowAccess(defender) != null) return false; + + return true; + } + + //removes any lava above sea level in a claim + //exclusionClaim is another claim indicating an sub-area to be excluded from this operation + //it may be null + public void removeSurfaceFluids(Claim exclusionClaim) + { + //don't do this for administrative claims + if (this.isAdminClaim()) return; + + //don't do it for very large claims + if (this.getArea() > 10000) return; + + //only in creative mode worlds + if (!GriefPrevention.instance.creativeRulesApply(this.lesserBoundaryCorner)) return; + + Location lesser = this.getLesserBoundaryCorner(); + Location greater = this.getGreaterBoundaryCorner(); + + if (lesser.getWorld().getEnvironment() == Environment.NETHER) return; //don't clean up lava in the nether + + int seaLevel = 0; //clean up all fluids in the end + + //respect sea level in normal worlds + if (lesser.getWorld().getEnvironment() == Environment.NORMAL) + seaLevel = GriefPrevention.instance.getSeaLevel(lesser.getWorld()); + + for (int x = lesser.getBlockX(); x <= greater.getBlockX(); x++) + { + for (int z = lesser.getBlockZ(); z <= greater.getBlockZ(); z++) + { + for (int y = seaLevel - 1; y <= lesser.getWorld().getMaxHeight(); y++) + { + //dodge the exclusion claim + Block block = lesser.getWorld().getBlockAt(x, y, z); + if (exclusionClaim != null && exclusionClaim.contains(block.getLocation(), true, false)) continue; + + if (block.getType() == Material.LAVA || block.getType() == Material.WATER) + { + block.setType(Material.AIR); + } + } + } + } + } + + //determines whether or not a claim has surface lava + //used to warn players when they abandon their claims about automatic fluid cleanup + boolean hasSurfaceFluids() + { + Location lesser = this.getLesserBoundaryCorner(); + Location greater = this.getGreaterBoundaryCorner(); + + //don't bother for very large claims, too expensive + if (this.getArea() > 10000) return false; + + int seaLevel = 0; //clean up all fluids in the end + + //respect sea level in normal worlds + if (lesser.getWorld().getEnvironment() == Environment.NORMAL) + seaLevel = GriefPrevention.instance.getSeaLevel(lesser.getWorld()); + + 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.WATER || block.getType() == Material.LAVA) + { + return true; + } + } + } + } + + return false; + } + + //main constructor. note that only creating a claim instance does nothing - a claim must be added to the data store to be effective + Claim(Location lesserBoundaryCorner, Location greaterBoundaryCorner, UUID ownerID, List builderIDs, List containerIDs, List accessorIDs, List managerIDs, boolean inheritNothing, Long id) + { + //modification date + this.modifiedDate = Calendar.getInstance().getTime(); + + //id + this.id = id; + + //store corners + this.lesserBoundaryCorner = lesserBoundaryCorner; + this.greaterBoundaryCorner = greaterBoundaryCorner; + + //owner + this.ownerID = ownerID; + + //other permissions + for (String builderID : builderIDs) + { + this.setPermission(builderID, ClaimPermission.Build); + } + + for (String containerID : containerIDs) + { + this.setPermission(containerID, ClaimPermission.Inventory); + } + + for (String accessorID : accessorIDs) + { + this.setPermission(accessorID, ClaimPermission.Access); + } + + for (String managerID : managerIDs) + { + if (managerID != null && !managerID.isEmpty()) + { + this.managers.add(managerID); + } + } + + this.inheritNothing = inheritNothing; + } + + Claim(Location lesserBoundaryCorner, Location greaterBoundaryCorner, UUID ownerID, List builderIDs, List containerIDs, List accessorIDs, List managerIDs, Long id) + { + this(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builderIDs, containerIDs, accessorIDs, managerIDs, false, id); + } + + //measurements. all measurements are in blocks + public int getArea() + { + int claimWidth = this.greaterBoundaryCorner.getBlockX() - this.lesserBoundaryCorner.getBlockX() + 1; + int claimHeight = this.greaterBoundaryCorner.getBlockZ() - this.lesserBoundaryCorner.getBlockZ() + 1; + + return claimWidth * claimHeight; + } + + public int getWidth() + { + return this.greaterBoundaryCorner.getBlockX() - this.lesserBoundaryCorner.getBlockX() + 1; + } + + public int getHeight() + { + return this.greaterBoundaryCorner.getBlockZ() - this.lesserBoundaryCorner.getBlockZ() + 1; + } + + public boolean getSubclaimRestrictions() + { + return inheritNothing; + } + + public void setSubclaimRestrictions(boolean inheritNothing) + { + this.inheritNothing = inheritNothing; + } + + //distance check for claims, distance in this case is a band around the outside of the claim rather then euclidean distance + public boolean isNear(Location location, int howNear) + { + Claim claim = new Claim + (new Location(this.lesserBoundaryCorner.getWorld(), this.lesserBoundaryCorner.getBlockX() - howNear, this.lesserBoundaryCorner.getBlockY(), this.lesserBoundaryCorner.getBlockZ() - howNear), + new Location(this.greaterBoundaryCorner.getWorld(), this.greaterBoundaryCorner.getBlockX() + howNear, this.greaterBoundaryCorner.getBlockY(), this.greaterBoundaryCorner.getBlockZ() + howNear), + null, new ArrayList(), new ArrayList(), new ArrayList(), new ArrayList(), null); + + return claim.contains(location, false, true); + } + + //permissions. note administrative "public" claims have different rules than other claims + //all of these return NULL when a player has permission, or a String error message when the player doesn't have permission + public String allowEdit(Player player) + { + //if we don't know who's asking, always say no (i've been told some mods can make this happen somehow) + if (player == null) return ""; + + //special cases... + + //admin claims need adminclaims permission only. + if (this.isAdminClaim()) + { + if (player.hasPermission("griefprevention.adminclaims")) return null; + } + + //anyone with deleteclaims permission can modify non-admin claims at any time + else + { + if (player.hasPermission("griefprevention.deleteclaims")) return null; + } + + //no resizing, deleting, and so forth while under siege + if (player.getUniqueId().equals(this.ownerID)) + { + if (this.siegeData != null) + { + return GriefPrevention.instance.dataStore.getMessage(Messages.NoModifyDuringSiege); + } + + //otherwise, owners can do whatever + return null; + } + + //permission inheritance for subdivisions + if (this.parent != null) + { + if (player.getUniqueId().equals(this.parent.ownerID)) + return null; + if (!inheritNothing) + return this.parent.allowEdit(player); + } + + //error message if all else fails + return GriefPrevention.instance.dataStore.getMessage(Messages.OnlyOwnersModifyClaims, this.getOwnerName()); + } + + private List placeableFarmingBlocksList = Arrays.asList( + Material.PUMPKIN_STEM, + Material.WHEAT, + Material.MELON_STEM, + Material.CARROTS, + Material.POTATOES, + Material.NETHER_WART, + Material.BEETROOTS); - //distance check for claims, distance in this case is a band around the outside of the claim rather then euclidean distance - public boolean isNear(Location location, int howNear) - { - Claim claim = new Claim - (new Location(this.lesserBoundaryCorner.getWorld(), this.lesserBoundaryCorner.getBlockX() - howNear, this.lesserBoundaryCorner.getBlockY(), this.lesserBoundaryCorner.getBlockZ() - howNear), - new Location(this.greaterBoundaryCorner.getWorld(), this.greaterBoundaryCorner.getBlockX() + howNear, this.greaterBoundaryCorner.getBlockY(), this.greaterBoundaryCorner.getBlockZ() + howNear), - null, new ArrayList(), new ArrayList(), new ArrayList(), new ArrayList(), null); - - return claim.contains(location, false, true); - } - - //permissions. note administrative "public" claims have different rules than other claims - //all of these return NULL when a player has permission, or a String error message when the player doesn't have permission - public String allowEdit(Player player) - { - //if we don't know who's asking, always say no (i've been told some mods can make this happen somehow) - if(player == null) return ""; - - //special cases... - - //admin claims need adminclaims permission only. - if(this.isAdminClaim()) - { - if(player.hasPermission("griefprevention.adminclaims")) return null; - } - - //anyone with deleteclaims permission can modify non-admin claims at any time - else - { - if(player.hasPermission("griefprevention.deleteclaims")) return null; - } - - //no resizing, deleting, and so forth while under siege - if(player.getUniqueId().equals(this.ownerID)) - { - if(this.siegeData != null) - { - return GriefPrevention.instance.dataStore.getMessage(Messages.NoModifyDuringSiege); - } - - //otherwise, owners can do whatever - return null; - } - - //permission inheritance for subdivisions - if(this.parent != null) - { - if (player.getUniqueId().equals(this.parent.ownerID)) - return null; - if (!inheritNothing) - return this.parent.allowEdit(player); - } - - //error message if all else fails - return GriefPrevention.instance.dataStore.getMessage(Messages.OnlyOwnersModifyClaims, this.getOwnerName()); - } - - private List placeableFarmingBlocksList = Arrays.asList( - Material.PUMPKIN_STEM, - Material.WHEAT, - Material.MELON_STEM, - Material.CARROTS, - Material.POTATOES, - Material.NETHER_WART, - Material.BEETROOTS); - private boolean placeableForFarming(Material material) { return this.placeableFarmingBlocksList.contains(material); } - - //build permission check - public String allowBuild(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 ""; - - //when a player tries to build in a claim, if he's under siege, the siege may extend to include the new claim - GriefPrevention.instance.dataStore.tryExtendSiege(player, this); - - //admin claims can always be modified by admins, no exceptions - if(this.isAdminClaim()) - { - if(player.hasPermission("griefprevention.adminclaims")) return null; - } - - //no building while under siege - if(this.siegeData != null) - { - return GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildUnderSiege, this.siegeData.attacker.getName()); - } - - //no building while in pvp combat - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); - if(playerData.inPvpCombat()) - { - return GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildPvP); - } - - //owners can make changes, or admins with ignore claims mode enabled - if(player.getUniqueId().equals(this.ownerID) || GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()).ignoreClaims) return null; - - //anyone with explicit build permission can make changes - if(this.hasExplicitPermission(player, ClaimPermission.Build)) return null; - //also everyone is a member of the "public", so check for public permission - if(ClaimPermission.Build.isGrantedBy(this.playerIDToClaimPermissionMap.get("public"))) return null; - - //allow for farming with /containertrust permission - if(this.allowContainers(player) == null) - { - //do allow for farming, if player has /containertrust permission - if(this.placeableForFarming(material)) - { - return null; - } - } - - //subdivision permission inheritance - if(this.parent != null) - { - if (player.getUniqueId().equals(this.parent.ownerID)) - return null; - if (!inheritNothing) - return this.parent.allowBuild(player, material); - } - - //failure message for all other cases - String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildPermission, this.getOwnerName()); - if(player.hasPermission("griefprevention.ignoreclaims")) - reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); - - return reason; - } + //build permission check + public String allowBuild(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 ""; - public boolean hasExplicitPermission(UUID uuid, ClaimPermission level) - { - return level.isGrantedBy(this.playerIDToClaimPermissionMap.get(uuid.toString())); - } + //when a player tries to build in a claim, if he's under siege, the siege may extend to include the new claim + GriefPrevention.instance.dataStore.tryExtendSiege(player, this); - public boolean hasExplicitPermission(Player player, ClaimPermission level) - { - // Check explicit ClaimPermission for UUID - if (this.hasExplicitPermission(player.getUniqueId(), level)) return true; + //admin claims can always be modified by admins, no exceptions + if (this.isAdminClaim()) + { + if (player.hasPermission("griefprevention.adminclaims")) return null; + } - // Check permission-based ClaimPermission - for (Map.Entry stringToPermission : this.playerIDToClaimPermissionMap.entrySet()) - { - String node = stringToPermission.getKey(); - // Ensure valid permission format for permissions - [permission.node] - if (node.length() < 3 || node.charAt(0) != '[' || node.charAt(node.length() -1) != ']') - { - continue; - } + //no building while under siege + if (this.siegeData != null) + { + return GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildUnderSiege, this.siegeData.attacker.getName()); + } - // Check if level is high enough and player has node - if (level.isGrantedBy(stringToPermission.getValue()) - && player.hasPermission(node.substring(1, node.length() - 1))) - { - return true; - } - } + //no building while in pvp combat + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); + if (playerData.inPvpCombat()) + { + return GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildPvP); + } - return false; - } - - //break permission check - public String allowBreak(Player player, Material material) - { - //if under siege, some blocks will be breakable - if(this.siegeData != null || this.doorsOpen) - { - boolean breakable = false; - - //search for block type in list of breakable blocks - for(int i = 0; i < GriefPrevention.instance.config_siege_blocks.size(); i++) - { - Material breakableMaterial = GriefPrevention.instance.config_siege_blocks.get(i); - if(breakableMaterial == material) - { - breakable = true; - break; - } - } - - //custom error messages for siege mode - if(!breakable) - { - return GriefPrevention.instance.dataStore.getMessage(Messages.NonSiegeMaterial); - } - else if(player.getUniqueId().equals(this.ownerID)) - { - return GriefPrevention.instance.dataStore.getMessage(Messages.NoOwnerBuildUnderSiege); - } - else - { - return null; - } - } - - //if not under siege, build rules apply - return this.allowBuild(player, material); - } - - //access permission check - public String allowAccess(Player player) - { - //following a siege where the defender lost, the claim will allow everyone access for a time - if(this.doorsOpen) return null; - - //admin claims need adminclaims permission only. - if(this.isAdminClaim()) - { - if(player.hasPermission("griefprevention.adminclaims")) return null; - } - - //claim owner and admins in ignoreclaims mode have access - if(player.getUniqueId().equals(this.ownerID) || GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()).ignoreClaims) return null; - - //look for explicit individual access, inventory, or build permission - if(this.hasExplicitPermission(player, ClaimPermission.Access)) return null; + //owners can make changes, or admins with ignore claims mode enabled + if (player.getUniqueId().equals(this.ownerID) || GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()).ignoreClaims) + return null; - //also check for public permission - if(ClaimPermission.Access.isGrantedBy(this.playerIDToClaimPermissionMap.get("public"))) return null; - - //permission inheritance for subdivisions - if(this.parent != null) - { - if (player.getUniqueId().equals(this.parent.ownerID)) - return null; - if (!inheritNothing) - return this.parent.allowAccess(player); - } - - //catch-all error message for all other cases - String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoAccessPermission, this.getOwnerName()); - if(player.hasPermission("griefprevention.ignoreclaims")) - reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); - return reason; - } - - //inventory permission check - public String allowContainers(Player player) - { - //if we don't know who's asking, always say no (i've been told some mods can make this happen somehow) - if(player == null) return ""; - - //trying to access inventory in a claim may extend an existing siege to include this claim - GriefPrevention.instance.dataStore.tryExtendSiege(player, this); - - //if under siege, nobody accesses containers - if(this.siegeData != null) - { - return GriefPrevention.instance.dataStore.getMessage(Messages.NoContainersSiege, siegeData.attacker.getName()); - } - - //owner and administrators in ignoreclaims mode have access - if(player.getUniqueId().equals(this.ownerID) || GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()).ignoreClaims) return null; - - //admin claims need adminclaims permission only. - if(this.isAdminClaim()) - { - if(player.hasPermission("griefprevention.adminclaims")) return null; - } - - //check for explicit individual container or build permission - if(this.hasExplicitPermission(player, ClaimPermission.Inventory)) return null; - - //check for public container or build permission - if(ClaimPermission.Inventory.isGrantedBy(this.playerIDToClaimPermissionMap.get("public"))) return null; - - //permission inheritance for subdivisions - if(this.parent != null) - { - if (player.getUniqueId().equals(this.parent.ownerID)) - return null; - if (!inheritNothing) - return this.parent.allowContainers(player); - } - - //error message for all other cases - String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoContainersPermission, this.getOwnerName()); - if(player.hasPermission("griefprevention.ignoreclaims")) - reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); - return reason; - } - - //grant permission check, relatively simple - public String allowGrantPermission(Player player) - { - //if we don't know who's asking, always say no (i've been told some mods can make this happen somehow) - if(player == null) return ""; - - //anyone who can modify the claim can do this - if(this.allowEdit(player) == null) return null; - - //anyone who's in the managers (/PermissionTrust) list can do this - for(int i = 0; i < this.managers.size(); i++) - { - String managerID = this.managers.get(i); - if(player.getUniqueId().toString().equals(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) - { - if (player.getUniqueId().equals(this.parent.ownerID)) - return null; - if (!inheritNothing) - return this.parent.allowGrantPermission(player); - } - - //generic error message - String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoPermissionTrust, this.getOwnerName()); - if(player.hasPermission("griefprevention.ignoreclaims")) - reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); - return reason; - } + //anyone with explicit build permission can make changes + if (this.hasExplicitPermission(player, ClaimPermission.Build)) return null; - public ClaimPermission getPermission(String playerID) - { - if (playerID == null || playerID.isEmpty()) - { - return null; - } + //also everyone is a member of the "public", so check for public permission + if (ClaimPermission.Build.isGrantedBy(this.playerIDToClaimPermissionMap.get("public"))) return null; - return this.playerIDToClaimPermissionMap.get(playerID.toLowerCase()); - } - - //grants a permission for a player or the public - public void setPermission(String playerID, ClaimPermission permissionLevel) - { - if (playerID == null || playerID.isEmpty()) - { - return; - } + //allow for farming with /containertrust permission + if (this.allowContainers(player) == null) + { + //do allow for farming, if player has /containertrust permission + if (this.placeableForFarming(material)) + { + return null; + } + } - this.playerIDToClaimPermissionMap.put(playerID.toLowerCase(), permissionLevel); - } - - //revokes a permission for a player or the public - public void dropPermission(String playerID) - { - this.playerIDToClaimPermissionMap.remove(playerID.toLowerCase()); - - for(Claim child : this.children) - { - child.dropPermission(playerID); - } - } - - //clears all permissions (except owner of course) - public void clearPermissions() - { - this.playerIDToClaimPermissionMap.clear(); - this.managers.clear(); - - for(Claim child : this.children) + //subdivision permission inheritance + if (this.parent != null) + { + if (player.getUniqueId().equals(this.parent.ownerID)) + return null; + if (!inheritNothing) + return this.parent.allowBuild(player, material); + } + + //failure message for all other cases + String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildPermission, this.getOwnerName()); + if (player.hasPermission("griefprevention.ignoreclaims")) + reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); + + return reason; + } + + public boolean hasExplicitPermission(UUID uuid, ClaimPermission level) + { + return level.isGrantedBy(this.playerIDToClaimPermissionMap.get(uuid.toString())); + } + + public boolean hasExplicitPermission(Player player, ClaimPermission level) + { + // Check explicit ClaimPermission for UUID + if (this.hasExplicitPermission(player.getUniqueId(), level)) return true; + + // Check permission-based ClaimPermission + for (Map.Entry stringToPermission : this.playerIDToClaimPermissionMap.entrySet()) + { + String node = stringToPermission.getKey(); + // Ensure valid permission format for permissions - [permission.node] + if (node.length() < 3 || node.charAt(0) != '[' || node.charAt(node.length() - 1) != ']') + { + continue; + } + + // Check if level is high enough and player has node + if (level.isGrantedBy(stringToPermission.getValue()) + && player.hasPermission(node.substring(1, node.length() - 1))) + { + return true; + } + } + + return false; + } + + //break permission check + public String allowBreak(Player player, Material material) + { + //if under siege, some blocks will be breakable + if (this.siegeData != null || this.doorsOpen) + { + boolean breakable = false; + + //search for block type in list of breakable blocks + for (int i = 0; i < GriefPrevention.instance.config_siege_blocks.size(); i++) + { + Material breakableMaterial = GriefPrevention.instance.config_siege_blocks.get(i); + if (breakableMaterial == material) + { + breakable = true; + break; + } + } + + //custom error messages for siege mode + if (!breakable) + { + return GriefPrevention.instance.dataStore.getMessage(Messages.NonSiegeMaterial); + } else if (player.getUniqueId().equals(this.ownerID)) + { + return GriefPrevention.instance.dataStore.getMessage(Messages.NoOwnerBuildUnderSiege); + } else + { + return null; + } + } + + //if not under siege, build rules apply + return this.allowBuild(player, material); + } + + //access permission check + public String allowAccess(Player player) + { + //following a siege where the defender lost, the claim will allow everyone access for a time + if (this.doorsOpen) return null; + + //admin claims need adminclaims permission only. + if (this.isAdminClaim()) + { + if (player.hasPermission("griefprevention.adminclaims")) return null; + } + + //claim owner and admins in ignoreclaims mode have access + if (player.getUniqueId().equals(this.ownerID) || GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()).ignoreClaims) + return null; + + //look for explicit individual access, inventory, or build permission + if (this.hasExplicitPermission(player, ClaimPermission.Access)) return null; + + //also check for public permission + if (ClaimPermission.Access.isGrantedBy(this.playerIDToClaimPermissionMap.get("public"))) return null; + + //permission inheritance for subdivisions + if (this.parent != null) + { + if (player.getUniqueId().equals(this.parent.ownerID)) + return null; + if (!inheritNothing) + return this.parent.allowAccess(player); + } + + //catch-all error message for all other cases + String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoAccessPermission, this.getOwnerName()); + if (player.hasPermission("griefprevention.ignoreclaims")) + reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); + return reason; + } + + //inventory permission check + public String allowContainers(Player player) + { + //if we don't know who's asking, always say no (i've been told some mods can make this happen somehow) + if (player == null) return ""; + + //trying to access inventory in a claim may extend an existing siege to include this claim + GriefPrevention.instance.dataStore.tryExtendSiege(player, this); + + //if under siege, nobody accesses containers + if (this.siegeData != null) + { + return GriefPrevention.instance.dataStore.getMessage(Messages.NoContainersSiege, siegeData.attacker.getName()); + } + + //owner and administrators in ignoreclaims mode have access + if (player.getUniqueId().equals(this.ownerID) || GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()).ignoreClaims) + return null; + + //admin claims need adminclaims permission only. + if (this.isAdminClaim()) + { + if (player.hasPermission("griefprevention.adminclaims")) return null; + } + + //check for explicit individual container or build permission + if (this.hasExplicitPermission(player, ClaimPermission.Inventory)) return null; + + //check for public container or build permission + if (ClaimPermission.Inventory.isGrantedBy(this.playerIDToClaimPermissionMap.get("public"))) return null; + + //permission inheritance for subdivisions + if (this.parent != null) + { + if (player.getUniqueId().equals(this.parent.ownerID)) + return null; + if (!inheritNothing) + return this.parent.allowContainers(player); + } + + //error message for all other cases + String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoContainersPermission, this.getOwnerName()); + if (player.hasPermission("griefprevention.ignoreclaims")) + reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); + return reason; + } + + //grant permission check, relatively simple + public String allowGrantPermission(Player player) + { + //if we don't know who's asking, always say no (i've been told some mods can make this happen somehow) + if (player == null) return ""; + + //anyone who can modify the claim can do this + if (this.allowEdit(player) == null) return null; + + //anyone who's in the managers (/PermissionTrust) list can do this + for (int i = 0; i < this.managers.size(); i++) + { + String managerID = this.managers.get(i); + if (player.getUniqueId().toString().equals(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) + { + if (player.getUniqueId().equals(this.parent.ownerID)) + return null; + if (!inheritNothing) + return this.parent.allowGrantPermission(player); + } + + //generic error message + String reason = GriefPrevention.instance.dataStore.getMessage(Messages.NoPermissionTrust, this.getOwnerName()); + if (player.hasPermission("griefprevention.ignoreclaims")) + reason += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); + return reason; + } + + public ClaimPermission getPermission(String playerID) + { + if (playerID == null || playerID.isEmpty()) + { + return null; + } + + return this.playerIDToClaimPermissionMap.get(playerID.toLowerCase()); + } + + //grants a permission for a player or the public + public void setPermission(String playerID, ClaimPermission permissionLevel) + { + if (playerID == null || playerID.isEmpty()) + { + return; + } + + this.playerIDToClaimPermissionMap.put(playerID.toLowerCase(), permissionLevel); + } + + //revokes a permission for a player or the public + public void dropPermission(String playerID) + { + this.playerIDToClaimPermissionMap.remove(playerID.toLowerCase()); + + for (Claim child : this.children) + { + child.dropPermission(playerID); + } + } + + //clears all permissions (except owner of course) + public void clearPermissions() + { + this.playerIDToClaimPermissionMap.clear(); + this.managers.clear(); + + for (Claim child : this.children) { child.clearPermissions(); } - } - - //gets ALL permissions - //useful for making copies of permissions during a claim resize and listing all permissions in a claim - public void getPermissions(ArrayList builders, ArrayList containers, ArrayList accessors, ArrayList managers) - { - //loop through all the entries in the hash map - Iterator> mappingsIterator = this.playerIDToClaimPermissionMap.entrySet().iterator(); - while(mappingsIterator.hasNext()) - { - Map.Entry entry = mappingsIterator.next(); + } - //build up a list for each permission level - if(entry.getValue() == ClaimPermission.Build) - { - builders.add(entry.getKey()); - } - else if(entry.getValue() == ClaimPermission.Inventory) - { - containers.add(entry.getKey()); - } - else - { - accessors.add(entry.getKey()); - } - } - - //managers are handled a little differently - for(int i = 0; i < this.managers.size(); i++) - { - managers.add(this.managers.get(i)); - } - } - - //returns a copy of the location representing lower x, y, z limits - public Location getLesserBoundaryCorner() - { - return this.lesserBoundaryCorner.clone(); - } - - //returns a copy of the location representing upper x, y, z limits - //NOTE: remember upper Y will always be ignored, all claims always extend to the sky - public Location getGreaterBoundaryCorner() - { - return this.greaterBoundaryCorner.clone(); - } - - //returns a friendly owner name (for admin claims, returns "an administrator" as the owner) - public String getOwnerName() - { - if(this.parent != null) - return this.parent.getOwnerName(); - - if(this.ownerID == null) - return GriefPrevention.instance.dataStore.getMessage(Messages.OwnerNameForAdminClaims); - - return GriefPrevention.lookupPlayerName(this.ownerID); - } - - //whether or not a location is in a claim - //ignoreHeight = true means location UNDER the claim will return TRUE - //excludeSubdivisions = true means that locations inside subdivisions of the claim will return FALSE - public boolean contains(Location location, boolean ignoreHeight, boolean excludeSubdivisions) - { - //not in the same world implies false - if(!location.getWorld().equals(this.lesserBoundaryCorner.getWorld())) return false; - - double x = location.getX(); - double y = location.getY(); - double z = location.getZ(); - - //main check - boolean inClaim = (ignoreHeight || y >= this.lesserBoundaryCorner.getY()) && - x >= this.lesserBoundaryCorner.getX() && - x < this.greaterBoundaryCorner.getX() + 1 && - z >= this.lesserBoundaryCorner.getZ() && - z < this.greaterBoundaryCorner.getZ() + 1; - - if(!inClaim) return false; - - //additional check for subdivisions - //you're only in a subdivision when you're also in its parent claim - //NOTE: if a player creates subdivions then resizes the parent claim, it's possible that - //a subdivision can reach outside of its parent's boundaries. so this check is important! - if(this.parent != null) - { - return this.parent.contains(location, ignoreHeight, false); - } - - //code to exclude subdivisions in this check - else if(excludeSubdivisions) - { - //search all subdivisions to see if the location is in any of them - for(int i = 0; i < this.children.size(); i++) - { - //if we find such a subdivision, return false - if(this.children.get(i).contains(location, ignoreHeight, true)) - { - return false; - } - } - } - - //otherwise yes - return true; - } - - //whether or not two claims overlap - //used internally to prevent overlaps when creating claims - boolean overlaps(Claim otherClaim) - { - //NOTE: if trying to understand this makes your head hurt, don't feel bad - it hurts mine too. - //try drawing pictures to visualize test cases. - - if(!this.lesserBoundaryCorner.getWorld().equals(otherClaim.getLesserBoundaryCorner().getWorld())) return false; - - //first, check the corners of this claim aren't inside any existing claims - if(otherClaim.contains(this.lesserBoundaryCorner, true, false)) return true; - if(otherClaim.contains(this.greaterBoundaryCorner, true, false)) return true; - if(otherClaim.contains(new Location(this.lesserBoundaryCorner.getWorld(), this.lesserBoundaryCorner.getBlockX(), 0, this.greaterBoundaryCorner.getBlockZ()), true, false)) return true; - if(otherClaim.contains(new Location(this.lesserBoundaryCorner.getWorld(), this.greaterBoundaryCorner.getBlockX(), 0, this.lesserBoundaryCorner.getBlockZ()), true, false)) return true; - - //verify that no claim's lesser boundary point is inside this new claim, to cover the "existing claim is entirely inside new claim" case - if(this.contains(otherClaim.getLesserBoundaryCorner(), true, false)) return true; - - //verify this claim doesn't band across an existing claim, either horizontally or vertically - if( this.getLesserBoundaryCorner().getBlockZ() <= otherClaim.getGreaterBoundaryCorner().getBlockZ() && - this.getLesserBoundaryCorner().getBlockZ() >= otherClaim.getLesserBoundaryCorner().getBlockZ() && - this.getLesserBoundaryCorner().getBlockX() < otherClaim.getLesserBoundaryCorner().getBlockX() && - this.getGreaterBoundaryCorner().getBlockX() > otherClaim.getGreaterBoundaryCorner().getBlockX() ) - return true; - - if( this.getGreaterBoundaryCorner().getBlockZ() <= otherClaim.getGreaterBoundaryCorner().getBlockZ() && - this.getGreaterBoundaryCorner().getBlockZ() >= otherClaim.getLesserBoundaryCorner().getBlockZ() && - this.getLesserBoundaryCorner().getBlockX() < otherClaim.getLesserBoundaryCorner().getBlockX() && - this.getGreaterBoundaryCorner().getBlockX() > otherClaim.getGreaterBoundaryCorner().getBlockX() ) - return true; - - if( this.getLesserBoundaryCorner().getBlockX() <= otherClaim.getGreaterBoundaryCorner().getBlockX() && - this.getLesserBoundaryCorner().getBlockX() >= otherClaim.getLesserBoundaryCorner().getBlockX() && - this.getLesserBoundaryCorner().getBlockZ() < otherClaim.getLesserBoundaryCorner().getBlockZ() && - this.getGreaterBoundaryCorner().getBlockZ() > otherClaim.getGreaterBoundaryCorner().getBlockZ() ) - return true; - - if( this.getGreaterBoundaryCorner().getBlockX() <= otherClaim.getGreaterBoundaryCorner().getBlockX() && - this.getGreaterBoundaryCorner().getBlockX() >= otherClaim.getLesserBoundaryCorner().getBlockX() && - this.getLesserBoundaryCorner().getBlockZ() < otherClaim.getLesserBoundaryCorner().getBlockZ() && - this.getGreaterBoundaryCorner().getBlockZ() > otherClaim.getGreaterBoundaryCorner().getBlockZ() ) - return true; - - return false; - } - - //whether more entities may be added to a claim - public String allowMoreEntities(boolean remove) - { - if(this.parent != null) return this.parent.allowMoreEntities(remove); - - //this rule only applies to creative mode worlds - if(!GriefPrevention.instance.creativeRulesApply(this.getLesserBoundaryCorner())) return null; - - //admin claims aren't restricted - if(this.isAdminClaim()) return null; - - //don't apply this rule to very large claims - if(this.getArea() > 10000) return null; - - //determine maximum allowable entity count, based on claim size - int maxEntities = this.getArea() / 50; - if(maxEntities == 0) return GriefPrevention.instance.dataStore.getMessage(Messages.ClaimTooSmallForEntities); - - //count current entities (ignoring players) - int totalEntities = 0; - ArrayList chunks = this.getChunks(); - for(Chunk chunk : chunks) - { - Entity [] entities = chunk.getEntities(); - for(int i = 0; i < entities.length; i++) - { - Entity entity = entities[i]; - if(!(entity instanceof Player) && this.contains(entity.getLocation(), false, false)) - { - totalEntities++; - if(remove && totalEntities > maxEntities) entity.remove(); - } - } - } - - if(totalEntities >= maxEntities) return GriefPrevention.instance.dataStore.getMessage(Messages.TooManyEntitiesInClaim); - - return null; - } - - public String allowMoreActiveBlocks() + //gets ALL permissions + //useful for making copies of permissions during a claim resize and listing all permissions in a claim + public void getPermissions(ArrayList builders, ArrayList containers, ArrayList accessors, ArrayList managers) { - if(this.parent != null) return this.parent.allowMoreActiveBlocks(); - - //determine maximum allowable entity count, based on claim size - int maxActives = this.getArea() / 100; - if(maxActives == 0) return GriefPrevention.instance.dataStore.getMessage(Messages.ClaimTooSmallForActiveBlocks); - + //loop through all the entries in the hash map + Iterator> mappingsIterator = this.playerIDToClaimPermissionMap.entrySet().iterator(); + while (mappingsIterator.hasNext()) + { + Map.Entry entry = mappingsIterator.next(); + + //build up a list for each permission level + if (entry.getValue() == ClaimPermission.Build) + { + builders.add(entry.getKey()); + } else if (entry.getValue() == ClaimPermission.Inventory) + { + containers.add(entry.getKey()); + } else + { + accessors.add(entry.getKey()); + } + } + + //managers are handled a little differently + for (int i = 0; i < this.managers.size(); i++) + { + managers.add(this.managers.get(i)); + } + } + + //returns a copy of the location representing lower x, y, z limits + public Location getLesserBoundaryCorner() + { + return this.lesserBoundaryCorner.clone(); + } + + //returns a copy of the location representing upper x, y, z limits + //NOTE: remember upper Y will always be ignored, all claims always extend to the sky + public Location getGreaterBoundaryCorner() + { + return this.greaterBoundaryCorner.clone(); + } + + //returns a friendly owner name (for admin claims, returns "an administrator" as the owner) + public String getOwnerName() + { + if (this.parent != null) + return this.parent.getOwnerName(); + + if (this.ownerID == null) + return GriefPrevention.instance.dataStore.getMessage(Messages.OwnerNameForAdminClaims); + + return GriefPrevention.lookupPlayerName(this.ownerID); + } + + //whether or not a location is in a claim + //ignoreHeight = true means location UNDER the claim will return TRUE + //excludeSubdivisions = true means that locations inside subdivisions of the claim will return FALSE + public boolean contains(Location location, boolean ignoreHeight, boolean excludeSubdivisions) + { + //not in the same world implies false + if (!location.getWorld().equals(this.lesserBoundaryCorner.getWorld())) return false; + + double x = location.getX(); + double y = location.getY(); + double z = location.getZ(); + + //main check + boolean inClaim = (ignoreHeight || y >= this.lesserBoundaryCorner.getY()) && + x >= this.lesserBoundaryCorner.getX() && + x < this.greaterBoundaryCorner.getX() + 1 && + z >= this.lesserBoundaryCorner.getZ() && + z < this.greaterBoundaryCorner.getZ() + 1; + + if (!inClaim) return false; + + //additional check for subdivisions + //you're only in a subdivision when you're also in its parent claim + //NOTE: if a player creates subdivions then resizes the parent claim, it's possible that + //a subdivision can reach outside of its parent's boundaries. so this check is important! + if (this.parent != null) + { + return this.parent.contains(location, ignoreHeight, false); + } + + //code to exclude subdivisions in this check + else if (excludeSubdivisions) + { + //search all subdivisions to see if the location is in any of them + for (int i = 0; i < this.children.size(); i++) + { + //if we find such a subdivision, return false + if (this.children.get(i).contains(location, ignoreHeight, true)) + { + return false; + } + } + } + + //otherwise yes + return true; + } + + //whether or not two claims overlap + //used internally to prevent overlaps when creating claims + boolean overlaps(Claim otherClaim) + { + //NOTE: if trying to understand this makes your head hurt, don't feel bad - it hurts mine too. + //try drawing pictures to visualize test cases. + + if (!this.lesserBoundaryCorner.getWorld().equals(otherClaim.getLesserBoundaryCorner().getWorld())) return false; + + //first, check the corners of this claim aren't inside any existing claims + if (otherClaim.contains(this.lesserBoundaryCorner, true, false)) return true; + if (otherClaim.contains(this.greaterBoundaryCorner, true, false)) return true; + if (otherClaim.contains(new Location(this.lesserBoundaryCorner.getWorld(), this.lesserBoundaryCorner.getBlockX(), 0, this.greaterBoundaryCorner.getBlockZ()), true, false)) + return true; + if (otherClaim.contains(new Location(this.lesserBoundaryCorner.getWorld(), this.greaterBoundaryCorner.getBlockX(), 0, this.lesserBoundaryCorner.getBlockZ()), true, false)) + return true; + + //verify that no claim's lesser boundary point is inside this new claim, to cover the "existing claim is entirely inside new claim" case + if (this.contains(otherClaim.getLesserBoundaryCorner(), true, false)) return true; + + //verify this claim doesn't band across an existing claim, either horizontally or vertically + if (this.getLesserBoundaryCorner().getBlockZ() <= otherClaim.getGreaterBoundaryCorner().getBlockZ() && + this.getLesserBoundaryCorner().getBlockZ() >= otherClaim.getLesserBoundaryCorner().getBlockZ() && + this.getLesserBoundaryCorner().getBlockX() < otherClaim.getLesserBoundaryCorner().getBlockX() && + this.getGreaterBoundaryCorner().getBlockX() > otherClaim.getGreaterBoundaryCorner().getBlockX()) + return true; + + if (this.getGreaterBoundaryCorner().getBlockZ() <= otherClaim.getGreaterBoundaryCorner().getBlockZ() && + this.getGreaterBoundaryCorner().getBlockZ() >= otherClaim.getLesserBoundaryCorner().getBlockZ() && + this.getLesserBoundaryCorner().getBlockX() < otherClaim.getLesserBoundaryCorner().getBlockX() && + this.getGreaterBoundaryCorner().getBlockX() > otherClaim.getGreaterBoundaryCorner().getBlockX()) + return true; + + if (this.getLesserBoundaryCorner().getBlockX() <= otherClaim.getGreaterBoundaryCorner().getBlockX() && + this.getLesserBoundaryCorner().getBlockX() >= otherClaim.getLesserBoundaryCorner().getBlockX() && + this.getLesserBoundaryCorner().getBlockZ() < otherClaim.getLesserBoundaryCorner().getBlockZ() && + this.getGreaterBoundaryCorner().getBlockZ() > otherClaim.getGreaterBoundaryCorner().getBlockZ()) + return true; + + if (this.getGreaterBoundaryCorner().getBlockX() <= otherClaim.getGreaterBoundaryCorner().getBlockX() && + this.getGreaterBoundaryCorner().getBlockX() >= otherClaim.getLesserBoundaryCorner().getBlockX() && + this.getLesserBoundaryCorner().getBlockZ() < otherClaim.getLesserBoundaryCorner().getBlockZ() && + this.getGreaterBoundaryCorner().getBlockZ() > otherClaim.getGreaterBoundaryCorner().getBlockZ()) + return true; + + return false; + } + + //whether more entities may be added to a claim + public String allowMoreEntities(boolean remove) + { + if (this.parent != null) return this.parent.allowMoreEntities(remove); + + //this rule only applies to creative mode worlds + if (!GriefPrevention.instance.creativeRulesApply(this.getLesserBoundaryCorner())) return null; + + //admin claims aren't restricted + if (this.isAdminClaim()) return null; + + //don't apply this rule to very large claims + if (this.getArea() > 10000) return null; + + //determine maximum allowable entity count, based on claim size + int maxEntities = this.getArea() / 50; + if (maxEntities == 0) return GriefPrevention.instance.dataStore.getMessage(Messages.ClaimTooSmallForEntities); + + //count current entities (ignoring players) + int totalEntities = 0; + ArrayList chunks = this.getChunks(); + for (Chunk chunk : chunks) + { + Entity[] entities = chunk.getEntities(); + for (int i = 0; i < entities.length; i++) + { + Entity entity = entities[i]; + if (!(entity instanceof Player) && this.contains(entity.getLocation(), false, false)) + { + totalEntities++; + if (remove && totalEntities > maxEntities) entity.remove(); + } + } + } + + if (totalEntities >= maxEntities) + return GriefPrevention.instance.dataStore.getMessage(Messages.TooManyEntitiesInClaim); + + return null; + } + + public String allowMoreActiveBlocks() + { + if (this.parent != null) return this.parent.allowMoreActiveBlocks(); + + //determine maximum allowable entity count, based on claim size + int maxActives = this.getArea() / 100; + if (maxActives == 0) + return GriefPrevention.instance.dataStore.getMessage(Messages.ClaimTooSmallForActiveBlocks); + //count current actives int totalActives = 0; ArrayList chunks = this.getChunks(); - for(Chunk chunk : chunks) + for (Chunk chunk : chunks) { - BlockState [] actives = chunk.getTileEntities(); - for(int i = 0; i < actives.length; i++) + BlockState[] actives = chunk.getTileEntities(); + for (int i = 0; i < actives.length; i++) { BlockState active = actives[i]; - if(BlockEventHandler.isActiveBlock(active)) + if (BlockEventHandler.isActiveBlock(active)) { - if(this.contains(active.getLocation(), false, false)) + if (this.contains(active.getLocation(), false, false)) { totalActives++; } @@ -854,102 +870,100 @@ public class Claim } } - if(totalActives >= maxActives) return GriefPrevention.instance.dataStore.getMessage(Messages.TooManyActiveBlocksInClaim); - + if (totalActives >= maxActives) + return GriefPrevention.instance.dataStore.getMessage(Messages.TooManyActiveBlocksInClaim); + return null; } - - //implements a strict ordering of claims, used to keep the claims collection sorted for faster searching - boolean greaterThan(Claim otherClaim) - { - Location thisCorner = this.getLesserBoundaryCorner(); - Location otherCorner = otherClaim.getLesserBoundaryCorner(); - - if(thisCorner.getBlockX() > otherCorner.getBlockX()) return true; - - if(thisCorner.getBlockX() < otherCorner.getBlockX()) return false; - - if(thisCorner.getBlockZ() > otherCorner.getBlockZ()) return true; - - if(thisCorner.getBlockZ() < otherCorner.getBlockZ()) return false; - - return thisCorner.getWorld().getName().compareTo(otherCorner.getWorld().getName()) < 0; - } - - @SuppressWarnings("deprecation") + + //implements a strict ordering of claims, used to keep the claims collection sorted for faster searching + boolean greaterThan(Claim otherClaim) + { + Location thisCorner = this.getLesserBoundaryCorner(); + Location otherCorner = otherClaim.getLesserBoundaryCorner(); + + if (thisCorner.getBlockX() > otherCorner.getBlockX()) return true; + + if (thisCorner.getBlockX() < otherCorner.getBlockX()) return false; + + if (thisCorner.getBlockZ() > otherCorner.getBlockZ()) return true; + + if (thisCorner.getBlockZ() < otherCorner.getBlockZ()) return false; + + return thisCorner.getWorld().getName().compareTo(otherCorner.getWorld().getName()) < 0; + } + + @SuppressWarnings("deprecation") long getPlayerInvestmentScore() - { - //decide which blocks will be considered player placed - Location lesserBoundaryCorner = this.getLesserBoundaryCorner(); - ArrayList playerBlocks = RestoreNatureProcessingTask.getPlayerBlocks(lesserBoundaryCorner.getWorld().getEnvironment(), lesserBoundaryCorner.getBlock().getBiome()); - - //scan the claim for player placed blocks - double score = 0; - - boolean creativeMode = GriefPrevention.instance.creativeRulesApply(lesserBoundaryCorner); - - for(int x = this.lesserBoundaryCorner.getBlockX(); x <= this.greaterBoundaryCorner.getBlockX(); x++) - { - for(int z = this.lesserBoundaryCorner.getBlockZ(); z <= this.greaterBoundaryCorner.getBlockZ(); z++) - { - int y = this.lesserBoundaryCorner.getBlockY(); - for(; y < GriefPrevention.instance.getSeaLevel(this.lesserBoundaryCorner.getWorld()) - 5; y++) - { - Block block = this.lesserBoundaryCorner.getWorld().getBlockAt(x, y, z); - if(playerBlocks.contains(block.getType())) - { - if(block.getType() == Material.CHEST && !creativeMode) - { - score += 10; - } - else - { - score += .5; - } - } - } - - for(; y < this.lesserBoundaryCorner.getWorld().getMaxHeight(); y++) - { - Block block = this.lesserBoundaryCorner.getWorld().getBlockAt(x, y, z); - if(playerBlocks.contains(block.getType())) - { - if(block.getType() == Material.CHEST && !creativeMode) - { - score += 10; - } - else if(creativeMode && (block.getType() == Material.LAVA)) - { - score -= 10; - } - else - { - score += 1; - } - } - } - } - } - - return (long)score; - } + { + //decide which blocks will be considered player placed + Location lesserBoundaryCorner = this.getLesserBoundaryCorner(); + ArrayList playerBlocks = RestoreNatureProcessingTask.getPlayerBlocks(lesserBoundaryCorner.getWorld().getEnvironment(), lesserBoundaryCorner.getBlock().getBiome()); + + //scan the claim for player placed blocks + double score = 0; + + boolean creativeMode = GriefPrevention.instance.creativeRulesApply(lesserBoundaryCorner); + + for (int x = this.lesserBoundaryCorner.getBlockX(); x <= this.greaterBoundaryCorner.getBlockX(); x++) + { + for (int z = this.lesserBoundaryCorner.getBlockZ(); z <= this.greaterBoundaryCorner.getBlockZ(); z++) + { + int y = this.lesserBoundaryCorner.getBlockY(); + for (; y < GriefPrevention.instance.getSeaLevel(this.lesserBoundaryCorner.getWorld()) - 5; y++) + { + Block block = this.lesserBoundaryCorner.getWorld().getBlockAt(x, y, z); + if (playerBlocks.contains(block.getType())) + { + if (block.getType() == Material.CHEST && !creativeMode) + { + score += 10; + } else + { + score += .5; + } + } + } + + for (; y < this.lesserBoundaryCorner.getWorld().getMaxHeight(); y++) + { + Block block = this.lesserBoundaryCorner.getWorld().getBlockAt(x, y, z); + if (playerBlocks.contains(block.getType())) + { + if (block.getType() == Material.CHEST && !creativeMode) + { + score += 10; + } else if (creativeMode && (block.getType() == Material.LAVA)) + { + score -= 10; + } else + { + score += 1; + } + } + } + } + } + + return (long) score; + } public ArrayList getChunks() { ArrayList chunks = new ArrayList(); - + World world = this.getLesserBoundaryCorner().getWorld(); Chunk lesserChunk = this.getLesserBoundaryCorner().getChunk(); Chunk greaterChunk = this.getGreaterBoundaryCorner().getChunk(); - - for(int x = lesserChunk.getX(); x <= greaterChunk.getX(); x++) + + for (int x = lesserChunk.getX(); x <= greaterChunk.getX(); x++) { - for(int z = lesserChunk.getZ(); z <= greaterChunk.getZ(); z++) + for (int z = lesserChunk.getZ(); z <= greaterChunk.getZ(); z++) { chunks.add(world.getChunkAt(x, z)); } } - + return chunks; } @@ -958,17 +972,17 @@ public class Claim ArrayList hashes = new ArrayList(); int smallX = this.getLesserBoundaryCorner().getBlockX() >> 4; int smallZ = this.getLesserBoundaryCorner().getBlockZ() >> 4; - int largeX = this.getGreaterBoundaryCorner().getBlockX() >> 4; - int largeZ = this.getGreaterBoundaryCorner().getBlockZ() >> 4; - - for(int x = smallX; x <= largeX; x++) - { - for(int z = smallZ; z <= largeZ; z++) - { - hashes.add(DataStore.getChunkHash(x, z)); - } - } - - return hashes; + int largeX = this.getGreaterBoundaryCorner().getBlockX() >> 4; + int largeZ = this.getGreaterBoundaryCorner().getBlockZ() >> 4; + + for (int x = smallX; x <= largeX; x++) + { + for (int z = smallZ; z <= largeZ; z++) + { + hashes.add(DataStore.getChunkHash(x, z)); + } + } + + return hashes; } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/ClaimPermission.java b/src/main/java/me/ryanhamshire/GriefPrevention/ClaimPermission.java index a24e0be..5b6321e 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/ClaimPermission.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/ClaimPermission.java @@ -15,26 +15,26 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - + package me.ryanhamshire.GriefPrevention; //basic enum stuff -public enum ClaimPermission +public enum ClaimPermission { - Build, - Inventory, - Access; + Build, + Inventory, + Access; - /** - * Check if a ClaimPermission is granted by another ClaimPermission. - * - * @param other the ClaimPermission to compare against - * @return true if this ClaimPermission is equal or lesser than the provided ClaimPermission - */ - public boolean isGrantedBy(ClaimPermission other) - { - // As this uses declaration order to compare, if trust levels are reordered this method must be rewritten. - return other != null && other.ordinal() <= this.ordinal(); - } + /** + * Check if a ClaimPermission is granted by another ClaimPermission. + * + * @param other the ClaimPermission to compare against + * @return true if this ClaimPermission is equal or lesser than the provided ClaimPermission + */ + public boolean isGrantedBy(ClaimPermission other) + { + // As this uses declaration order to compare, if trust levels are reordered this method must be rewritten. + return other != null && other.ordinal() <= this.ordinal(); + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimPreTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimPreTask.java index 27d9360..c363026 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimPreTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimPreTask.java @@ -15,8 +15,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - - package me.ryanhamshire.GriefPrevention; + +package me.ryanhamshire.GriefPrevention; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; @@ -26,40 +26,40 @@ import java.util.UUID; //asynchronously loads player data without caching it in the datastore, then //passes those data to a claim cleanup task which might decide to delete a claim for inactivity -class CleanupUnusedClaimPreTask implements Runnable -{ - private UUID ownerID = null; - - CleanupUnusedClaimPreTask(UUID uuid) - { - this.ownerID = uuid; - } - - @Override - public void run() - { - //get the data - PlayerData ownerData = GriefPrevention.instance.dataStore.getPlayerDataFromStorage(ownerID); - OfflinePlayer ownerInfo = Bukkit.getServer().getOfflinePlayer(ownerID); +class CleanupUnusedClaimPreTask implements Runnable +{ + private UUID ownerID = null; - GriefPrevention.AddLogEntry("Looking for expired claims. Checking data for " + ownerID.toString(), CustomLogEntryTypes.Debug, true); - - //expiration code uses last logout timestamp to decide whether to expire claims - //don't expire claims for online players - if(ownerInfo.isOnline()) - { - GriefPrevention.AddLogEntry("Player is online. Ignoring.", CustomLogEntryTypes.Debug, true); - return; - } - if(ownerInfo.getLastPlayed() <= 0) - { - GriefPrevention.AddLogEntry("Player is new or not in the server's cached userdata. Ignoring. getLastPlayed = " + ownerInfo.getLastPlayed(), CustomLogEntryTypes.Debug, true); - return; - } - - //skip claims belonging to exempted players based on block totals in config - int bonusBlocks = ownerData.getBonusClaimBlocks(); - if(bonusBlocks >= GriefPrevention.instance.config_claims_expirationExemptionBonusBlocks || bonusBlocks + ownerData.getAccruedClaimBlocks() >= GriefPrevention.instance.config_claims_expirationExemptionTotalBlocks) + CleanupUnusedClaimPreTask(UUID uuid) + { + this.ownerID = uuid; + } + + @Override + public void run() + { + //get the data + PlayerData ownerData = GriefPrevention.instance.dataStore.getPlayerDataFromStorage(ownerID); + OfflinePlayer ownerInfo = Bukkit.getServer().getOfflinePlayer(ownerID); + + GriefPrevention.AddLogEntry("Looking for expired claims. Checking data for " + ownerID.toString(), CustomLogEntryTypes.Debug, true); + + //expiration code uses last logout timestamp to decide whether to expire claims + //don't expire claims for online players + if (ownerInfo.isOnline()) + { + GriefPrevention.AddLogEntry("Player is online. Ignoring.", CustomLogEntryTypes.Debug, true); + return; + } + if (ownerInfo.getLastPlayed() <= 0) + { + GriefPrevention.AddLogEntry("Player is new or not in the server's cached userdata. Ignoring. getLastPlayed = " + ownerInfo.getLastPlayed(), CustomLogEntryTypes.Debug, true); + return; + } + + //skip claims belonging to exempted players based on block totals in config + int bonusBlocks = ownerData.getBonusClaimBlocks(); + if (bonusBlocks >= GriefPrevention.instance.config_claims_expirationExemptionBonusBlocks || bonusBlocks + ownerData.getAccruedClaimBlocks() >= GriefPrevention.instance.config_claims_expirationExemptionTotalBlocks) { GriefPrevention.AddLogEntry("Player exempt from claim expiration based on claim block counts vs. config file settings.", CustomLogEntryTypes.Debug, true); return; @@ -67,22 +67,22 @@ class CleanupUnusedClaimPreTask implements Runnable Claim claimToExpire = null; - for (Claim claim : GriefPrevention.instance.dataStore.getClaims()) - { - if (ownerID.equals(claim.ownerID)) - { - claimToExpire = claim; - break; - } - } + for (Claim claim : GriefPrevention.instance.dataStore.getClaims()) + { + if (ownerID.equals(claim.ownerID)) + { + claimToExpire = claim; + break; + } + } - if (claimToExpire == null) - { - GriefPrevention.AddLogEntry("Unable to find a claim to expire for " + ownerID.toString(), CustomLogEntryTypes.Debug, false); - return; - } + if (claimToExpire == null) + { + GriefPrevention.AddLogEntry("Unable to find a claim to expire for " + ownerID.toString(), CustomLogEntryTypes.Debug, false); + return; + } - //pass it back to the main server thread, where it's safe to delete a claim if needed - Bukkit.getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, new CleanupUnusedClaimTask(claimToExpire, ownerData, ownerInfo), 1L); - } + //pass it back to the main server thread, where it's safe to delete a claim if needed + Bukkit.getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, new CleanupUnusedClaimTask(claimToExpire, ownerData, ownerInfo), 1L); + } } \ No newline at end of file diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimTask.java index 502821e..48bb979 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimTask.java @@ -15,134 +15,131 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - - package me.ryanhamshire.GriefPrevention; + +package me.ryanhamshire.GriefPrevention; + +import me.ryanhamshire.GriefPrevention.events.ClaimExpirationEvent; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import java.util.Calendar; import java.util.Date; import java.util.Vector; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; - -import me.ryanhamshire.GriefPrevention.events.ClaimExpirationEvent; - -class CleanupUnusedClaimTask implements Runnable -{ +class CleanupUnusedClaimTask implements Runnable +{ Claim claim; PlayerData ownerData; OfflinePlayer ownerInfo; - - CleanupUnusedClaimTask(Claim claim, PlayerData ownerData, OfflinePlayer ownerInfo) - { - this.claim = claim; - this.ownerData = ownerData; - this.ownerInfo = ownerInfo; - } - - @Override - public void run() - { - - //determine area of the default chest claim - int areaOfDefaultClaim = 0; - if(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius >= 0) - { - areaOfDefaultClaim = (int)Math.pow(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius * 2 + 1, 2); - } - - //if this claim is a chest claim and those are set to expire - if(claim.getArea() <= areaOfDefaultClaim && GriefPrevention.instance.config_claims_chestClaimExpirationDays > 0) - { + CleanupUnusedClaimTask(Claim claim, PlayerData ownerData, OfflinePlayer ownerInfo) + { + this.claim = claim; + this.ownerData = ownerData; + this.ownerInfo = ownerInfo; + } + + @Override + public void run() + { + + + //determine area of the default chest claim + int areaOfDefaultClaim = 0; + if (GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius >= 0) + { + areaOfDefaultClaim = (int) Math.pow(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius * 2 + 1, 2); + } + + //if this claim is a chest claim and those are set to expire + if (claim.getArea() <= areaOfDefaultClaim && GriefPrevention.instance.config_claims_chestClaimExpirationDays > 0) + { //if the owner has been gone at least a week, and if he has ONLY the new player claim, it will be removed - Calendar sevenDaysAgo = Calendar.getInstance(); - sevenDaysAgo.add(Calendar.DATE, -GriefPrevention.instance.config_claims_chestClaimExpirationDays); - boolean newPlayerClaimsExpired = sevenDaysAgo.getTime().after(new Date(ownerInfo.getLastPlayed())); - if(newPlayerClaimsExpired && ownerData.getClaims().size() == 1) - { - if (expireEventCanceled()) - return; - claim.removeSurfaceFluids(null); - GriefPrevention.instance.dataStore.deleteClaim(claim, true, true); - - //if configured to do so, restore the land to natural - if(GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner()) || GriefPrevention.instance.config_claims_survivalAutoNatureRestoration) - { - GriefPrevention.instance.restoreClaim(claim, 0); - } - - GriefPrevention.AddLogEntry(" " + claim.getOwnerName() + "'s new player claim expired.", CustomLogEntryTypes.AdminActivity); - } - } - - //if configured to always remove claims after some inactivity period without exceptions... - else if(GriefPrevention.instance.config_claims_expirationDays > 0) - { - Calendar earliestPermissibleLastLogin = Calendar.getInstance(); - earliestPermissibleLastLogin.add(Calendar.DATE, -GriefPrevention.instance.config_claims_expirationDays); - - if(earliestPermissibleLastLogin.getTime().after(new Date(ownerInfo.getLastPlayed()))) - { - if (expireEventCanceled()) - return; - //make a copy of this player's claim list - Vector claims = new Vector(); - for(int i = 0; i < ownerData.getClaims().size(); i++) - { - claims.add(ownerData.getClaims().get(i)); - } - - //delete them - GriefPrevention.instance.dataStore.deleteClaimsForPlayer(claim.ownerID, true); - GriefPrevention.AddLogEntry(" All of " + claim.getOwnerName() + "'s claims have expired.", CustomLogEntryTypes.AdminActivity); - - for(int i = 0; i < claims.size(); i++) - { - //if configured to do so, restore the land to natural - if(GriefPrevention.instance.creativeRulesApply(claims.get(i).getLesserBoundaryCorner()) || GriefPrevention.instance.config_claims_survivalAutoNatureRestoration) - { - GriefPrevention.instance.restoreClaim(claims.get(i), 0); - } - } - } - } - - else if(GriefPrevention.instance.config_claims_unusedClaimExpirationDays > 0 && GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner())) - { - //avoid scanning large claims and administrative claims - if(claim.isAdminClaim() || claim.getWidth() > 25 || claim.getHeight() > 25) return; - - //otherwise scan the claim content - int minInvestment = 400; - - long investmentScore = claim.getPlayerInvestmentScore(); - - if(investmentScore < minInvestment) - { - //if the owner has been gone at least a week, and if he has ONLY the new player claim, it will be removed - Calendar sevenDaysAgo = Calendar.getInstance(); - sevenDaysAgo.add(Calendar.DATE, -GriefPrevention.instance.config_claims_unusedClaimExpirationDays); - boolean claimExpired = sevenDaysAgo.getTime().after(new Date(ownerInfo.getLastPlayed())); - if(claimExpired) - { - if (expireEventCanceled()) - return; - GriefPrevention.instance.dataStore.deleteClaim(claim, true, true); - GriefPrevention.AddLogEntry("Removed " + claim.getOwnerName() + "'s unused claim @ " + GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner()), CustomLogEntryTypes.AdminActivity); - - //restore the claim area to natural state - GriefPrevention.instance.restoreClaim(claim, 0); - } - } - } - } + Calendar sevenDaysAgo = Calendar.getInstance(); + sevenDaysAgo.add(Calendar.DATE, -GriefPrevention.instance.config_claims_chestClaimExpirationDays); + boolean newPlayerClaimsExpired = sevenDaysAgo.getTime().after(new Date(ownerInfo.getLastPlayed())); + if (newPlayerClaimsExpired && ownerData.getClaims().size() == 1) + { + if (expireEventCanceled()) + return; + claim.removeSurfaceFluids(null); + GriefPrevention.instance.dataStore.deleteClaim(claim, true, true); - public boolean expireEventCanceled() - { - //see if any other plugins don't want this claim deleted - ClaimExpirationEvent event = new ClaimExpirationEvent(this.claim); - Bukkit.getPluginManager().callEvent(event); - return event.isCancelled(); - } + //if configured to do so, restore the land to natural + if (GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner()) || GriefPrevention.instance.config_claims_survivalAutoNatureRestoration) + { + GriefPrevention.instance.restoreClaim(claim, 0); + } + + GriefPrevention.AddLogEntry(" " + claim.getOwnerName() + "'s new player claim expired.", CustomLogEntryTypes.AdminActivity); + } + } + + //if configured to always remove claims after some inactivity period without exceptions... + else if (GriefPrevention.instance.config_claims_expirationDays > 0) + { + Calendar earliestPermissibleLastLogin = Calendar.getInstance(); + earliestPermissibleLastLogin.add(Calendar.DATE, -GriefPrevention.instance.config_claims_expirationDays); + + if (earliestPermissibleLastLogin.getTime().after(new Date(ownerInfo.getLastPlayed()))) + { + if (expireEventCanceled()) + return; + //make a copy of this player's claim list + Vector claims = new Vector(); + for (int i = 0; i < ownerData.getClaims().size(); i++) + { + claims.add(ownerData.getClaims().get(i)); + } + + //delete them + GriefPrevention.instance.dataStore.deleteClaimsForPlayer(claim.ownerID, true); + GriefPrevention.AddLogEntry(" All of " + claim.getOwnerName() + "'s claims have expired.", CustomLogEntryTypes.AdminActivity); + + for (int i = 0; i < claims.size(); i++) + { + //if configured to do so, restore the land to natural + if (GriefPrevention.instance.creativeRulesApply(claims.get(i).getLesserBoundaryCorner()) || GriefPrevention.instance.config_claims_survivalAutoNatureRestoration) + { + GriefPrevention.instance.restoreClaim(claims.get(i), 0); + } + } + } + } else if (GriefPrevention.instance.config_claims_unusedClaimExpirationDays > 0 && GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner())) + { + //avoid scanning large claims and administrative claims + if (claim.isAdminClaim() || claim.getWidth() > 25 || claim.getHeight() > 25) return; + + //otherwise scan the claim content + int minInvestment = 400; + + long investmentScore = claim.getPlayerInvestmentScore(); + + if (investmentScore < minInvestment) + { + //if the owner has been gone at least a week, and if he has ONLY the new player claim, it will be removed + Calendar sevenDaysAgo = Calendar.getInstance(); + sevenDaysAgo.add(Calendar.DATE, -GriefPrevention.instance.config_claims_unusedClaimExpirationDays); + boolean claimExpired = sevenDaysAgo.getTime().after(new Date(ownerInfo.getLastPlayed())); + if (claimExpired) + { + if (expireEventCanceled()) + return; + GriefPrevention.instance.dataStore.deleteClaim(claim, true, true); + GriefPrevention.AddLogEntry("Removed " + claim.getOwnerName() + "'s unused claim @ " + GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner()), CustomLogEntryTypes.AdminActivity); + + //restore the claim area to natural state + GriefPrevention.instance.restoreClaim(claim, 0); + } + } + } + } + + public boolean expireEventCanceled() + { + //see if any other plugins don't want this claim deleted + ClaimExpirationEvent event = new ClaimExpirationEvent(this.claim); + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/CreateClaimResult.java b/src/main/java/me/ryanhamshire/GriefPrevention/CreateClaimResult.java index 847ac93..c9c8913 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/CreateClaimResult.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/CreateClaimResult.java @@ -18,12 +18,12 @@ package me.ryanhamshire.GriefPrevention; -public class CreateClaimResult +public class CreateClaimResult { - //whether or not the creation succeeded (it would fail if the new claim overlapped another existing claim) - public boolean succeeded; - - //when succeeded, this is a reference to the new claim - //when failed, this is a reference to the pre-existing, conflicting claim - public Claim claim; + //whether or not the creation succeeded (it would fail if the new claim overlapped another existing claim) + public boolean succeeded; + + //when succeeded, this is a reference to the new claim + //when failed, this is a reference to the pre-existing, conflicting claim + public Claim claim; } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/CustomLogger.java b/src/main/java/me/ryanhamshire/GriefPrevention/CustomLogger.java index 39c9903..6db2083 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/CustomLogger.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/CustomLogger.java @@ -18,6 +18,9 @@ package me.ryanhamshire.GriefPrevention; +import com.google.common.io.Files; +import org.bukkit.scheduler.BukkitScheduler; + import java.io.File; import java.nio.charset.Charset; import java.text.SimpleDateFormat; @@ -26,32 +29,28 @@ import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.bukkit.scheduler.BukkitScheduler; - -import com.google.common.io.Files; - class CustomLogger { private final SimpleDateFormat timestampFormat = new SimpleDateFormat("HH:mm"); private final SimpleDateFormat filenameFormat = new SimpleDateFormat("yyyy_MM_dd"); private final String logFolderPath = DataStore.dataLayerFolderPath + File.separator + "Logs"; private final int secondsBetweenWrites = 300; - + //stringbuilder is not thread safe, stringbuffer is private StringBuffer queuedEntries = new StringBuffer(); - + CustomLogger() { //ensure log folder exists File logFolder = new File(this.logFolderPath); logFolder.mkdirs(); - + //delete any outdated log files immediately this.DeleteExpiredLogs(); - + //unless disabled, schedule recurring tasks int daysToKeepLogs = GriefPrevention.instance.config_logs_daysToKeep; - if(daysToKeepLogs > 0) + if (daysToKeepLogs > 0) { BukkitScheduler scheduler = GriefPrevention.instance.getServer().getScheduler(); final long ticksPerSecond = 20L; @@ -60,33 +59,38 @@ class CustomLogger scheduler.runTaskTimerAsynchronously(GriefPrevention.instance, new ExpiredLogRemover(), ticksPerDay, ticksPerDay); } } - - private static final Pattern inlineFormatterPattern = Pattern.compile("§."); + + private static final Pattern inlineFormatterPattern = Pattern.compile("§."); + void AddEntry(String entry, CustomLogEntryTypes entryType) { //if disabled, do nothing int daysToKeepLogs = GriefPrevention.instance.config_logs_daysToKeep; - if(daysToKeepLogs == 0) return; - + if (daysToKeepLogs == 0) return; + //if entry type is not enabled, do nothing - if(!this.isEnabledType(entryType)) return; - + if (!this.isEnabledType(entryType)) return; + //otherwise write to the in-memory buffer, after removing formatters Matcher matcher = inlineFormatterPattern.matcher(entry); entry = matcher.replaceAll(""); String timestamp = this.timestampFormat.format(new Date()); this.queuedEntries.append(timestamp + " " + entry + "\n"); } - + private boolean isEnabledType(CustomLogEntryTypes entryType) { - if(entryType == CustomLogEntryTypes.Exception) return true; - if(entryType == CustomLogEntryTypes.SocialActivity && !GriefPrevention.instance.config_logs_socialEnabled) return false; - if(entryType == CustomLogEntryTypes.SuspiciousActivity && !GriefPrevention.instance.config_logs_suspiciousEnabled) return false; - if(entryType == CustomLogEntryTypes.AdminActivity && !GriefPrevention.instance.config_logs_adminEnabled) return false; - if(entryType == CustomLogEntryTypes.Debug && !GriefPrevention.instance.config_logs_debugEnabled) return false; - if(entryType == CustomLogEntryTypes.MutedChat && !GriefPrevention.instance.config_logs_mutedChatEnabled) return false; - + if (entryType == CustomLogEntryTypes.Exception) return true; + if (entryType == CustomLogEntryTypes.SocialActivity && !GriefPrevention.instance.config_logs_socialEnabled) + return false; + if (entryType == CustomLogEntryTypes.SuspiciousActivity && !GriefPrevention.instance.config_logs_suspiciousEnabled) + return false; + if (entryType == CustomLogEntryTypes.AdminActivity && !GriefPrevention.instance.config_logs_adminEnabled) + return false; + if (entryType == CustomLogEntryTypes.Debug && !GriefPrevention.instance.config_logs_debugEnabled) return false; + if (entryType == CustomLogEntryTypes.MutedChat && !GriefPrevention.instance.config_logs_mutedChatEnabled) + return false; + return true; } @@ -95,73 +99,73 @@ class CustomLogger try { //if nothing to write, stop here - if(this.queuedEntries.length() == 0) return; - + if (this.queuedEntries.length() == 0) return; + //determine filename based on date - String filename = this.filenameFormat.format(new Date()) + ".log"; + String filename = this.filenameFormat.format(new Date()) + ".log"; String filepath = this.logFolderPath + File.separator + filename; File logFile = new File(filepath); - + //dump content Files.append(this.queuedEntries.toString(), logFile, Charset.forName("UTF-8")); - + //in case of a failure to write the above due to exception, //the unwritten entries will remain the buffer for the next write to retry this.queuedEntries.setLength(0); } - catch(Exception e) + catch (Exception e) { e.printStackTrace(); } } - + private void DeleteExpiredLogs() { try { //get list of log files File logFolder = new File(this.logFolderPath); - File [] files = logFolder.listFiles(); - + File[] files = logFolder.listFiles(); + //delete any created before x days ago int daysToKeepLogs = GriefPrevention.instance.config_logs_daysToKeep; Calendar expirationBoundary = Calendar.getInstance(); expirationBoundary.add(Calendar.DATE, -daysToKeepLogs); - for(int i = 0; i < files.length; i++) + for (int i = 0; i < files.length; i++) { File file = files[i]; - if(file.isDirectory()) continue; //skip any folders - + if (file.isDirectory()) continue; //skip any folders + String filename = file.getName().replace(".log", ""); - String [] dateParts = filename.split("_"); //format is yyyy_MM_dd - if(dateParts.length != 3) continue; - + String[] dateParts = filename.split("_"); //format is yyyy_MM_dd + if (dateParts.length != 3) continue; + try { int year = Integer.parseInt(dateParts[0]); int month = Integer.parseInt(dateParts[1]) - 1; int day = Integer.parseInt(dateParts[2]); - + Calendar filedate = Calendar.getInstance(); filedate.set(year, month, day); - if(filedate.before(expirationBoundary)) + if (filedate.before(expirationBoundary)) { file.delete(); } } - catch(NumberFormatException e) + catch (NumberFormatException e) { //throw this away - effectively ignoring any files without the correct filename format GriefPrevention.AddLogEntry("Ignoring an unexpected file in the abridged logs folder: " + file.getName(), CustomLogEntryTypes.Debug, true); } } } - catch(Exception e) + catch (Exception e) { e.printStackTrace(); } } - + //transfers the internal buffer to a log file private class EntryWriter implements Runnable { @@ -171,7 +175,7 @@ class CustomLogger WriteEntries(); } } - + private class ExpiredLogRemover implements Runnable { @Override diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/CustomizableMessage.java b/src/main/java/me/ryanhamshire/GriefPrevention/CustomizableMessage.java index 5cb4b00..8d64657 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/CustomizableMessage.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/CustomizableMessage.java @@ -18,16 +18,16 @@ package me.ryanhamshire.GriefPrevention; -public class CustomizableMessage +public class CustomizableMessage { - public Messages id; - public String text; - public String notes; - - public CustomizableMessage(Messages id, String text, String notes) - { - this.id = id; - this.text = text; - this.notes = notes; - } + public Messages id; + public String text; + public String notes; + + public CustomizableMessage(Messages id, String text, String notes) + { + this.id = id; + this.text = text; + this.notes = notes; + } } \ No newline at end of file diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/DataStore.java b/src/main/java/me/ryanhamshire/GriefPrevention/DataStore.java index de75c32..5d2bc72 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/DataStore.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/DataStore.java @@ -60,642 +60,648 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; //singleton class which manages all GriefPrevention data (except for config options) -public abstract class DataStore +public abstract class DataStore { - //in-memory cache for player data - protected ConcurrentHashMap playerNameToPlayerDataMap = new ConcurrentHashMap(); - - //in-memory cache for group (permission-based) data - protected ConcurrentHashMap permissionToBonusBlocksMap = new ConcurrentHashMap(); - - //in-memory cache for claim data - ArrayList claims = new ArrayList(); - ConcurrentHashMap> chunksToClaimsMap = new ConcurrentHashMap>(); - - //in-memory cache for messages - private String [] messages; - - //pattern for unique user identifiers (UUIDs) - protected final static Pattern uuidpattern = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); - - //next claim ID - Long nextClaimID = (long)0; - - //path information, for where stuff stored on disk is well... stored - protected final static String dataLayerFolderPath = "plugins" + File.separator + "GriefPreventionData"; - final static String playerDataFolderPath = dataLayerFolderPath + File.separator + "PlayerData"; + //in-memory cache for player data + protected ConcurrentHashMap playerNameToPlayerDataMap = new ConcurrentHashMap(); + + //in-memory cache for group (permission-based) data + protected ConcurrentHashMap permissionToBonusBlocksMap = new ConcurrentHashMap(); + + //in-memory cache for claim data + ArrayList claims = new ArrayList(); + ConcurrentHashMap> chunksToClaimsMap = new ConcurrentHashMap>(); + + //in-memory cache for messages + private String[] messages; + + //pattern for unique user identifiers (UUIDs) + protected final static Pattern uuidpattern = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); + + //next claim ID + Long nextClaimID = (long) 0; + + //path information, for where stuff stored on disk is well... stored + protected final static String dataLayerFolderPath = "plugins" + File.separator + "GriefPreventionData"; + final static String playerDataFolderPath = dataLayerFolderPath + File.separator + "PlayerData"; final static String configFilePath = dataLayerFolderPath + File.separator + "config.yml"; - final static String messagesFilePath = dataLayerFolderPath + File.separator + "messages.yml"; - final static String softMuteFilePath = dataLayerFolderPath + File.separator + "softMute.txt"; - final static String bannedWordsFilePath = dataLayerFolderPath + File.separator + "bannedWords.txt"; + final static String messagesFilePath = dataLayerFolderPath + File.separator + "messages.yml"; + final static String softMuteFilePath = dataLayerFolderPath + File.separator + "softMute.txt"; + final static String bannedWordsFilePath = dataLayerFolderPath + File.separator + "bannedWords.txt"; //the latest version of the data schema implemented here - protected static final int latestSchemaVersion = 3; - - //reading and writing the schema version to the data store - abstract int getSchemaVersionFromStorage(); + protected static final int latestSchemaVersion = 3; + + //reading and writing the schema version to the data store + abstract int getSchemaVersionFromStorage(); + abstract void updateSchemaVersionInStorage(int versionToSet); - - //current version of the schema of data in secondary storage + + //current version of the schema of data in secondary storage private int currentSchemaVersion = -1; //-1 means not determined yet - + //video links static final String SURVIVAL_VIDEO_URL = "" + ChatColor.DARK_AQUA + ChatColor.UNDERLINE + "bit.ly/mcgpuser" + ChatColor.RESET; static final String CREATIVE_VIDEO_URL = "" + ChatColor.DARK_AQUA + ChatColor.UNDERLINE + "bit.ly/mcgpcrea" + ChatColor.RESET; static final String SUBDIVISION_VIDEO_URL = "" + ChatColor.DARK_AQUA + ChatColor.UNDERLINE + "bit.ly/mcgpsub" + ChatColor.RESET; - + //list of UUIDs which are soft-muted ConcurrentHashMap softMuteMap = new ConcurrentHashMap(); - + //world guard reference, if available private WorldGuardWrapper worldGuard = null; - + protected int getSchemaVersion() { - if(this.currentSchemaVersion >= 0) + if (this.currentSchemaVersion >= 0) { return this.currentSchemaVersion; - } - else + } else { - this.currentSchemaVersion = this.getSchemaVersionFromStorage(); + this.currentSchemaVersion = this.getSchemaVersionFromStorage(); return this.currentSchemaVersion; } } - + protected void setSchemaVersion(int versionToSet) { this.currentSchemaVersion = versionToSet; this.updateSchemaVersionInStorage(versionToSet); } - - //initialization! - void initialize() throws Exception - { - GriefPrevention.AddLogEntry(this.claims.size() + " total claims loaded."); - //RoboMWM: ensure the nextClaimID is greater than any other claim ID. If not, data corruption occurred (out of storage space, usually). - for (Claim claim : this.claims) - { - if (claim.id >= nextClaimID) - { - GriefPrevention.instance.getLogger().severe("nextClaimID was lesser or equal to an already-existing claim ID!\n" + - "This usually happens if you ran out of storage space."); - GriefPrevention.AddLogEntry("Changing nextClaimID from " + nextClaimID + " to " + claim.id, CustomLogEntryTypes.Debug, false); - nextClaimID = claim.id + 1; - } - } - - //ensure data folders exist + //initialization! + void initialize() throws Exception + { + GriefPrevention.AddLogEntry(this.claims.size() + " total claims loaded."); + + //RoboMWM: ensure the nextClaimID is greater than any other claim ID. If not, data corruption occurred (out of storage space, usually). + for (Claim claim : this.claims) + { + if (claim.id >= nextClaimID) + { + GriefPrevention.instance.getLogger().severe("nextClaimID was lesser or equal to an already-existing claim ID!\n" + + "This usually happens if you ran out of storage space."); + GriefPrevention.AddLogEntry("Changing nextClaimID from " + nextClaimID + " to " + claim.id, CustomLogEntryTypes.Debug, false); + nextClaimID = claim.id + 1; + } + } + + //ensure data folders exist File playerDataFolder = new File(playerDataFolderPath); - if(!playerDataFolder.exists()) + if (!playerDataFolder.exists()) { playerDataFolder.mkdirs(); } - - //load up all the messages from messages.yml - this.loadMessages(); - GriefPrevention.AddLogEntry("Customizable messages loaded."); - - //if converting up from an earlier schema version, write all claims back to storage using the latest format - if(this.getSchemaVersion() < latestSchemaVersion) + + //load up all the messages from messages.yml + this.loadMessages(); + GriefPrevention.AddLogEntry("Customizable messages loaded."); + + //if converting up from an earlier schema version, write all claims back to storage using the latest format + if (this.getSchemaVersion() < latestSchemaVersion) { GriefPrevention.AddLogEntry("Please wait. Updating data format."); - - for(Claim claim : this.claims) + + for (Claim claim : this.claims) { this.saveClaim(claim); - - for(Claim subClaim : claim.children) + + for (Claim subClaim : claim.children) { this.saveClaim(subClaim); } } - + //clean up any UUID conversion work - if(UUIDFetcher.lookupCache != null) + if (UUIDFetcher.lookupCache != null) { UUIDFetcher.lookupCache.clear(); UUIDFetcher.correctedNames.clear(); } - + GriefPrevention.AddLogEntry("Update finished."); } - - //load list of soft mutes + + //load list of soft mutes this.loadSoftMutes(); - + //make a note of the data store schema version - this.setSchemaVersion(latestSchemaVersion); - - //try to hook into world guard - try - { - this.worldGuard = new WorldGuardWrapper(); - GriefPrevention.AddLogEntry("Successfully hooked into WorldGuard."); - } - //if failed, world guard compat features will just be disabled. - catch(ClassNotFoundException exception){ } - catch(NoClassDefFoundError exception){ } - } - - private void loadSoftMutes() - { - File softMuteFile = new File(softMuteFilePath); - if(softMuteFile.exists()) + this.setSchemaVersion(latestSchemaVersion); + + //try to hook into world guard + try + { + this.worldGuard = new WorldGuardWrapper(); + GriefPrevention.AddLogEntry("Successfully hooked into WorldGuard."); + } + //if failed, world guard compat features will just be disabled. + catch (ClassNotFoundException exception) { } + catch (NoClassDefFoundError exception) { } + } + + private void loadSoftMutes() + { + File softMuteFile = new File(softMuteFilePath); + if (softMuteFile.exists()) { BufferedReader inStream = null; try { //open the file inStream = new BufferedReader(new FileReader(softMuteFile.getAbsolutePath())); - + //while there are lines left String nextID = inStream.readLine(); - while(nextID != null) - { + while (nextID != null) + { //parse line into a UUID UUID playerID; try { playerID = UUID.fromString(nextID); } - catch(Exception e) + catch (Exception e) { playerID = null; GriefPrevention.AddLogEntry("Failed to parse soft mute entry as a UUID: " + nextID); } - + //push it into the map - if(playerID != null) + if (playerID != null) { this.softMuteMap.put(playerID, true); } - + //move to the next nextID = inStream.readLine(); } } - catch(Exception e) + catch (Exception e) { GriefPrevention.AddLogEntry("Failed to read from the soft mute data file: " + e.toString()); e.printStackTrace(); } - + try { - if(inStream != null) inStream.close(); + if (inStream != null) inStream.close(); } - catch(IOException exception) {} - } + catch (IOException exception) {} + } } - - public List loadBannedWords() + + public List loadBannedWords() { try { - File bannedWordsFile = new File(bannedWordsFilePath); - if(!bannedWordsFile.exists()) + File bannedWordsFile = new File(bannedWordsFilePath); + if (!bannedWordsFile.exists()) { Files.touch(bannedWordsFile); - String defaultWords = - "nigger\nniggers\nniger\nnigga\nnigers\nniggas\n" + - "fag\nfags\nfaggot\nfaggots\nfeggit\nfeggits\nfaggit\nfaggits\n" + - "cunt\ncunts\nwhore\nwhores\nslut\nsluts\n"; + String defaultWords = + "nigger\nniggers\nniger\nnigga\nnigers\nniggas\n" + + "fag\nfags\nfaggot\nfaggots\nfeggit\nfeggits\nfaggit\nfaggits\n" + + "cunt\ncunts\nwhore\nwhores\nslut\nsluts\n"; Files.append(defaultWords, bannedWordsFile, Charset.forName("UTF-8")); } - + return Files.readLines(bannedWordsFile, Charset.forName("UTF-8")); } - catch(Exception e) + catch (Exception e) { GriefPrevention.AddLogEntry("Failed to read from the banned words data file: " + e.toString()); e.printStackTrace(); return new ArrayList(); } } - - //updates soft mute map and data file - boolean toggleSoftMute(UUID playerID) - { - boolean newValue = !this.isSoftMuted(playerID); - - this.softMuteMap.put(playerID, newValue); - this.saveSoftMutes(); - - return newValue; - } - - public boolean isSoftMuted(UUID playerID) - { - Boolean mapEntry = this.softMuteMap.get(playerID); - if(mapEntry == null || mapEntry == Boolean.FALSE) - { - return false; - } - - return true; - } - - private void saveSoftMutes() - { - BufferedWriter outStream = null; - + + //updates soft mute map and data file + boolean toggleSoftMute(UUID playerID) + { + boolean newValue = !this.isSoftMuted(playerID); + + this.softMuteMap.put(playerID, newValue); + this.saveSoftMutes(); + + return newValue; + } + + public boolean isSoftMuted(UUID playerID) + { + Boolean mapEntry = this.softMuteMap.get(playerID); + if (mapEntry == null || mapEntry == Boolean.FALSE) + { + return false; + } + + return true; + } + + private void saveSoftMutes() + { + BufferedWriter outStream = null; + try { //open the file and write the new value File softMuteFile = new File(softMuteFilePath); softMuteFile.createNewFile(); outStream = new BufferedWriter(new FileWriter(softMuteFile)); - - for(Map.Entry entry : softMuteMap.entrySet()) + + for (Map.Entry entry : softMuteMap.entrySet()) { - if(entry.getValue().booleanValue()) + if (entry.getValue().booleanValue()) { outStream.write(entry.getKey().toString()); outStream.newLine(); } } - - } - + + } + //if any problem, log it - catch(Exception e) + catch (Exception e) { GriefPrevention.AddLogEntry("Unexpected exception saving soft mute data: " + e.getMessage()); e.printStackTrace(); } - + //close the file try { - if(outStream != null) outStream.close(); + if (outStream != null) outStream.close(); } - catch(IOException exception) {} - } - + catch (IOException exception) {} + } + //removes cached player data from memory - synchronized void clearCachedPlayerData(UUID playerID) - { - this.playerNameToPlayerDataMap.remove(playerID); - } - - //gets the number of bonus blocks a player has from his permissions - //Bukkit doesn't allow for checking permissions of an offline player. - //this will return 0 when he's offline, and the correct number when online. - synchronized public int getGroupBonusBlocks(UUID playerID) - { - int bonusBlocks = 0; - Set keys = permissionToBonusBlocksMap.keySet(); - Iterator iterator = keys.iterator(); - while(iterator.hasNext()) - { - String groupName = iterator.next(); - Player player = GriefPrevention.instance.getServer().getPlayer(playerID); - if(player != null && 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 - synchronized 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 storage to ensure they don't get lost - this.saveGroupBonusBlocks(groupName, currentValue); - - return currentValue; - } - - abstract void saveGroupBonusBlocks(String groupName, int amount); - - public class NoTransferException extends RuntimeException - { + synchronized void clearCachedPlayerData(UUID playerID) + { + this.playerNameToPlayerDataMap.remove(playerID); + } + + //gets the number of bonus blocks a player has from his permissions + //Bukkit doesn't allow for checking permissions of an offline player. + //this will return 0 when he's offline, and the correct number when online. + synchronized public int getGroupBonusBlocks(UUID playerID) + { + int bonusBlocks = 0; + Set keys = permissionToBonusBlocksMap.keySet(); + Iterator iterator = keys.iterator(); + while (iterator.hasNext()) + { + String groupName = iterator.next(); + Player player = GriefPrevention.instance.getServer().getPlayer(playerID); + if (player != null && 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 + synchronized 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 storage to ensure they don't get lost + this.saveGroupBonusBlocks(groupName, currentValue); + + return currentValue; + } + + abstract void saveGroupBonusBlocks(String groupName, int amount); + + public class NoTransferException extends RuntimeException + { private static final long serialVersionUID = 1L; NoTransferException(String message) - { - super(message); - } - } - synchronized public void changeClaimOwner(Claim claim, UUID newOwnerID) - { - //if it's a subdivision, throw an exception - if(claim.parent != null) - { - throw new NoTransferException("Subdivisions can't be transferred. Only top-level claims may change owners."); - } - - //otherwise update information - - //determine current claim owner - PlayerData ownerData = null; - if(!claim.isAdminClaim()) - { - ownerData = this.getPlayerData(claim.ownerID); - } - - //determine new owner - PlayerData newOwnerData = null; - - if(newOwnerID != null) - { - newOwnerData = this.getPlayerData(newOwnerID); - } - - //transfer - claim.ownerID = newOwnerID; - this.saveClaim(claim); - - //adjust blocks and other records - if(ownerData != null) - { - ownerData.getClaims().remove(claim); - } - - if(newOwnerData != null) - { - newOwnerData.getClaims().add(claim); - } - } + { + super(message); + } + } - //adds a claim to the datastore, making it an effective claim - synchronized void addClaim(Claim newClaim, boolean writeToStorage) - { - //subdivisions are added under their parent, not directly to the hash map for direct search - if(newClaim.parent != null) - { - if(!newClaim.parent.children.contains(newClaim)) - { - newClaim.parent.children.add(newClaim); - } - newClaim.inDataStore = true; - if(writeToStorage) - { - this.saveClaim(newClaim); - } - return; - } - - //add it and mark it as added - this.claims.add(newClaim); - addToChunkClaimMap(newClaim); - - newClaim.inDataStore = true; - - //except for administrative claims (which have no owner), update the owner's playerData with the new claim - if(!newClaim.isAdminClaim() && writeToStorage) - { - PlayerData ownerData = this.getPlayerData(newClaim.ownerID); - ownerData.getClaims().add(newClaim); - } - - //make sure the claim is saved to disk - if(writeToStorage) - { - this.saveClaim(newClaim); - } - } - - private void addToChunkClaimMap(Claim claim){ - ArrayList chunkHashes = claim.getChunkHashes(); - for(Long chunkHash : chunkHashes) - { - ArrayList claimsInChunk = this.chunksToClaimsMap.get(chunkHash); - if(claimsInChunk == null) - { - this.chunksToClaimsMap.put(chunkHash, claimsInChunk = new ArrayList<>()); - } - - claimsInChunk.add(claim); - } - } - - private void removeFromChunkClaimMap(Claim claim) { - ArrayList chunkHashes = claim.getChunkHashes(); - for(Long chunkHash : chunkHashes) + synchronized public void changeClaimOwner(Claim claim, UUID newOwnerID) + { + //if it's a subdivision, throw an exception + if (claim.parent != null) + { + throw new NoTransferException("Subdivisions can't be transferred. Only top-level claims may change owners."); + } + + //otherwise update information + + //determine current claim owner + PlayerData ownerData = null; + if (!claim.isAdminClaim()) + { + ownerData = this.getPlayerData(claim.ownerID); + } + + //determine new owner + PlayerData newOwnerData = null; + + if (newOwnerID != null) + { + newOwnerData = this.getPlayerData(newOwnerID); + } + + //transfer + claim.ownerID = newOwnerID; + this.saveClaim(claim); + + //adjust blocks and other records + if (ownerData != null) + { + ownerData.getClaims().remove(claim); + } + + if (newOwnerData != null) + { + newOwnerData.getClaims().add(claim); + } + } + + //adds a claim to the datastore, making it an effective claim + synchronized void addClaim(Claim newClaim, boolean writeToStorage) + { + //subdivisions are added under their parent, not directly to the hash map for direct search + if (newClaim.parent != null) + { + if (!newClaim.parent.children.contains(newClaim)) + { + newClaim.parent.children.add(newClaim); + } + newClaim.inDataStore = true; + if (writeToStorage) + { + this.saveClaim(newClaim); + } + return; + } + + //add it and mark it as added + this.claims.add(newClaim); + addToChunkClaimMap(newClaim); + + newClaim.inDataStore = true; + + //except for administrative claims (which have no owner), update the owner's playerData with the new claim + if (!newClaim.isAdminClaim() && writeToStorage) + { + PlayerData ownerData = this.getPlayerData(newClaim.ownerID); + ownerData.getClaims().add(newClaim); + } + + //make sure the claim is saved to disk + if (writeToStorage) + { + this.saveClaim(newClaim); + } + } + + private void addToChunkClaimMap(Claim claim) + { + ArrayList chunkHashes = claim.getChunkHashes(); + for (Long chunkHash : chunkHashes) { ArrayList claimsInChunk = this.chunksToClaimsMap.get(chunkHash); - if(claimsInChunk != null) + if (claimsInChunk == null) { - for(Iterator it = claimsInChunk.iterator(); it.hasNext();) + this.chunksToClaimsMap.put(chunkHash, claimsInChunk = new ArrayList<>()); + } + + claimsInChunk.add(claim); + } + } + + private void removeFromChunkClaimMap(Claim claim) + { + ArrayList chunkHashes = claim.getChunkHashes(); + for (Long chunkHash : chunkHashes) + { + ArrayList claimsInChunk = this.chunksToClaimsMap.get(chunkHash); + if (claimsInChunk != null) + { + for (Iterator it = claimsInChunk.iterator(); it.hasNext(); ) { Claim c = it.next(); - if(c.id.equals(claim.id)) + if (c.id.equals(claim.id)) { it.remove(); break; } } - if (claimsInChunk.isEmpty()) { // if nothing's left, remove this chunk's cache + if (claimsInChunk.isEmpty()) + { // if nothing's left, remove this chunk's cache this.chunksToClaimsMap.remove(chunkHash); } } } - } - - //turns a location into a string, useful in data storage - private String locationStringDelimiter = ";"; - String locationToString(Location location) - { - StringBuilder stringBuilder = new StringBuilder(location.getWorld().getName()); - stringBuilder.append(locationStringDelimiter); - stringBuilder.append(location.getBlockX()); - stringBuilder.append(locationStringDelimiter); - stringBuilder.append(location.getBlockY()); - stringBuilder.append(locationStringDelimiter); - stringBuilder.append(location.getBlockZ()); - - return stringBuilder.toString(); - } - - //turns a location string back into a location - Location locationFromString(String string, List validWorlds) throws Exception - { - //split the input string on the space - String [] elements = string.split(locationStringDelimiter); - - //expect four elements - world name, X, Y, and Z, respectively - if(elements.length < 4) - { - throw new Exception("Expected four distinct parts to the location string: \"" + string + "\""); - } - - String worldName = elements[0]; - String xString = elements[1]; - String yString = elements[2]; - String zString = elements[3]; - - //identify world the claim is in - World world = null; - for(World w : validWorlds) - { - if(w.getName().equalsIgnoreCase(worldName)) - { - world = w; - break; - } - } - - if(world == null) - { - throw new Exception("World not found: \"" + worldName + "\""); - } - - //convert those numerical strings to integer values - int x = Integer.parseInt(xString); - int y = Integer.parseInt(yString); - int z = Integer.parseInt(zString); - - return new Location(world, x, y, z); - } + } - //saves any changes to a claim to secondary storage - synchronized public void saveClaim(Claim claim) - { - assignClaimID(claim); - - this.writeClaimToStorage(claim); - } - - private void assignClaimID(Claim claim) { - //ensure a unique identifier for the claim which will be used to name the file on disk - if(claim.id == null || claim.id == -1) - { - claim.id = this.nextClaimID; - this.incrementNextClaimID(); - } - } - - abstract void writeClaimToStorage(Claim claim); - - //increments the claim ID and updates secondary storage to be sure it's saved - abstract void incrementNextClaimID(); - - //retrieves player data from memory or secondary storage, as necessary - //if the player has never been on the server before, this will return a fresh player data with default values - synchronized public PlayerData getPlayerData(UUID playerID) - { - //first, look in memory - PlayerData playerData = this.playerNameToPlayerDataMap.get(playerID); - - //if not there, build a fresh instance with some blanks for what may be in secondary storage - if(playerData == null) - { - playerData = new PlayerData(); - playerData.playerID = playerID; - - //shove that new player data into the hash map cache - this.playerNameToPlayerDataMap.put(playerID, playerData); - } - - return playerData; - } - - abstract PlayerData getPlayerDataFromStorage(UUID playerID); - - //deletes a claim or subdivision + //turns a location into a string, useful in data storage + private String locationStringDelimiter = ";"; + + String locationToString(Location location) + { + StringBuilder stringBuilder = new StringBuilder(location.getWorld().getName()); + stringBuilder.append(locationStringDelimiter); + stringBuilder.append(location.getBlockX()); + stringBuilder.append(locationStringDelimiter); + stringBuilder.append(location.getBlockY()); + stringBuilder.append(locationStringDelimiter); + stringBuilder.append(location.getBlockZ()); + + return stringBuilder.toString(); + } + + //turns a location string back into a location + Location locationFromString(String string, List validWorlds) throws Exception + { + //split the input string on the space + String[] elements = string.split(locationStringDelimiter); + + //expect four elements - world name, X, Y, and Z, respectively + if (elements.length < 4) + { + throw new Exception("Expected four distinct parts to the location string: \"" + string + "\""); + } + + String worldName = elements[0]; + String xString = elements[1]; + String yString = elements[2]; + String zString = elements[3]; + + //identify world the claim is in + World world = null; + for (World w : validWorlds) + { + if (w.getName().equalsIgnoreCase(worldName)) + { + world = w; + break; + } + } + + if (world == null) + { + throw new Exception("World not found: \"" + worldName + "\""); + } + + //convert those numerical strings to integer values + int x = Integer.parseInt(xString); + int y = Integer.parseInt(yString); + int z = Integer.parseInt(zString); + + return new Location(world, x, y, z); + } + + //saves any changes to a claim to secondary storage + synchronized public void saveClaim(Claim claim) + { + assignClaimID(claim); + + this.writeClaimToStorage(claim); + } + + private void assignClaimID(Claim claim) + { + //ensure a unique identifier for the claim which will be used to name the file on disk + if (claim.id == null || claim.id == -1) + { + claim.id = this.nextClaimID; + this.incrementNextClaimID(); + } + } + + abstract void writeClaimToStorage(Claim claim); + + //increments the claim ID and updates secondary storage to be sure it's saved + abstract void incrementNextClaimID(); + + //retrieves player data from memory or secondary storage, as necessary + //if the player has never been on the server before, this will return a fresh player data with default values + synchronized public PlayerData getPlayerData(UUID playerID) + { + //first, look in memory + PlayerData playerData = this.playerNameToPlayerDataMap.get(playerID); + + //if not there, build a fresh instance with some blanks for what may be in secondary storage + if (playerData == null) + { + playerData = new PlayerData(); + playerData.playerID = playerID; + + //shove that new player data into the hash map cache + this.playerNameToPlayerDataMap.put(playerID, playerData); + } + + return playerData; + } + + abstract PlayerData getPlayerDataFromStorage(UUID playerID); + + //deletes a claim or subdivision synchronized public void deleteClaim(Claim claim) { this.deleteClaim(claim, true, false); } - + //deletes a claim or subdivision synchronized public void deleteClaim(Claim claim, boolean releasePets) { this.deleteClaim(claim, true, releasePets); } - - synchronized void deleteClaim(Claim claim, boolean fireEvent, boolean releasePets) - { - //delete any children - for(int j = 1; (j - 1) < claim.children.size(); j++) + + synchronized void deleteClaim(Claim claim, boolean fireEvent, boolean releasePets) + { + //delete any children + for (int j = 1; (j - 1) < claim.children.size(); j++) { - this.deleteClaim(claim.children.get(j-1), true); + this.deleteClaim(claim.children.get(j - 1), true); } - - //subdivisions must also be removed from the parent claim child list - if(claim.parent != null) - { - Claim parentClaim = claim.parent; - parentClaim.children.remove(claim); - } - - //mark as deleted so any references elsewhere can be ignored - claim.inDataStore = false; - - //remove from memory - for(int i = 0; i < this.claims.size(); i++) - { - if(claims.get(i).id.equals(claim.id)) - { - this.claims.remove(i); - break; - } - } - - removeFromChunkClaimMap(claim); - - //remove from secondary storage - this.deleteClaimFromSecondaryStorage(claim); - - //update player data - if(claim.ownerID != null) - { - PlayerData ownerData = this.getPlayerData(claim.ownerID); - for(int i = 0; i < ownerData.getClaims().size(); i++) - { - if(ownerData.getClaims().get(i).id.equals(claim.id)) - { - ownerData.getClaims().remove(i); - break; - } - } - this.savePlayerData(claim.ownerID, ownerData); - } - - if(fireEvent) - { - ClaimDeletedEvent ev = new ClaimDeletedEvent(claim); - Bukkit.getPluginManager().callEvent(ev); - } - - //optionally set any pets free which belong to the claim owner - if(releasePets && claim.ownerID != null && claim.parent == null) + + //subdivisions must also be removed from the parent claim child list + if (claim.parent != null) { - for(Chunk chunk : claim.getChunks()) + Claim parentClaim = claim.parent; + parentClaim.children.remove(claim); + } + + //mark as deleted so any references elsewhere can be ignored + claim.inDataStore = false; + + //remove from memory + for (int i = 0; i < this.claims.size(); i++) + { + if (claims.get(i).id.equals(claim.id)) { - Entity [] entities = chunk.getEntities(); - for(Entity entity : entities) + this.claims.remove(i); + break; + } + } + + removeFromChunkClaimMap(claim); + + //remove from secondary storage + this.deleteClaimFromSecondaryStorage(claim); + + //update player data + if (claim.ownerID != null) + { + PlayerData ownerData = this.getPlayerData(claim.ownerID); + for (int i = 0; i < ownerData.getClaims().size(); i++) + { + if (ownerData.getClaims().get(i).id.equals(claim.id)) { - if(entity instanceof Tameable) + ownerData.getClaims().remove(i); + break; + } + } + this.savePlayerData(claim.ownerID, ownerData); + } + + if (fireEvent) + { + ClaimDeletedEvent ev = new ClaimDeletedEvent(claim); + Bukkit.getPluginManager().callEvent(ev); + } + + //optionally set any pets free which belong to the claim owner + if (releasePets && claim.ownerID != null && claim.parent == null) + { + for (Chunk chunk : claim.getChunks()) + { + Entity[] entities = chunk.getEntities(); + for (Entity entity : entities) + { + if (entity instanceof Tameable) { - Tameable pet = (Tameable)entity; - if(pet.isTamed()) + Tameable pet = (Tameable) entity; + if (pet.isTamed()) { AnimalTamer owner = pet.getOwner(); - if(owner != null) + if (owner != null) { UUID ownerID = owner.getUniqueId(); - if(ownerID != null) + if (ownerID != null) { - if(ownerID.equals(claim.ownerID)) + if (ownerID.equals(claim.ownerID)) { pet.setTamed(false); pet.setOwner(null); - if(pet instanceof InventoryHolder) + if (pet instanceof InventoryHolder) { - InventoryHolder holder = (InventoryHolder)pet; + InventoryHolder holder = (InventoryHolder) pet; holder.getInventory().clear(); } } @@ -706,619 +712,617 @@ public abstract class DataStore } } } - } - - abstract void deleteClaimFromSecondaryStorage(Claim claim); - - //gets the claim at a specific location - //ignoreHeight = TRUE means that a location UNDER an existing claim will return the claim - //cachedClaim can be NULL, but will help performance if you have a reasonable guess about which claim the location is in - synchronized public Claim getClaimAt(Location location, boolean ignoreHeight, Claim cachedClaim) - { - //check cachedClaim guess first. if it's in the datastore and the location is inside it, we're done - if(cachedClaim != null && cachedClaim.inDataStore && cachedClaim.contains(location, ignoreHeight, true)) return cachedClaim; - - //find a top level claim - Long chunkID = getChunkHash(location); - ArrayList claimsInChunk = this.chunksToClaimsMap.get(chunkID); - if(claimsInChunk == null) return null; - - for(Claim claim : claimsInChunk) - { - if(claim.inDataStore && claim.contains(location, ignoreHeight, false)) - { - //when we find a top level claim, if the location is in one of its subdivisions, + } + + abstract void deleteClaimFromSecondaryStorage(Claim claim); + + //gets the claim at a specific location + //ignoreHeight = TRUE means that a location UNDER an existing claim will return the claim + //cachedClaim can be NULL, but will help performance if you have a reasonable guess about which claim the location is in + synchronized public Claim getClaimAt(Location location, boolean ignoreHeight, Claim cachedClaim) + { + //check cachedClaim guess first. if it's in the datastore and the location is inside it, we're done + if (cachedClaim != null && cachedClaim.inDataStore && cachedClaim.contains(location, ignoreHeight, true)) + return cachedClaim; + + //find a top level claim + Long chunkID = getChunkHash(location); + ArrayList claimsInChunk = this.chunksToClaimsMap.get(chunkID); + if (claimsInChunk == null) return null; + + for (Claim claim : claimsInChunk) + { + if (claim.inDataStore && claim.contains(location, ignoreHeight, false)) + { + //when we find a top level claim, if the location is in one of its subdivisions, //return the SUBDIVISION, not the top level claim - for(int j = 0; j < claim.children.size(); j++) + for (int j = 0; j < claim.children.size(); j++) { Claim subdivision = claim.children.get(j); - if(subdivision.inDataStore && subdivision.contains(location, ignoreHeight, false)) return subdivision; - } - + if (subdivision.inDataStore && subdivision.contains(location, ignoreHeight, false)) + return subdivision; + } + return claim; - } - } - - //if no claim found, return null - return null; - } - - //finds a claim by ID - public synchronized Claim getClaim(long id) - { - for(Claim claim : this.claims) - { - if(claim.inDataStore && claim.getID() == id) return claim; - } - - return null; - } - - //returns a read-only access point for the list of all land claims - //if you need to make changes, use provided methods like .deleteClaim() and .createClaim(). - //this will ensure primary memory (RAM) and secondary memory (disk, database) stay in sync - public Collection getClaims() - { - return Collections.unmodifiableCollection(this.claims); - } - - public Collection getClaims(int chunkx, int chunkz) - { - ArrayList chunkClaims = this.chunksToClaimsMap.get(getChunkHash(chunkx, chunkz)); - if(chunkClaims != null) - { - return Collections.unmodifiableCollection(chunkClaims); - } - else - { - return Collections.unmodifiableCollection(new ArrayList()); - } - } - - //gets an almost-unique, persistent identifier for a chunk + } + } + + //if no claim found, return null + return null; + } + + //finds a claim by ID + public synchronized Claim getClaim(long id) + { + for (Claim claim : this.claims) + { + if (claim.inDataStore && claim.getID() == id) return claim; + } + + return null; + } + + //returns a read-only access point for the list of all land claims + //if you need to make changes, use provided methods like .deleteClaim() and .createClaim(). + //this will ensure primary memory (RAM) and secondary memory (disk, database) stay in sync + public Collection getClaims() + { + return Collections.unmodifiableCollection(this.claims); + } + + public Collection getClaims(int chunkx, int chunkz) + { + ArrayList chunkClaims = this.chunksToClaimsMap.get(getChunkHash(chunkx, chunkz)); + if (chunkClaims != null) + { + return Collections.unmodifiableCollection(chunkClaims); + } else + { + return Collections.unmodifiableCollection(new ArrayList()); + } + } + + //gets an almost-unique, persistent identifier for a chunk static Long getChunkHash(long chunkx, long chunkz) { return (chunkz ^ (chunkx << 32)); } - - //gets an almost-unique, persistent identifier for a chunk - static Long getChunkHash(Location location) - { + + //gets an almost-unique, persistent identifier for a chunk + static Long getChunkHash(Location location) + { return getChunkHash(location.getBlockX() >> 4, location.getBlockZ() >> 4); } /* - * Creates a claim and flags it as being new....throwing a create claim event; + * Creates a claim and flags it as being new....throwing a create claim event; */ synchronized public CreateClaimResult createClaim(World world, int x1, int x2, int y1, int y2, int z1, int z2, UUID ownerID, Claim parent, Long id, Player creatingPlayer) { - return createClaim(world,x1,x2,y1,y2,z1,z2,ownerID,parent,id,creatingPlayer,false); + return createClaim(world, x1, x2, y1, y2, z1, z2, ownerID, parent, id, creatingPlayer, false); } + //creates a claim. - //if the new claim would overlap an existing claim, returns a failure along with a reference to the existing claim - //if the new claim would overlap a WorldGuard region where the player doesn't have permission to build, returns a failure with NULL for claim - //otherwise, returns a success along with a reference to the new claim - //use ownerName == "" for administrative claims - //for top level claims, pass parent == NULL - //DOES adjust claim blocks available on success (players can go into negative quantity available) - //DOES check for world guard regions where the player doesn't have permission - //does NOT check a player has permission to create a claim, or enough claim blocks. - //does NOT check minimum claim size constraints - //does NOT visualize the new claim for any players - synchronized public CreateClaimResult createClaim(World world, int x1, int x2, int y1, int y2, int z1, int z2, UUID ownerID, Claim parent, Long id, Player creatingPlayer, boolean dryRun) - { - CreateClaimResult result = new CreateClaimResult(); - - int smallx, bigx, smally, bigy, smallz, bigz; - - if(y1 < GriefPrevention.instance.config_claims_maxDepth) y1 = GriefPrevention.instance.config_claims_maxDepth; - if(y2 < GriefPrevention.instance.config_claims_maxDepth) y2 = GriefPrevention.instance.config_claims_maxDepth; + //if the new claim would overlap an existing claim, returns a failure along with a reference to the existing claim + //if the new claim would overlap a WorldGuard region where the player doesn't have permission to build, returns a failure with NULL for claim + //otherwise, returns a success along with a reference to the new claim + //use ownerName == "" for administrative claims + //for top level claims, pass parent == NULL + //DOES adjust claim blocks available on success (players can go into negative quantity available) + //DOES check for world guard regions where the player doesn't have permission + //does NOT check a player has permission to create a claim, or enough claim blocks. + //does NOT check minimum claim size constraints + //does NOT visualize the new claim for any players + synchronized public CreateClaimResult createClaim(World world, int x1, int x2, int y1, int y2, int z1, int z2, UUID ownerID, Claim parent, Long id, Player creatingPlayer, boolean dryRun) + { + CreateClaimResult result = new CreateClaimResult(); - //determine small versus big inputs - if(x1 < x2) - { - smallx = x1; - bigx = x2; - } - else - { - smallx = x2; - bigx = x1; - } - - if(y1 < y2) - { - smally = y1; - bigy = y2; - } - else - { - smally = y2; - bigy = y1; - } - - if(z1 < z2) - { - smallz = z1; - bigz = z2; - } - else - { - smallz = z2; - bigz = z1; - } + int smallx, bigx, smally, bigy, smallz, bigz; - if(parent != null) - { - Location lesser = parent.getLesserBoundaryCorner(); - Location greater = parent.getGreaterBoundaryCorner(); - if(smallx < lesser.getX() || smallz < lesser.getZ() || bigx > greater.getX() || bigz > greater.getZ()) - { - result.succeeded = false; - result.claim = parent; - return result; - } - } - - //creative mode claims always go to bedrock - if(GriefPrevention.instance.config_claims_worldModes.get(world) == ClaimsMode.Creative) - { - smally = 0; - } - - //create a new claim instance (but don't save it, yet) - Claim newClaim = new Claim( - new Location(world, smallx, smally, smallz), - new Location(world, bigx, bigy, bigz), - ownerID, - new ArrayList(), - new ArrayList(), - new ArrayList(), - new ArrayList(), - id); - - newClaim.parent = parent; - - //ensure this new claim won't overlap any existing claims - ArrayList claimsToCheck; - if(newClaim.parent != null) - { - claimsToCheck = newClaim.parent.children; - } - else - { - claimsToCheck = this.claims; - } + if (y1 < GriefPrevention.instance.config_claims_maxDepth) y1 = GriefPrevention.instance.config_claims_maxDepth; + if (y2 < GriefPrevention.instance.config_claims_maxDepth) y2 = GriefPrevention.instance.config_claims_maxDepth; - for(int i = 0; i < claimsToCheck.size(); i++) - { - Claim otherClaim = claimsToCheck.get(i); - - //if we find an existing claim which will be overlapped - if(otherClaim.id != newClaim.id && otherClaim.inDataStore && otherClaim.overlaps(newClaim)) - { - //result = fail, return conflicting claim - result.succeeded = false; - result.claim = otherClaim; - return result; - } - } - - //if worldguard is installed, also prevent claims from overlapping any worldguard regions - if(GriefPrevention.instance.config_claims_respectWorldGuard && this.worldGuard != null && creatingPlayer != null) - { - if(!this.worldGuard.canBuild(newClaim.lesserBoundaryCorner, newClaim.greaterBoundaryCorner, creatingPlayer)) - { + //determine small versus big inputs + if (x1 < x2) + { + smallx = x1; + bigx = x2; + } else + { + smallx = x2; + bigx = x1; + } + + if (y1 < y2) + { + smally = y1; + bigy = y2; + } else + { + smally = y2; + bigy = y1; + } + + if (z1 < z2) + { + smallz = z1; + bigz = z2; + } else + { + smallz = z2; + bigz = z1; + } + + if (parent != null) + { + Location lesser = parent.getLesserBoundaryCorner(); + Location greater = parent.getGreaterBoundaryCorner(); + if (smallx < lesser.getX() || smallz < lesser.getZ() || bigx > greater.getX() || bigz > greater.getZ()) + { + result.succeeded = false; + result.claim = parent; + return result; + } + } + + //creative mode claims always go to bedrock + if (GriefPrevention.instance.config_claims_worldModes.get(world) == ClaimsMode.Creative) + { + smally = 0; + } + + //create a new claim instance (but don't save it, yet) + Claim newClaim = new Claim( + new Location(world, smallx, smally, smallz), + new Location(world, bigx, bigy, bigz), + ownerID, + new ArrayList(), + new ArrayList(), + new ArrayList(), + new ArrayList(), + id); + + newClaim.parent = parent; + + //ensure this new claim won't overlap any existing claims + ArrayList claimsToCheck; + if (newClaim.parent != null) + { + claimsToCheck = newClaim.parent.children; + } else + { + claimsToCheck = this.claims; + } + + for (int i = 0; i < claimsToCheck.size(); i++) + { + Claim otherClaim = claimsToCheck.get(i); + + //if we find an existing claim which will be overlapped + if (otherClaim.id != newClaim.id && otherClaim.inDataStore && otherClaim.overlaps(newClaim)) + { + //result = fail, return conflicting claim + result.succeeded = false; + result.claim = otherClaim; + return result; + } + } + + //if worldguard is installed, also prevent claims from overlapping any worldguard regions + if (GriefPrevention.instance.config_claims_respectWorldGuard && this.worldGuard != null && creatingPlayer != null) + { + if (!this.worldGuard.canBuild(newClaim.lesserBoundaryCorner, newClaim.greaterBoundaryCorner, creatingPlayer)) + { result.succeeded = false; result.claim = null; return result; } } - if (dryRun) { + if (dryRun) + { // since this is a dry run, just return the unsaved claim as is. result.succeeded = true; result.claim = newClaim; return result; } assignClaimID(newClaim); // assign a claim ID before calling event, in case a plugin wants to know the ID. - ClaimCreatedEvent event = new ClaimCreatedEvent(newClaim, creatingPlayer); - Bukkit.getPluginManager().callEvent(event); - if (event.isCancelled()) { - result.succeeded = false; - result.claim = null; - return result; + ClaimCreatedEvent event = new ClaimCreatedEvent(newClaim, creatingPlayer); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) + { + result.succeeded = false; + result.claim = null; + return result; - } - //otherwise add this new claim to the data store to make it effective - this.addClaim(newClaim, true); - - //then return success along with reference to new claim - result.succeeded = true; - result.claim = newClaim; - return result; - } - - //saves changes to player data to secondary storage. MUST be called after you're done making changes, otherwise a reload will lose them + } + //otherwise add this new claim to the data store to make it effective + this.addClaim(newClaim, true); + + //then return success along with reference to new claim + result.succeeded = true; + result.claim = newClaim; + return result; + } + + //saves changes to player data to secondary storage. MUST be called after you're done making changes, otherwise a reload will lose them public void savePlayerDataSync(UUID playerID, PlayerData playerData) { //ensure player data is already read from file before trying to save playerData.getAccruedClaimBlocks(); playerData.getClaims(); - + this.asyncSavePlayerData(playerID, playerData); } - - //saves changes to player data to secondary storage. MUST be called after you're done making changes, otherwise a reload will lose them - public void savePlayerData(UUID playerID, PlayerData playerData) - { - new SavePlayerDataThread(playerID, playerData).start(); - } - - public void asyncSavePlayerData(UUID playerID, PlayerData playerData) - { - //save everything except the ignore list - this.overrideSavePlayerData(playerID, playerData); - - //save the ignore list - if(playerData.ignoreListChanged) - { - StringBuilder fileContent = new StringBuilder(); + + //saves changes to player data to secondary storage. MUST be called after you're done making changes, otherwise a reload will lose them + public void savePlayerData(UUID playerID, PlayerData playerData) + { + new SavePlayerDataThread(playerID, playerData).start(); + } + + public void asyncSavePlayerData(UUID playerID, PlayerData playerData) + { + //save everything except the ignore list + this.overrideSavePlayerData(playerID, playerData); + + //save the ignore list + if (playerData.ignoreListChanged) + { + StringBuilder fileContent = new StringBuilder(); try { - for(UUID uuidKey : playerData.ignoredPlayers.keySet()) + for (UUID uuidKey : playerData.ignoredPlayers.keySet()) { Boolean value = playerData.ignoredPlayers.get(uuidKey); - if(value == null) continue; - + if (value == null) continue; + //admin-enforced ignores begin with an asterisk - if(value) + if (value) { fileContent.append("*"); } - + fileContent.append(uuidKey); fileContent.append("\n"); } - + //write data to file File playerDataFile = new File(playerDataFolderPath + File.separator + playerID + ".ignore"); Files.write(fileContent.toString().trim().getBytes("UTF-8"), playerDataFile); - } - + } + //if any problem, log it - catch(Exception e) + catch (Exception e) { GriefPrevention.AddLogEntry("GriefPrevention: Unexpected exception saving data for player \"" + playerID.toString() + "\": " + e.getMessage()); e.printStackTrace(); } - } - } - - abstract void overrideSavePlayerData(UUID playerID, PlayerData playerData); - - //extends a claim to a new depth - //respects the max depth config variable - synchronized public void extendClaim(Claim claim, int newDepth) - { - if(newDepth < GriefPrevention.instance.config_claims_maxDepth) newDepth = GriefPrevention.instance.config_claims_maxDepth; - - if(claim.parent != null) claim = claim.parent; - - //adjust to new depth - claim.lesserBoundaryCorner.setY(newDepth); - claim.greaterBoundaryCorner.setY(newDepth); - for(Claim subdivision : claim.children) - { - subdivision.lesserBoundaryCorner.setY(newDepth); + } + } + + abstract void overrideSavePlayerData(UUID playerID, PlayerData playerData); + + //extends a claim to a new depth + //respects the max depth config variable + synchronized public void extendClaim(Claim claim, int newDepth) + { + if (newDepth < GriefPrevention.instance.config_claims_maxDepth) + newDepth = GriefPrevention.instance.config_claims_maxDepth; + + if (claim.parent != null) claim = claim.parent; + + //adjust to new depth + claim.lesserBoundaryCorner.setY(newDepth); + claim.greaterBoundaryCorner.setY(newDepth); + for (Claim subdivision : claim.children) + { + subdivision.lesserBoundaryCorner.setY(newDepth); subdivision.greaterBoundaryCorner.setY(newDepth); - this.saveClaim(subdivision); - } - - //save changes - this.saveClaim(claim); - ClaimModifiedEvent event = new ClaimModifiedEvent(claim,null); + this.saveClaim(subdivision); + } + + //save changes + this.saveClaim(claim); + ClaimModifiedEvent event = new ClaimModifiedEvent(claim, null); Bukkit.getPluginManager().callEvent(event); - } + } - //starts a siege on a claim - //does NOT check siege cooldowns, see onCooldown() below - synchronized public void startSiege(Player attacker, Player defender, Claim defenderClaim) - { - //fill-in the necessary SiegeData instance - SiegeData siegeData = new SiegeData(attacker, defender, defenderClaim); - PlayerData attackerData = this.getPlayerData(attacker.getUniqueId()); - PlayerData defenderData = this.getPlayerData(defender.getUniqueId()); - attackerData.siegeData = siegeData; - defenderData.siegeData = siegeData; - defenderClaim.siegeData = siegeData; - - //start a task to monitor the siege - //why isn't this a "repeating" task? - //because depending on the status of the siege at the time the task runs, there may or may not be a reason to run the task again - SiegeCheckupTask task = new SiegeCheckupTask(siegeData); - siegeData.checkupTaskID = GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 20L * 30); - } - - //ends a siege - //either winnerName or loserName can be null, but not both - synchronized public void endSiege(SiegeData siegeData, String winnerName, String loserName, List drops) - { - boolean grantAccess = false; - - //determine winner and loser - if(winnerName == null && loserName != null) - { - if(siegeData.attacker.getName().equals(loserName)) - { - winnerName = siegeData.defender.getName(); - } - else - { - winnerName = siegeData.attacker.getName(); - } - } - else if(winnerName != null && loserName == null) - { - if(siegeData.attacker.getName().equals(winnerName)) - { - loserName = siegeData.defender.getName(); - } - else - { - loserName = siegeData.attacker.getName(); - } - } - - //if the attacker won, plan to open the doors for looting - if(siegeData.attacker.getName().equals(winnerName)) - { - grantAccess = true; - } - - PlayerData attackerData = this.getPlayerData(siegeData.attacker.getUniqueId()); - attackerData.siegeData = null; - - PlayerData defenderData = this.getPlayerData(siegeData.defender.getUniqueId()); - defenderData.siegeData = null; - defenderData.lastSiegeEndTimeStamp = System.currentTimeMillis(); - - //start a cooldown for this attacker/defender pair - Long now = Calendar.getInstance().getTimeInMillis(); - Long cooldownEnd = now + 1000 * 60 * GriefPrevention.instance.config_siege_cooldownEndInMinutes; //one hour from now - this.siegeCooldownRemaining.put(siegeData.attacker.getName() + "_" + siegeData.defender.getName(), cooldownEnd); - - //start cooldowns for every attacker/involved claim pair - for(int i = 0; i < siegeData.claims.size(); i++) - { - Claim claim = siegeData.claims.get(i); - claim.siegeData = null; - this.siegeCooldownRemaining.put(siegeData.attacker.getName() + "_" + claim.getOwnerName(), cooldownEnd); - - //if doors should be opened for looting, do that now - if(grantAccess) - { - claim.doorsOpen = true; - } - } - - //cancel the siege checkup task - GriefPrevention.instance.getServer().getScheduler().cancelTask(siegeData.checkupTaskID); - - //notify everyone who won and lost - if(winnerName != null && loserName != null) - { - GriefPrevention.instance.getServer().broadcastMessage(winnerName + " defeated " + loserName + " in siege warfare!"); - } - - //if the claim should be opened to looting - if(grantAccess) - { - @SuppressWarnings("deprecation") - Player winner = GriefPrevention.instance.getServer().getPlayer(winnerName); - if(winner != null) - { - //notify the winner - GriefPrevention.sendMessage(winner, TextMode.Success, Messages.SiegeWinDoorsOpen); - - //schedule a task to secure the claims in about 5 minutes - SecureClaimTask task = new SecureClaimTask(siegeData); - - GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask( - GriefPrevention.instance, task, 20L * GriefPrevention.instance.config_siege_doorsOpenSeconds - ); - } - } - - //if the siege ended due to death, transfer inventory to winner - if(drops != null) - { - @SuppressWarnings("deprecation") - Player winner = GriefPrevention.instance.getServer().getPlayer(winnerName); - @SuppressWarnings("deprecation") - Player loser = GriefPrevention.instance.getServer().getPlayer(loserName); - if(winner != null && loser != null) - { - //try to add any drops to the winner's inventory - for(ItemStack stack : drops) - { - if(stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; - - HashMap wontFitItems = winner.getInventory().addItem(stack); - - //drop any remainder on the ground at his feet - Object [] keys = wontFitItems.keySet().toArray(); - Location winnerLocation = winner.getLocation(); - for(int i = 0; i < keys.length; i++) - { - Integer key = (Integer)keys[i]; - winnerLocation.getWorld().dropItemNaturally(winnerLocation, wontFitItems.get(key)); - } - } - - drops.clear(); - } - } - } - - //timestamp for each siege cooldown to end - private HashMap siegeCooldownRemaining = new HashMap(); - - //whether or not a sieger can siege a particular victim or claim, considering only cooldowns - synchronized public boolean onCooldown(Player attacker, Player defender, Claim defenderClaim) - { - Long cooldownEnd = null; - - //look for an attacker/defender cooldown - if(this.siegeCooldownRemaining.get(attacker.getName() + "_" + defender.getName()) != null) - { - cooldownEnd = this.siegeCooldownRemaining.get(attacker.getName() + "_" + defender.getName()); - - if(Calendar.getInstance().getTimeInMillis() < cooldownEnd) - { - return true; - } - - //if found but expired, remove it - this.siegeCooldownRemaining.remove(attacker.getName() + "_" + defender.getName()); - } - - //look for genderal defender cooldown + //starts a siege on a claim + //does NOT check siege cooldowns, see onCooldown() below + synchronized public void startSiege(Player attacker, Player defender, Claim defenderClaim) + { + //fill-in the necessary SiegeData instance + SiegeData siegeData = new SiegeData(attacker, defender, defenderClaim); + PlayerData attackerData = this.getPlayerData(attacker.getUniqueId()); PlayerData defenderData = this.getPlayerData(defender.getUniqueId()); - if(defenderData.lastSiegeEndTimeStamp > 0) + attackerData.siegeData = siegeData; + defenderData.siegeData = siegeData; + defenderClaim.siegeData = siegeData; + + //start a task to monitor the siege + //why isn't this a "repeating" task? + //because depending on the status of the siege at the time the task runs, there may or may not be a reason to run the task again + SiegeCheckupTask task = new SiegeCheckupTask(siegeData); + siegeData.checkupTaskID = GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 20L * 30); + } + + //ends a siege + //either winnerName or loserName can be null, but not both + synchronized public void endSiege(SiegeData siegeData, String winnerName, String loserName, List drops) + { + boolean grantAccess = false; + + //determine winner and loser + if (winnerName == null && loserName != null) + { + if (siegeData.attacker.getName().equals(loserName)) + { + winnerName = siegeData.defender.getName(); + } else + { + winnerName = siegeData.attacker.getName(); + } + } else if (winnerName != null && loserName == null) + { + if (siegeData.attacker.getName().equals(winnerName)) + { + loserName = siegeData.defender.getName(); + } else + { + loserName = siegeData.attacker.getName(); + } + } + + //if the attacker won, plan to open the doors for looting + if (siegeData.attacker.getName().equals(winnerName)) + { + grantAccess = true; + } + + PlayerData attackerData = this.getPlayerData(siegeData.attacker.getUniqueId()); + attackerData.siegeData = null; + + PlayerData defenderData = this.getPlayerData(siegeData.defender.getUniqueId()); + defenderData.siegeData = null; + defenderData.lastSiegeEndTimeStamp = System.currentTimeMillis(); + + //start a cooldown for this attacker/defender pair + Long now = Calendar.getInstance().getTimeInMillis(); + Long cooldownEnd = now + 1000 * 60 * GriefPrevention.instance.config_siege_cooldownEndInMinutes; //one hour from now + this.siegeCooldownRemaining.put(siegeData.attacker.getName() + "_" + siegeData.defender.getName(), cooldownEnd); + + //start cooldowns for every attacker/involved claim pair + for (int i = 0; i < siegeData.claims.size(); i++) + { + Claim claim = siegeData.claims.get(i); + claim.siegeData = null; + this.siegeCooldownRemaining.put(siegeData.attacker.getName() + "_" + claim.getOwnerName(), cooldownEnd); + + //if doors should be opened for looting, do that now + if (grantAccess) + { + claim.doorsOpen = true; + } + } + + //cancel the siege checkup task + GriefPrevention.instance.getServer().getScheduler().cancelTask(siegeData.checkupTaskID); + + //notify everyone who won and lost + if (winnerName != null && loserName != null) + { + GriefPrevention.instance.getServer().broadcastMessage(winnerName + " defeated " + loserName + " in siege warfare!"); + } + + //if the claim should be opened to looting + if (grantAccess) + { + @SuppressWarnings("deprecation") + Player winner = GriefPrevention.instance.getServer().getPlayer(winnerName); + if (winner != null) + { + //notify the winner + GriefPrevention.sendMessage(winner, TextMode.Success, Messages.SiegeWinDoorsOpen); + + //schedule a task to secure the claims in about 5 minutes + SecureClaimTask task = new SecureClaimTask(siegeData); + + GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask( + GriefPrevention.instance, task, 20L * GriefPrevention.instance.config_siege_doorsOpenSeconds + ); + } + } + + //if the siege ended due to death, transfer inventory to winner + if (drops != null) + { + @SuppressWarnings("deprecation") + Player winner = GriefPrevention.instance.getServer().getPlayer(winnerName); + @SuppressWarnings("deprecation") + Player loser = GriefPrevention.instance.getServer().getPlayer(loserName); + if (winner != null && loser != null) + { + //try to add any drops to the winner's inventory + for (ItemStack stack : drops) + { + if (stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; + + HashMap wontFitItems = winner.getInventory().addItem(stack); + + //drop any remainder on the ground at his feet + Object[] keys = wontFitItems.keySet().toArray(); + Location winnerLocation = winner.getLocation(); + for (int i = 0; i < keys.length; i++) + { + Integer key = (Integer) keys[i]; + winnerLocation.getWorld().dropItemNaturally(winnerLocation, wontFitItems.get(key)); + } + } + + drops.clear(); + } + } + } + + //timestamp for each siege cooldown to end + private HashMap siegeCooldownRemaining = new HashMap(); + + //whether or not a sieger can siege a particular victim or claim, considering only cooldowns + synchronized public boolean onCooldown(Player attacker, Player defender, Claim defenderClaim) + { + Long cooldownEnd = null; + + //look for an attacker/defender cooldown + if (this.siegeCooldownRemaining.get(attacker.getName() + "_" + defender.getName()) != null) + { + cooldownEnd = this.siegeCooldownRemaining.get(attacker.getName() + "_" + defender.getName()); + + if (Calendar.getInstance().getTimeInMillis() < cooldownEnd) + { + return true; + } + + //if found but expired, remove it + this.siegeCooldownRemaining.remove(attacker.getName() + "_" + defender.getName()); + } + + //look for genderal defender cooldown + PlayerData defenderData = this.getPlayerData(defender.getUniqueId()); + if (defenderData.lastSiegeEndTimeStamp > 0) { long now = System.currentTimeMillis(); - if(now - defenderData.lastSiegeEndTimeStamp > 1000 * 60 * 15) //15 minutes in milliseconds + if (now - defenderData.lastSiegeEndTimeStamp > 1000 * 60 * 15) //15 minutes in milliseconds { return true; } } - - //look for an attacker/claim cooldown - if(cooldownEnd == null && this.siegeCooldownRemaining.get(attacker.getName() + "_" + defenderClaim.getOwnerName()) != null) - { - cooldownEnd = this.siegeCooldownRemaining.get(attacker.getName() + "_" + defenderClaim.getOwnerName()); - - if(Calendar.getInstance().getTimeInMillis() < cooldownEnd) - { - return true; - } - - //if found but expired, remove it - this.siegeCooldownRemaining.remove(attacker.getName() + "_" + defenderClaim.getOwnerName()); - } - - return false; - } - //extend a siege, if it's possible to do so - synchronized void tryExtendSiege(Player player, Claim claim) - { - PlayerData playerData = this.getPlayerData(player.getUniqueId()); - - //player must be sieged - if(playerData.siegeData == null) return; - - //claim isn't already under the same siege - if(playerData.siegeData.claims.contains(claim)) return; - - //admin claims can't be sieged - if(claim.isAdminClaim()) return; - - //player must have some level of permission to be sieged in a claim - if(claim.allowAccess(player) != null) return; - - //otherwise extend the siege - playerData.siegeData.claims.add(claim); - claim.siegeData = playerData.siegeData; - } - - //deletes all claims owned by a player - synchronized public void deleteClaimsForPlayer(UUID playerID, boolean releasePets) - { - //make a list of the player's claims - ArrayList claimsToDelete = new ArrayList(); - for(int i = 0; i < this.claims.size(); i++) - { - Claim claim = this.claims.get(i); - if((playerID == claim.ownerID || (playerID != null && playerID.equals(claim.ownerID)))) - claimsToDelete.add(claim); - } + //look for an attacker/claim cooldown + if (cooldownEnd == null && this.siegeCooldownRemaining.get(attacker.getName() + "_" + defenderClaim.getOwnerName()) != null) + { + cooldownEnd = this.siegeCooldownRemaining.get(attacker.getName() + "_" + defenderClaim.getOwnerName()); - //delete them one by one - for(int i = 0; i < claimsToDelete.size(); i++) - { - Claim claim = claimsToDelete.get(i); - claim.removeSurfaceFluids(null); + if (Calendar.getInstance().getTimeInMillis() < cooldownEnd) + { + return true; + } - this.deleteClaim(claim, releasePets); - - //if in a creative mode world, delete the claim - if(GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner())) - { - GriefPrevention.instance.restoreClaim(claim, 0); - } - } - } + //if found but expired, remove it + this.siegeCooldownRemaining.remove(attacker.getName() + "_" + defenderClaim.getOwnerName()); + } - //tries to resize a claim - //see CreateClaim() for details on return value - synchronized public CreateClaimResult resizeClaim(Claim claim, int newx1, int newx2, int newy1, int newy2, int newz1, int newz2, Player resizingPlayer) - { - //try to create this new claim, ignoring the original when checking for overlap - CreateClaimResult result = this.createClaim(claim.getLesserBoundaryCorner().getWorld(), newx1, newx2, newy1, newy2, newz1, newz2, claim.ownerID, claim.parent, claim.id, resizingPlayer, true); - - //if succeeded - if(result.succeeded) - { - removeFromChunkClaimMap(claim); // remove the old boundary from the chunk cache - // copy the boundary from the claim created in the dry run of createClaim() to our existing claim - claim.lesserBoundaryCorner = result.claim.lesserBoundaryCorner; - claim.greaterBoundaryCorner = result.claim.greaterBoundaryCorner; - result.claim = claim; - addToChunkClaimMap(claim); // add the new boundary to the chunk cache - - //save those changes - this.saveClaim(result.claim); - ClaimModifiedEvent event = new ClaimModifiedEvent(result.claim,resizingPlayer); - Bukkit.getPluginManager().callEvent(event); - } - - return result; - } - - void resizeClaimWithChecks(Player player, PlayerData playerData, int newx1, int newx2, int newy1, int newy2, int newz1, int newz2) + return false; + } + + //extend a siege, if it's possible to do so + synchronized void tryExtendSiege(Player player, Claim claim) { - //for top level claims, apply size rules and claim blocks requirement - if(playerData.claimResizing.parent == null) - { + PlayerData playerData = this.getPlayerData(player.getUniqueId()); + + //player must be sieged + if (playerData.siegeData == null) return; + + //claim isn't already under the same siege + if (playerData.siegeData.claims.contains(claim)) return; + + //admin claims can't be sieged + if (claim.isAdminClaim()) return; + + //player must have some level of permission to be sieged in a claim + if (claim.allowAccess(player) != null) return; + + //otherwise extend the siege + playerData.siegeData.claims.add(claim); + claim.siegeData = playerData.siegeData; + } + + //deletes all claims owned by a player + synchronized public void deleteClaimsForPlayer(UUID playerID, boolean releasePets) + { + //make a list of the player's claims + ArrayList claimsToDelete = new ArrayList(); + for (int i = 0; i < this.claims.size(); i++) + { + Claim claim = this.claims.get(i); + if ((playerID == claim.ownerID || (playerID != null && playerID.equals(claim.ownerID)))) + claimsToDelete.add(claim); + } + + //delete them one by one + for (int i = 0; i < claimsToDelete.size(); i++) + { + Claim claim = claimsToDelete.get(i); + claim.removeSurfaceFluids(null); + + this.deleteClaim(claim, releasePets); + + //if in a creative mode world, delete the claim + if (GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner())) + { + GriefPrevention.instance.restoreClaim(claim, 0); + } + } + } + + //tries to resize a claim + //see CreateClaim() for details on return value + synchronized public CreateClaimResult resizeClaim(Claim claim, int newx1, int newx2, int newy1, int newy2, int newz1, int newz2, Player resizingPlayer) + { + //try to create this new claim, ignoring the original when checking for overlap + CreateClaimResult result = this.createClaim(claim.getLesserBoundaryCorner().getWorld(), newx1, newx2, newy1, newy2, newz1, newz2, claim.ownerID, claim.parent, claim.id, resizingPlayer, true); + + //if succeeded + if (result.succeeded) + { + removeFromChunkClaimMap(claim); // remove the old boundary from the chunk cache + // copy the boundary from the claim created in the dry run of createClaim() to our existing claim + claim.lesserBoundaryCorner = result.claim.lesserBoundaryCorner; + claim.greaterBoundaryCorner = result.claim.greaterBoundaryCorner; + result.claim = claim; + addToChunkClaimMap(claim); // add the new boundary to the chunk cache + + //save those changes + this.saveClaim(result.claim); + ClaimModifiedEvent event = new ClaimModifiedEvent(result.claim, resizingPlayer); + Bukkit.getPluginManager().callEvent(event); + } + + return result; + } + + void resizeClaimWithChecks(Player player, PlayerData playerData, int newx1, int newx2, int newy1, int newy2, int newz1, int newz2) + { + //for top level claims, apply size rules and claim blocks requirement + if (playerData.claimResizing.parent == null) + { //measure new claim, apply size rules int newWidth = (Math.abs(newx1 - newx2) + 1); int newHeight = (Math.abs(newz1 - newz2) + 1); boolean smaller = newWidth < playerData.claimResizing.getWidth() || newHeight < playerData.claimResizing.getHeight(); - - if(!player.hasPermission("griefprevention.adminclaims") && !playerData.claimResizing.isAdminClaim() && smaller) + + if (!player.hasPermission("griefprevention.adminclaims") && !playerData.claimResizing.isAdminClaim() && smaller) { - if(newWidth < GriefPrevention.instance.config_claims_minWidth || newHeight < GriefPrevention.instance.config_claims_minWidth) + if (newWidth < GriefPrevention.instance.config_claims_minWidth || newHeight < GriefPrevention.instance.config_claims_minWidth) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.ResizeClaimTooNarrow, String.valueOf(GriefPrevention.instance.config_claims_minWidth)); return; } - + int newArea = newWidth * newHeight; - if(newArea < GriefPrevention.instance.config_claims_minArea) + if (newArea < GriefPrevention.instance.config_claims_minArea) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.ResizeClaimInsufficientArea, String.valueOf(GriefPrevention.instance.config_claims_minArea)); return; } } - + //make sure player has enough blocks to make up the difference - if(!playerData.claimResizing.isAdminClaim() && player.getName().equals(playerData.claimResizing.getOwnerName())) + if (!playerData.claimResizing.isAdminClaim() && player.getName().equals(playerData.claimResizing.getOwnerName())) { - int newArea = newWidth * newHeight; + int newArea = newWidth * newHeight; int blocksRemainingAfter = playerData.getRemainingClaimBlocks() + playerData.claimResizing.getArea() - newArea; - - if(blocksRemainingAfter < 0) + + if (blocksRemainingAfter < 0) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.ResizeNeedMoreBlocks, String.valueOf(Math.abs(blocksRemainingAfter))); this.tryAdvertiseAdminAlternatives(player); @@ -1326,517 +1330,512 @@ public abstract class DataStore } } } - + //special rule for making a top-level claim smaller. to check this, verifying the old claim's corners are inside the new claim's boundaries. //rule: in any mode, shrinking a claim removes any surface fluids Claim oldClaim = playerData.claimResizing; boolean smaller = false; - if(oldClaim.parent == null) - { + if (oldClaim.parent == null) + { //temporary claim instance, just for checking contains() Claim newClaim = new Claim( - new Location(oldClaim.getLesserBoundaryCorner().getWorld(), newx1, newy1, newz1), + new Location(oldClaim.getLesserBoundaryCorner().getWorld(), newx1, newy1, newz1), new Location(oldClaim.getLesserBoundaryCorner().getWorld(), newx2, newy2, newz2), null, new ArrayList(), new ArrayList(), new ArrayList(), new ArrayList(), null); - + //if the new claim is smaller - if(!newClaim.contains(oldClaim.getLesserBoundaryCorner(), true, false) || !newClaim.contains(oldClaim.getGreaterBoundaryCorner(), true, false)) + if (!newClaim.contains(oldClaim.getLesserBoundaryCorner(), true, false) || !newClaim.contains(oldClaim.getGreaterBoundaryCorner(), true, false)) { smaller = true; - + //remove surface fluids about to be unclaimed oldClaim.removeSurfaceFluids(newClaim); } } - + //ask the datastore to try and resize the claim, this checks for conflicts with other claims CreateClaimResult result = GriefPrevention.instance.dataStore.resizeClaim(playerData.claimResizing, newx1, newx2, newy1, newy2, newz1, newz2, player); - - if(result.succeeded) + + if (result.succeeded) { //decide how many claim blocks are available for more resizing int claimBlocksRemaining = 0; - if(!playerData.claimResizing.isAdminClaim()) + if (!playerData.claimResizing.isAdminClaim()) { UUID ownerID = playerData.claimResizing.ownerID; - if(playerData.claimResizing.parent != null) + if (playerData.claimResizing.parent != null) { ownerID = playerData.claimResizing.parent.ownerID; } - if(ownerID == player.getUniqueId()) + if (ownerID == player.getUniqueId()) { claimBlocksRemaining = playerData.getRemainingClaimBlocks(); - } - else + } else { PlayerData ownerData = this.getPlayerData(ownerID); claimBlocksRemaining = ownerData.getRemainingClaimBlocks(); OfflinePlayer owner = GriefPrevention.instance.getServer().getOfflinePlayer(ownerID); - if(!owner.isOnline()) + if (!owner.isOnline()) { this.clearCachedPlayerData(ownerID); } } } - + //inform about success, visualize, communicate remaining blocks available GriefPrevention.sendMessage(player, TextMode.Success, Messages.ClaimResizeSuccess, String.valueOf(claimBlocksRemaining)); Visualization visualization = Visualization.FromClaim(result.claim, player.getEyeLocation().getBlockY(), VisualizationType.Claim, player.getLocation()); Visualization.Apply(player, visualization); - + //if resizing someone else's claim, make a log entry - if(!player.getUniqueId().equals(playerData.claimResizing.ownerID) && playerData.claimResizing.parent == null) + if (!player.getUniqueId().equals(playerData.claimResizing.ownerID) && playerData.claimResizing.parent == null) { GriefPrevention.AddLogEntry(player.getName() + " resized " + playerData.claimResizing.getOwnerName() + "'s claim at " + GriefPrevention.getfriendlyLocationString(playerData.claimResizing.lesserBoundaryCorner) + "."); } - + //if increased to a sufficiently large size and no subdivisions yet, send subdivision instructions - if(oldClaim.getArea() < 1000 && result.claim.getArea() >= 1000 && result.claim.children.size() == 0 && !player.hasPermission("griefprevention.adminclaims")) + if (oldClaim.getArea() < 1000 && result.claim.getArea() >= 1000 && result.claim.children.size() == 0 && !player.hasPermission("griefprevention.adminclaims")) { - GriefPrevention.sendMessage(player, TextMode.Info, Messages.BecomeMayor, 200L); - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SubdivisionVideo2, 201L, DataStore.SUBDIVISION_VIDEO_URL); + GriefPrevention.sendMessage(player, TextMode.Info, Messages.BecomeMayor, 200L); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SubdivisionVideo2, 201L, DataStore.SUBDIVISION_VIDEO_URL); } - + //if in a creative mode world and shrinking an existing claim, restore any unclaimed area - if(smaller && GriefPrevention.instance.creativeRulesApply(oldClaim.getLesserBoundaryCorner())) + if (smaller && GriefPrevention.instance.creativeRulesApply(oldClaim.getLesserBoundaryCorner())) { GriefPrevention.sendMessage(player, TextMode.Warn, Messages.UnclaimCleanupWarning); GriefPrevention.instance.restoreClaim(oldClaim, 20L * 60 * 2); //2 minutes GriefPrevention.AddLogEntry(player.getName() + " shrank a claim @ " + GriefPrevention.getfriendlyLocationString(playerData.claimResizing.getLesserBoundaryCorner())); } - + //clean up playerData.claimResizing = null; playerData.lastShovelLocation = null; - } - else + } else { - if(result.claim != null) + if (result.claim != null) { //inform player GriefPrevention.sendMessage(player, TextMode.Err, Messages.ResizeFailOverlap); - + //show the player the conflicting claim Visualization visualization = Visualization.FromClaim(result.claim, player.getEyeLocation().getBlockY(), VisualizationType.ErrorClaim, player.getLocation()); Visualization.Apply(player, visualization); - } - else + } else { GriefPrevention.sendMessage(player, TextMode.Err, Messages.ResizeFailOverlapRegion); } } } - + //educates a player about /adminclaims and /acb, if he can use them void tryAdvertiseAdminAlternatives(Player player) { - if(player.hasPermission("griefprevention.adminclaims") && player.hasPermission("griefprevention.adjustclaimblocks")) + if (player.hasPermission("griefprevention.adminclaims") && player.hasPermission("griefprevention.adjustclaimblocks")) { GriefPrevention.sendMessage(player, TextMode.Info, Messages.AdvertiseACandACB); - } - else if(player.hasPermission("griefprevention.adminclaims")) + } else if (player.hasPermission("griefprevention.adminclaims")) { GriefPrevention.sendMessage(player, TextMode.Info, Messages.AdvertiseAdminClaims); - } - else if(player.hasPermission("griefprevention.adjustclaimblocks")) + } else if (player.hasPermission("griefprevention.adjustclaimblocks")) { GriefPrevention.sendMessage(player, TextMode.Info, Messages.AdvertiseACB); } } - - 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.PlayerNotFound2, "No player by that name has logged in recently.", 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.MaxBonusReached, "Can't purchase {0} more claim blocks. The server has a limit of {1} bonus claim blocks.", "0: block count; 1: bonus claims limit"); - 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.SubdivisionVideo2, "Click for Subdivision Help: {0}", "0:video URL"); - 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.AdjustBlocksAllSuccess, "Adjusted all online players' bonus claim blocks by {0}.", "0: adjustment amount"); - this.addDefault(defaults, Messages.NotTrappedHere, "You can build here. Save yourself.", null); - 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.NoSiegeYourself, "You cannot siege yourself, don't be silly", 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.PlayerTooCloseForFire2, "You can't start a fire this close to another player.", null); - 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.", null); - this.addDefault(defaults, Messages.UnprotectedChestWarning, "This chest is NOT protected. Consider using a golden shovel to expand an existing claim or to create 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.CreativeBasicsVideo2, "Click for Land Claim Help: {0}", "{0}: video URL"); - this.addDefault(defaults, Messages.SurvivalBasicsVideo2, "Click for Land Claim Help: {0}", "{0}: video URL"); - this.addDefault(defaults, Messages.TrappedChatKeyword, "trapped;stuck", "When mentioned in chat, players get information about the /trapped command (multiple words can be separated with semi-colons)"); - this.addDefault(defaults, Messages.TrappedInstructions, "Are you trapped in someone's land claim? Try 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.ResizeClaimTooNarrow, "This new size would be too small. Claims must be at least {0} blocks wide.", "0: minimum claim width"); - 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. {0} available claim blocks remaining.", "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.NewClaimTooNarrow, "This claim would be too small. Any claim must be at least {0} blocks wide.", "0: minimum claim width"); - this.addDefault(defaults, Messages.ResizeClaimInsufficientArea, "This claim would be too small. Any claim must use at least {0} total claim blocks.", "0: minimum claim area"); - 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.", 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 lava inside the claim. 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.HowToClaimRegex, "(^|.*\\W)how\\W.*\\W(claim|protect|lock)(\\W.*|$)", "This is a Java Regular Expression. Look it up before editing! It's used to tell players about the demo video when they ask how to claim land."); - this.addDefault(defaults, Messages.NoBuildOutsideClaims, "You can't build here unless you claim some land first.", null); - this.addDefault(defaults, Messages.PlayerOfflineTime, " Last login: {0} days ago.", "0: number of full days since last login"); - this.addDefault(defaults, Messages.BuildingOutsideClaims, "Other players can build here, too. Consider creating a land claim to protect your work!", null); - this.addDefault(defaults, Messages.TrappedWontWorkHere, "Sorry, unable to find a safe location to teleport you to. Contact an admin.", null); - this.addDefault(defaults, Messages.CommandBannedInPvP, "You can't use that command while in PvP combat.", null); - this.addDefault(defaults, Messages.UnclaimCleanupWarning, "The land you've unclaimed may be changed by other players or cleaned up by administrators. If you've built something there you want to keep, you should reclaim it.", null); - this.addDefault(defaults, Messages.BuySellNotConfigured, "Sorry, buying and selling claim blocks is disabled.", null); - this.addDefault(defaults, Messages.NoTeleportPvPCombat, "You can't teleport while fighting another player.", null); - this.addDefault(defaults, Messages.NoTNTDamageAboveSeaLevel, "Warning: TNT will not destroy blocks above sea level.", null); - this.addDefault(defaults, Messages.NoTNTDamageClaims, "Warning: TNT will not destroy claimed blocks.", null); - this.addDefault(defaults, Messages.IgnoreClaimsAdvertisement, "To override, use /IgnoreClaims.", null); - this.addDefault(defaults, Messages.NoPermissionForCommand, "You don't have permission to do that.", null); - this.addDefault(defaults, Messages.ClaimsListNoPermission, "You don't have permission to get information about another player's land claims.", null); - this.addDefault(defaults, Messages.ExplosivesDisabled, "This claim is now protected from explosions. Use /ClaimExplosions again to disable.", null); - this.addDefault(defaults, Messages.ExplosivesEnabled, "This claim is now vulnerable to explosions. Use /ClaimExplosions again to re-enable protections.", null); - this.addDefault(defaults, Messages.ClaimExplosivesAdvertisement, "To allow explosives to destroy blocks in this land claim, use /ClaimExplosions.", null); - this.addDefault(defaults, Messages.PlayerInPvPSafeZone, "That player is in a PvP safe zone.", null); - this.addDefault(defaults, Messages.NoPistonsOutsideClaims, "Warning: Pistons won't move blocks outside land claims.", null); - this.addDefault(defaults, Messages.SoftMuted, "Soft-muted {0}.", "0: The changed player's name."); - this.addDefault(defaults, Messages.UnSoftMuted, "Un-soft-muted {0}.", "0: The changed player's name."); - this.addDefault(defaults, Messages.DropUnlockAdvertisement, "Other players can't pick up your dropped items unless you /UnlockDrops first.", null); - this.addDefault(defaults, Messages.PickupBlockedExplanation, "You can't pick this up unless {0} uses /UnlockDrops.", "0: The item stack's owner."); - this.addDefault(defaults, Messages.DropUnlockConfirmation, "Unlocked your drops. Other players may now pick them up (until you die again).", null); - this.addDefault(defaults, Messages.DropUnlockOthersConfirmation, "Unlocked {0}'s drops.", "0: The owner of the unlocked drops."); - this.addDefault(defaults, Messages.AdvertiseACandACB, "You may use /ACB to give yourself more claim blocks, or /AdminClaims to create a free administrative claim.", null); - this.addDefault(defaults, Messages.AdvertiseAdminClaims, "You could create an administrative land claim instead using /AdminClaims, which you'd share with other administrators.", null); - this.addDefault(defaults, Messages.AdvertiseACB, "You may use /ACB to give yourself more claim blocks.", null); - this.addDefault(defaults, Messages.NotYourPet, "That belongs to {0} until it's given to you with /GivePet.", "0: owner name"); - this.addDefault(defaults, Messages.PetGiveawayConfirmation, "Pet transferred.", null); - this.addDefault(defaults, Messages.PetTransferCancellation, "Pet giveaway cancelled.", null); - this.addDefault(defaults, Messages.ReadyToTransferPet, "Ready to transfer! Right-click the pet you'd like to give away, or cancel with /GivePet cancel.", null); - this.addDefault(defaults, Messages.AvoidGriefClaimLand, "Prevent grief! If you claim your land, you will be grief-proof.", null); - this.addDefault(defaults, Messages.BecomeMayor, "Subdivide your land claim and become a mayor!", null); - this.addDefault(defaults, Messages.ClaimCreationFailedOverClaimCountLimit, "You've reached your limit on land claims. Use /AbandonClaim to remove one before creating another.", null); - this.addDefault(defaults, Messages.CreateClaimFailOverlapRegion, "You can't claim all of this because you're not allowed to build here.", null); - this.addDefault(defaults, Messages.ResizeFailOverlapRegion, "You don't have permission to build there, so you can't claim that area.", null); - this.addDefault(defaults, Messages.ShowNearbyClaims, "Found {0} land claims.", "0: Number of claims found."); - this.addDefault(defaults, Messages.NoChatUntilMove, "Sorry, but you have to move a little more before you can chat. We get lots of spam bots here. :)", null); - this.addDefault(defaults, Messages.SiegeImmune, "That player is immune to /siege.", null); - this.addDefault(defaults, Messages.SetClaimBlocksSuccess, "Updated accrued claim blocks.", null); - this.addDefault(defaults, Messages.IgnoreConfirmation, "You're now ignoring chat messages from that player.", null); - this.addDefault(defaults, Messages.UnIgnoreConfirmation, "You're no longer ignoring chat messages from that player.", null); - this.addDefault(defaults, Messages.NotIgnoringPlayer, "You're not ignoring that player.", null); - this.addDefault(defaults, Messages.SeparateConfirmation, "Those players will now ignore each other in chat.", null); - this.addDefault(defaults, Messages.UnSeparateConfirmation, "Those players will no longer ignore each other in chat.", null); + + 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.PlayerNotFound2, "No player by that name has logged in recently.", 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.MaxBonusReached, "Can't purchase {0} more claim blocks. The server has a limit of {1} bonus claim blocks.", "0: block count; 1: bonus claims limit"); + 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.SubdivisionVideo2, "Click for Subdivision Help: {0}", "0:video URL"); + 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.AdjustBlocksAllSuccess, "Adjusted all online players' bonus claim blocks by {0}.", "0: adjustment amount"); + this.addDefault(defaults, Messages.NotTrappedHere, "You can build here. Save yourself.", null); + 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.NoSiegeYourself, "You cannot siege yourself, don't be silly", 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.PlayerTooCloseForFire2, "You can't start a fire this close to another player.", null); + 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.", null); + this.addDefault(defaults, Messages.UnprotectedChestWarning, "This chest is NOT protected. Consider using a golden shovel to expand an existing claim or to create 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.CreativeBasicsVideo2, "Click for Land Claim Help: {0}", "{0}: video URL"); + this.addDefault(defaults, Messages.SurvivalBasicsVideo2, "Click for Land Claim Help: {0}", "{0}: video URL"); + this.addDefault(defaults, Messages.TrappedChatKeyword, "trapped;stuck", "When mentioned in chat, players get information about the /trapped command (multiple words can be separated with semi-colons)"); + this.addDefault(defaults, Messages.TrappedInstructions, "Are you trapped in someone's land claim? Try 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.ResizeClaimTooNarrow, "This new size would be too small. Claims must be at least {0} blocks wide.", "0: minimum claim width"); + 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. {0} available claim blocks remaining.", "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.NewClaimTooNarrow, "This claim would be too small. Any claim must be at least {0} blocks wide.", "0: minimum claim width"); + this.addDefault(defaults, Messages.ResizeClaimInsufficientArea, "This claim would be too small. Any claim must use at least {0} total claim blocks.", "0: minimum claim area"); + 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.", 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 lava inside the claim. 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.HowToClaimRegex, "(^|.*\\W)how\\W.*\\W(claim|protect|lock)(\\W.*|$)", "This is a Java Regular Expression. Look it up before editing! It's used to tell players about the demo video when they ask how to claim land."); + this.addDefault(defaults, Messages.NoBuildOutsideClaims, "You can't build here unless you claim some land first.", null); + this.addDefault(defaults, Messages.PlayerOfflineTime, " Last login: {0} days ago.", "0: number of full days since last login"); + this.addDefault(defaults, Messages.BuildingOutsideClaims, "Other players can build here, too. Consider creating a land claim to protect your work!", null); + this.addDefault(defaults, Messages.TrappedWontWorkHere, "Sorry, unable to find a safe location to teleport you to. Contact an admin.", null); + this.addDefault(defaults, Messages.CommandBannedInPvP, "You can't use that command while in PvP combat.", null); + this.addDefault(defaults, Messages.UnclaimCleanupWarning, "The land you've unclaimed may be changed by other players or cleaned up by administrators. If you've built something there you want to keep, you should reclaim it.", null); + this.addDefault(defaults, Messages.BuySellNotConfigured, "Sorry, buying and selling claim blocks is disabled.", null); + this.addDefault(defaults, Messages.NoTeleportPvPCombat, "You can't teleport while fighting another player.", null); + this.addDefault(defaults, Messages.NoTNTDamageAboveSeaLevel, "Warning: TNT will not destroy blocks above sea level.", null); + this.addDefault(defaults, Messages.NoTNTDamageClaims, "Warning: TNT will not destroy claimed blocks.", null); + this.addDefault(defaults, Messages.IgnoreClaimsAdvertisement, "To override, use /IgnoreClaims.", null); + this.addDefault(defaults, Messages.NoPermissionForCommand, "You don't have permission to do that.", null); + this.addDefault(defaults, Messages.ClaimsListNoPermission, "You don't have permission to get information about another player's land claims.", null); + this.addDefault(defaults, Messages.ExplosivesDisabled, "This claim is now protected from explosions. Use /ClaimExplosions again to disable.", null); + this.addDefault(defaults, Messages.ExplosivesEnabled, "This claim is now vulnerable to explosions. Use /ClaimExplosions again to re-enable protections.", null); + this.addDefault(defaults, Messages.ClaimExplosivesAdvertisement, "To allow explosives to destroy blocks in this land claim, use /ClaimExplosions.", null); + this.addDefault(defaults, Messages.PlayerInPvPSafeZone, "That player is in a PvP safe zone.", null); + this.addDefault(defaults, Messages.NoPistonsOutsideClaims, "Warning: Pistons won't move blocks outside land claims.", null); + this.addDefault(defaults, Messages.SoftMuted, "Soft-muted {0}.", "0: The changed player's name."); + this.addDefault(defaults, Messages.UnSoftMuted, "Un-soft-muted {0}.", "0: The changed player's name."); + this.addDefault(defaults, Messages.DropUnlockAdvertisement, "Other players can't pick up your dropped items unless you /UnlockDrops first.", null); + this.addDefault(defaults, Messages.PickupBlockedExplanation, "You can't pick this up unless {0} uses /UnlockDrops.", "0: The item stack's owner."); + this.addDefault(defaults, Messages.DropUnlockConfirmation, "Unlocked your drops. Other players may now pick them up (until you die again).", null); + this.addDefault(defaults, Messages.DropUnlockOthersConfirmation, "Unlocked {0}'s drops.", "0: The owner of the unlocked drops."); + this.addDefault(defaults, Messages.AdvertiseACandACB, "You may use /ACB to give yourself more claim blocks, or /AdminClaims to create a free administrative claim.", null); + this.addDefault(defaults, Messages.AdvertiseAdminClaims, "You could create an administrative land claim instead using /AdminClaims, which you'd share with other administrators.", null); + this.addDefault(defaults, Messages.AdvertiseACB, "You may use /ACB to give yourself more claim blocks.", null); + this.addDefault(defaults, Messages.NotYourPet, "That belongs to {0} until it's given to you with /GivePet.", "0: owner name"); + this.addDefault(defaults, Messages.PetGiveawayConfirmation, "Pet transferred.", null); + this.addDefault(defaults, Messages.PetTransferCancellation, "Pet giveaway cancelled.", null); + this.addDefault(defaults, Messages.ReadyToTransferPet, "Ready to transfer! Right-click the pet you'd like to give away, or cancel with /GivePet cancel.", null); + this.addDefault(defaults, Messages.AvoidGriefClaimLand, "Prevent grief! If you claim your land, you will be grief-proof.", null); + this.addDefault(defaults, Messages.BecomeMayor, "Subdivide your land claim and become a mayor!", null); + this.addDefault(defaults, Messages.ClaimCreationFailedOverClaimCountLimit, "You've reached your limit on land claims. Use /AbandonClaim to remove one before creating another.", null); + this.addDefault(defaults, Messages.CreateClaimFailOverlapRegion, "You can't claim all of this because you're not allowed to build here.", null); + this.addDefault(defaults, Messages.ResizeFailOverlapRegion, "You don't have permission to build there, so you can't claim that area.", null); + this.addDefault(defaults, Messages.ShowNearbyClaims, "Found {0} land claims.", "0: Number of claims found."); + this.addDefault(defaults, Messages.NoChatUntilMove, "Sorry, but you have to move a little more before you can chat. We get lots of spam bots here. :)", null); + this.addDefault(defaults, Messages.SiegeImmune, "That player is immune to /siege.", null); + this.addDefault(defaults, Messages.SetClaimBlocksSuccess, "Updated accrued claim blocks.", null); + this.addDefault(defaults, Messages.IgnoreConfirmation, "You're now ignoring chat messages from that player.", null); + this.addDefault(defaults, Messages.UnIgnoreConfirmation, "You're no longer ignoring chat messages from that player.", null); + this.addDefault(defaults, Messages.NotIgnoringPlayer, "You're not ignoring that player.", null); + this.addDefault(defaults, Messages.SeparateConfirmation, "Those players will now ignore each other in chat.", null); + this.addDefault(defaults, Messages.UnSeparateConfirmation, "Those players will no longer ignore each other in chat.", null); this.addDefault(defaults, Messages.NotIgnoringAnyone, "You're not ignoring anyone.", null); - this.addDefault(defaults, Messages.TrustListHeader, "Explicit permissions here:", null); - this.addDefault(defaults, Messages.Manage, "Manage", null); - this.addDefault(defaults, Messages.Build, "Build", null); - this.addDefault(defaults, Messages.Containers, "Containers", null); - this.addDefault(defaults, Messages.Access, "Access", null); - this.addDefault(defaults, Messages.HasSubclaimRestriction, "This subclaim does not inherit permissions from the parent", null); - this.addDefault(defaults, Messages.StartBlockMath, "{0} blocks from play + {1} bonus = {2} total.", null); - this.addDefault(defaults, Messages.ClaimsListHeader, "Claims:", null); - this.addDefault(defaults, Messages.ContinueBlockMath, " (-{0} blocks)", null); - this.addDefault(defaults, Messages.EndBlockMath, " = {0} blocks left to spend", null); - this.addDefault(defaults, Messages.NoClaimDuringPvP, "You can't claim lands during PvP combat.", null); - this.addDefault(defaults, Messages.UntrustAllOwnerOnly, "Only the claim owner can clear all its permissions.", null); - this.addDefault(defaults, Messages.ManagersDontUntrustManagers, "Only the claim owner can demote a manager.", null); - this.addDefault(defaults, Messages.PlayerNotIgnorable, "You can't ignore that player.", null); - this.addDefault(defaults, Messages.NoEnoughBlocksForChestClaim, "Because you don't have any claim blocks available, no automatic land claim was created for you. You can use /ClaimsList to monitor your available claim block total.", null); - this.addDefault(defaults, Messages.MustHoldModificationToolForThat, "You must be holding a golden shovel to do that.", null); - this.addDefault(defaults, Messages.StandInClaimToResize, "Stand inside the land claim you want to resize.", null); - this.addDefault(defaults, Messages.ClaimsExtendToSky, "Land claims always extend to max build height.", null); - this.addDefault(defaults, Messages.ClaimsAutoExtendDownward, "Land claims auto-extend deeper into the ground when you place blocks under them.", null); - this.addDefault(defaults, Messages.MinimumRadius, "Minimum radius is {0}.", "0: minimum radius"); - this.addDefault(defaults, Messages.RadiusRequiresGoldenShovel, "You must be holding a golden shovel when specifying a radius.", null); - this.addDefault(defaults, Messages.ClaimTooSmallForActiveBlocks, "This claim isn't big enough to support any active block types (hoppers, spawners, beacons...). Make the claim bigger first.", null); - this.addDefault(defaults, Messages.TooManyActiveBlocksInClaim, "This claim is at its limit for active block types (hoppers, spawners, beacons...). Either make it bigger, or remove other active blocks first.", null); - - this.addDefault(defaults, Messages.BookAuthor, "BigScary", null); - this.addDefault(defaults, Messages.BookTitle, "How to Claim Land", null); - this.addDefault(defaults, Messages.BookLink, "Click: {0}", "{0}: video URL"); - this.addDefault(defaults, Messages.BookIntro, "Claim land to protect your stuff! Click the link above to learn land claims in 3 minutes or less. :)", null); - this.addDefault(defaults, Messages.BookTools, "Our claim tools are {0} and {1}.", "0: claim modification tool name; 1:claim information tool name"); - this.addDefault(defaults, Messages.BookDisabledChestClaims, " On this server, placing a chest will NOT claim land for you.", null); - this.addDefault(defaults, Messages.BookUsefulCommands, "Useful Commands:", null); - this.addDefault(defaults, Messages.NoProfanity, "Please moderate your language.", null); - this.addDefault(defaults, Messages.IsIgnoringYou, "That player is ignoring you.", null); - this.addDefault(defaults, Messages.ConsoleOnlyCommand, "That command may only be executed from the server console.", null); - this.addDefault(defaults, Messages.WorldNotFound, "World not found.", null); - this.addDefault(defaults, Messages.TooMuchIpOverlap, "Sorry, there are too many players logged in with your IP address.", null); + this.addDefault(defaults, Messages.TrustListHeader, "Explicit permissions here:", null); + this.addDefault(defaults, Messages.Manage, "Manage", null); + this.addDefault(defaults, Messages.Build, "Build", null); + this.addDefault(defaults, Messages.Containers, "Containers", null); + this.addDefault(defaults, Messages.Access, "Access", null); + this.addDefault(defaults, Messages.HasSubclaimRestriction, "This subclaim does not inherit permissions from the parent", null); + this.addDefault(defaults, Messages.StartBlockMath, "{0} blocks from play + {1} bonus = {2} total.", null); + this.addDefault(defaults, Messages.ClaimsListHeader, "Claims:", null); + this.addDefault(defaults, Messages.ContinueBlockMath, " (-{0} blocks)", null); + this.addDefault(defaults, Messages.EndBlockMath, " = {0} blocks left to spend", null); + this.addDefault(defaults, Messages.NoClaimDuringPvP, "You can't claim lands during PvP combat.", null); + this.addDefault(defaults, Messages.UntrustAllOwnerOnly, "Only the claim owner can clear all its permissions.", null); + this.addDefault(defaults, Messages.ManagersDontUntrustManagers, "Only the claim owner can demote a manager.", null); + this.addDefault(defaults, Messages.PlayerNotIgnorable, "You can't ignore that player.", null); + this.addDefault(defaults, Messages.NoEnoughBlocksForChestClaim, "Because you don't have any claim blocks available, no automatic land claim was created for you. You can use /ClaimsList to monitor your available claim block total.", null); + this.addDefault(defaults, Messages.MustHoldModificationToolForThat, "You must be holding a golden shovel to do that.", null); + this.addDefault(defaults, Messages.StandInClaimToResize, "Stand inside the land claim you want to resize.", null); + this.addDefault(defaults, Messages.ClaimsExtendToSky, "Land claims always extend to max build height.", null); + this.addDefault(defaults, Messages.ClaimsAutoExtendDownward, "Land claims auto-extend deeper into the ground when you place blocks under them.", null); + this.addDefault(defaults, Messages.MinimumRadius, "Minimum radius is {0}.", "0: minimum radius"); + this.addDefault(defaults, Messages.RadiusRequiresGoldenShovel, "You must be holding a golden shovel when specifying a radius.", null); + this.addDefault(defaults, Messages.ClaimTooSmallForActiveBlocks, "This claim isn't big enough to support any active block types (hoppers, spawners, beacons...). Make the claim bigger first.", null); + this.addDefault(defaults, Messages.TooManyActiveBlocksInClaim, "This claim is at its limit for active block types (hoppers, spawners, beacons...). Either make it bigger, or remove other active blocks first.", null); - this.addDefault(defaults, Messages.StandInSubclaim, "You need to be standing in a subclaim to restrict it", null); - this.addDefault(defaults, Messages.SubclaimRestricted, "This subclaim's permissions will no longer inherit from the parent claim", null); - this.addDefault(defaults, Messages.SubclaimUnrestricted, "This subclaim's permissions will now inherit from the parent claim", null); + this.addDefault(defaults, Messages.BookAuthor, "BigScary", null); + this.addDefault(defaults, Messages.BookTitle, "How to Claim Land", null); + this.addDefault(defaults, Messages.BookLink, "Click: {0}", "{0}: video URL"); + this.addDefault(defaults, Messages.BookIntro, "Claim land to protect your stuff! Click the link above to learn land claims in 3 minutes or less. :)", null); + this.addDefault(defaults, Messages.BookTools, "Our claim tools are {0} and {1}.", "0: claim modification tool name; 1:claim information tool name"); + this.addDefault(defaults, Messages.BookDisabledChestClaims, " On this server, placing a chest will NOT claim land for you.", null); + this.addDefault(defaults, Messages.BookUsefulCommands, "Useful Commands:", null); + this.addDefault(defaults, Messages.NoProfanity, "Please moderate your language.", null); + this.addDefault(defaults, Messages.IsIgnoringYou, "That player is ignoring you.", null); + this.addDefault(defaults, Messages.ConsoleOnlyCommand, "That command may only be executed from the server console.", null); + this.addDefault(defaults, Messages.WorldNotFound, "World not found.", null); + this.addDefault(defaults, Messages.TooMuchIpOverlap, "Sorry, there are too many players logged in with your IP address.", null); - this.addDefault(defaults, Messages.NetherPortalTrapDetectionMessage, "It seems you might be stuck inside a nether portal. We will rescue you in a few seconds if that is the case!", "Sent to player on join, if they left while inside a nether portal."); + this.addDefault(defaults, Messages.StandInSubclaim, "You need to be standing in a subclaim to restrict it", null); + this.addDefault(defaults, Messages.SubclaimRestricted, "This subclaim's permissions will no longer inherit from the parent claim", null); + this.addDefault(defaults, Messages.SubclaimUnrestricted, "This subclaim's permissions will now inherit from the parent claim", null); - //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()]); - - //support color codes - if(messageID != Messages.HowToClaimRegex) - { - this.messages[messageID.ordinal()] = this.messages[messageID.ordinal()].replace('$', (char)0x00A7); - } - - 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.options().header("Use a YAML editor like NotepadPlusPlus to edit this file. \nAfter editing, back up your changes before reloading the server in case you made a syntax error. \nUse dollar signs ($) for formatting codes, which are documented here: http://minecraft.gamepedia.com/Formatting_codes"); - config.save(DataStore.messagesFilePath); - } - catch(IOException exception) - { - GriefPrevention.AddLogEntry("Unable to write to the configuration file at \"" + DataStore.messagesFilePath + "\""); - } - - defaults.clear(); - System.gc(); - } + this.addDefault(defaults, Messages.NetherPortalTrapDetectionMessage, "It seems you might be stuck inside a nether portal. We will rescue you in a few seconds if that is the case!", "Sent to player on join, if they left while inside a nether portal."); - private void addDefault(HashMap defaults, - Messages id, String text, String notes) - { - CustomizableMessage message = new CustomizableMessage(id, text, notes); - defaults.put(id.name(), message); - } + //load the config file + FileConfiguration config = YamlConfiguration.loadConfiguration(new File(messagesFilePath)); - synchronized 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); - } + //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()); - return message; - } - - //used in updating the data schema from 0 to 1. - //converts player names in a list to uuids - protected List convertNameListToUUIDList(List names) - { - //doesn't apply after schema has been updated to version 1 - if(this.getSchemaVersion() >= 1) return names; - - //list to build results - List resultNames = new ArrayList(); - - for(String name : names) - { - //skip non-player-names (groups and "public"), leave them as-is - if(name.startsWith("[") || name.equals("public")) + //if default is missing, log an error and use some fake data for now so that the plugin can run + if (messageData == null) { - resultNames.add(name); - continue; + 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); } - - //otherwise try to convert to a UUID - UUID playerID = null; - try - { - playerID = UUIDFetcher.getUUIDOf(name); - } - catch(Exception ex){ } - - //if successful, replace player name with corresponding UUID - if(playerID != null) - { - resultNames.add(playerID.toString()); - } - } - - return resultNames; + + //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()]); + + //support color codes + if (messageID != Messages.HowToClaimRegex) + { + this.messages[messageID.ordinal()] = this.messages[messageID.ordinal()].replace('$', (char) 0x00A7); + } + + 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.options().header("Use a YAML editor like NotepadPlusPlus to edit this file. \nAfter editing, back up your changes before reloading the server in case you made a syntax error. \nUse dollar signs ($) for formatting codes, which are documented here: http://minecraft.gamepedia.com/Formatting_codes"); + 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); + } + + synchronized 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; + } + + //used in updating the data schema from 0 to 1. + //converts player names in a list to uuids + protected List convertNameListToUUIDList(List names) + { + //doesn't apply after schema has been updated to version 1 + if (this.getSchemaVersion() >= 1) return names; + + //list to build results + List resultNames = new ArrayList(); + + for (String name : names) + { + //skip non-player-names (groups and "public"), leave them as-is + if (name.startsWith("[") || name.equals("public")) + { + resultNames.add(name); + continue; + } + + //otherwise try to convert to a UUID + UUID playerID = null; + try + { + playerID = UUIDFetcher.getUUIDOf(name); + } + catch (Exception ex) { } + + //if successful, replace player name with corresponding UUID + if (playerID != null) + { + resultNames.add(playerID.toString()); + } + } + + return resultNames; + } + + abstract void close(); + + private class SavePlayerDataThread extends Thread + { + private UUID playerID; + private PlayerData playerData; + + SavePlayerDataThread(UUID playerID, PlayerData playerData) + { + this.playerID = playerID; + this.playerData = playerData; + } + + public void run() + { + //ensure player data is already read from file before trying to save + playerData.getAccruedClaimBlocks(); + playerData.getClaims(); + asyncSavePlayerData(this.playerID, this.playerData); + } } - - abstract void close(); - - private class SavePlayerDataThread extends Thread - { - private UUID playerID; - private PlayerData playerData; - - SavePlayerDataThread(UUID playerID, PlayerData playerData) - { - this.playerID = playerID; - this.playerData = playerData; - } - - public void run() - { - //ensure player data is already read from file before trying to save - playerData.getAccruedClaimBlocks(); - playerData.getClaims(); - asyncSavePlayerData(this.playerID, this.playerData); - } - } //gets all the claims "near" a location - Set getNearbyClaims(Location location) + Set getNearbyClaims(Location location) { Set claims = new HashSet(); - + Chunk lesserChunk = location.getWorld().getChunkAt(location.subtract(150, 0, 150)); Chunk greaterChunk = location.getWorld().getChunkAt(location.add(300, 0, 300)); - - for(int chunk_x = lesserChunk.getX(); chunk_x <= greaterChunk.getX(); chunk_x++) + + for (int chunk_x = lesserChunk.getX(); chunk_x <= greaterChunk.getX(); chunk_x++) { - for(int chunk_z = lesserChunk.getZ(); chunk_z <= greaterChunk.getZ(); chunk_z++) + for (int chunk_z = lesserChunk.getZ(); chunk_z <= greaterChunk.getZ(); chunk_z++) { Chunk chunk = location.getWorld().getChunkAt(chunk_x, chunk_z); - Long chunkID = getChunkHash(chunk.getBlock(0, 0, 0).getLocation()); + Long chunkID = getChunkHash(chunk.getBlock(0, 0, 0).getLocation()); ArrayList claimsInChunk = this.chunksToClaimsMap.get(chunkID); - if(claimsInChunk != null) + if (claimsInChunk != null) { - for(Claim claim : claimsInChunk) + for (Claim claim : claimsInChunk) { - if(claim.inDataStore && claim.getLesserBoundaryCorner().getWorld().equals(location.getWorld())) + if (claim.inDataStore && claim.getLesserBoundaryCorner().getWorld().equals(location.getWorld())) { claims.add(claim); } @@ -1844,22 +1843,22 @@ public abstract class DataStore } } } - + return claims; } - - //deletes all the land claims in a specified world - void deleteClaimsInWorld(World world, boolean deleteAdminClaims) - { - for(int i = 0; i < claims.size(); i++) - { - Claim claim = claims.get(i); - if(claim.getLesserBoundaryCorner().getWorld().equals(world)) - { - if(!deleteAdminClaims && claim.isAdminClaim()) continue; - this.deleteClaim(claim, false, false); - i--; - } - } + + //deletes all the land claims in a specified world + void deleteClaimsInWorld(World world, boolean deleteAdminClaims) + { + for (int i = 0; i < claims.size(); i++) + { + Claim claim = claims.get(i); + if (claim.getLesserBoundaryCorner().getWorld().equals(world)) + { + if (!deleteAdminClaims && claim.isAdminClaim()) continue; + this.deleteClaim(claim, false, false); + i--; + } + } } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java b/src/main/java/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java index d05ae6e..89765aa 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java @@ -18,720 +18,752 @@ package me.ryanhamshire.GriefPrevention; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; + import java.io.PrintWriter; import java.io.StringWriter; -import java.sql.*; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; - -import org.bukkit.*; +import java.util.HashMap; +import java.util.List; +import java.util.Properties; +import java.util.UUID; //manages data stored in the file system public class DatabaseDataStore extends DataStore { - private Connection databaseConnection = null; - - private String databaseUrl; - private String userName; - private String password; - - private String updateNameSQL; - private String insertClaimSQL; - private String deleteClaimSQL; - private String getPlayerDataSQL; - private String deletePlayerDataSQL; - private String insertPlayerDataSQL; - private String insertNextClaimIdSQL; - private String deleteGroupBonusSQL; - private String insertSchemaVerSQL; - private String deleteNextClaimIdSQL; - private String deleteSchemaVersionSQL; - private String selectSchemaVersionSQL; - - DatabaseDataStore(String url, String userName, String password) throws Exception - { - this.databaseUrl = url; - this.userName = userName; - this.password = password; - - this.initialize(); - } - - @Override - void initialize() throws Exception - { - try - { - //load the java driver for mySQL - Class.forName("com.mysql.jdbc.Driver"); - } - catch(Exception e) - { - GriefPrevention.AddLogEntry("ERROR: Unable to load Java's mySQL database driver. Check to make sure you've installed it properly."); - throw e; - } - - try - { - this.refreshDataConnection(); - } - catch(Exception e2) - { - GriefPrevention.AddLogEntry("ERROR: Unable to connect to database. Check your config file settings."); - throw e2; - } - - try - { - //ensure the data tables exist - Statement statement = databaseConnection.createStatement(); - - statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_nextclaimid (nextid INT(15));"); - - statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_claimdata (id INT(15), owner VARCHAR(50), lessercorner VARCHAR(100), greatercorner VARCHAR(100), builders TEXT, containers TEXT, accessors TEXT, managers TEXT, inheritnothing BOOLEAN, parentid INT(15));"); - - statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_playerdata (name VARCHAR(50), lastlogin DATETIME, accruedblocks INT(15), bonusblocks INT(15));"); - - statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_schemaversion (version INT(15));"); - - statement.execute("ALTER TABLE griefprevention_claimdata MODIFY builders TEXT;"); - statement.execute("ALTER TABLE griefprevention_claimdata MODIFY containers TEXT;"); - statement.execute("ALTER TABLE griefprevention_claimdata MODIFY accessors TEXT;"); - statement.execute("ALTER TABLE griefprevention_claimdata MODIFY managers TEXT;"); - - //if the next claim id table is empty, this is a brand new database which will write using the latest schema - //otherwise, schema version is determined by schemaversion table (or =0 if table is empty, see getSchemaVersion()) - ResultSet results = statement.executeQuery("SELECT * FROM griefprevention_nextclaimid;"); - if(!results.next()) - { - this.setSchemaVersion(latestSchemaVersion); - } - } - catch(Exception e3) - { - GriefPrevention.AddLogEntry("ERROR: Unable to create the necessary database table. Details:"); - GriefPrevention.AddLogEntry(e3.getMessage()); - e3.printStackTrace(); - throw e3; - } - - this.updateNameSQL = "UPDATE griefprevention_playerdata SET name = ? WHERE name = ?;"; - this.insertClaimSQL = "INSERT INTO griefprevention_claimdata (id, owner, lessercorner, greatercorner, builders, containers, accessors, managers, inheritnothing, parentid) VALUES(?,?,?,?,?,?,?,?,?,?);"; - this.deleteClaimSQL = "DELETE FROM griefprevention_claimdata WHERE id=?;"; - this.getPlayerDataSQL = "SELECT * FROM griefprevention_playerdata WHERE name=?;"; - this.deletePlayerDataSQL = "DELETE FROM griefprevention_playerdata WHERE name=?;"; - this.insertPlayerDataSQL = "INSERT INTO griefprevention_playerdata (name, lastlogin, accruedblocks, bonusblocks) VALUES (?,?,?,?);"; - this.insertNextClaimIdSQL = "INSERT INTO griefprevention_nextclaimid VALUES (?);"; - this.deleteGroupBonusSQL = "DELETE FROM griefprevention_playerdata WHERE name=?;"; - this.insertSchemaVerSQL = "INSERT INTO griefprevention_schemaversion VALUES (?)"; - this.deleteNextClaimIdSQL = "DELETE FROM griefprevention_nextclaimid;"; - this.deleteSchemaVersionSQL = "DELETE FROM griefprevention_schemaversion;"; - this.selectSchemaVersionSQL = "SELECT * FROM griefprevention_schemaversion;"; - - //load group data into memory - Statement statement = databaseConnection.createStatement(); - ResultSet results = statement.executeQuery("SELECT * FROM griefprevention_playerdata;"); - - while(results.next()) - { - String name = results.getString("name"); - - //ignore non-groups. all group names start with a dollar sign. - if(!name.startsWith("$")) continue; - - String groupName = name.substring(1); - if(groupName == null || groupName.isEmpty()) continue; //defensive coding, avoid unlikely cases - - int groupBonusBlocks = results.getInt("bonusblocks"); - - this.permissionToBonusBlocksMap.put(groupName, groupBonusBlocks); - } - - //load next claim number into memory - results = statement.executeQuery("SELECT * FROM griefprevention_nextclaimid;"); - - //if there's nothing yet, add it - if(!results.next()) - { - statement.execute("INSERT INTO griefprevention_nextclaimid VALUES(0);"); - this.nextClaimID = (long)0; - } - - //otherwise load it - else - { - this.nextClaimID = results.getLong("nextid"); - } - - if(this.getSchemaVersion() == 0) - { - try - { - this.refreshDataConnection(); - - //pull ALL player data from the database - statement = this.databaseConnection.createStatement(); - results = statement.executeQuery("SELECT * FROM griefprevention_playerdata;"); - - //make a list of changes to be made - HashMap changes = new HashMap(); - - ArrayList namesToConvert = new ArrayList(); - while(results.next()) - { - //get the id - String playerName = results.getString("name"); - - //add to list of names to convert to UUID - namesToConvert.add(playerName); - } - - //resolve and cache as many as possible through various means - try - { - UUIDFetcher fetcher = new UUIDFetcher(namesToConvert); - fetcher.call(); - } - catch(Exception e) - { - GriefPrevention.AddLogEntry("Failed to resolve a batch of names to UUIDs. Details:" + e.getMessage()); - e.printStackTrace(); - } - - //reset results cursor - results.beforeFirst(); - - //for each result - while(results.next()) - { - //get the id - String playerName = results.getString("name"); - - //try to convert player name to UUID - try - { - UUID playerID = UUIDFetcher.getUUIDOf(playerName); - - //if successful, update the playerdata row by replacing the player's name with the player's UUID - if(playerID != null) - { - changes.put(playerName, playerID); - } - } - //otherwise leave it as-is. no harm done - it won't be requested by name, and this update only happens once. - catch(Exception ex){ } - } - - //refresh data connection in case data migration took a long time - this.refreshDataConnection(); - - for(String name : changes.keySet()) - { - try (PreparedStatement updateStmnt = this.databaseConnection.prepareStatement(this.getUpdateNameSQL())) { - updateStmnt.setString(1, changes.get(name).toString()); - updateStmnt.setString(2, name); - updateStmnt.executeUpdate(); - } - catch(SQLException e) - { - GriefPrevention.AddLogEntry("Unable to convert player data for " + name + ". Skipping."); - GriefPrevention.AddLogEntry(e.getMessage()); - } - } - } - catch(SQLException e) - { - GriefPrevention.AddLogEntry("Unable to convert player data. Details:"); - GriefPrevention.AddLogEntry(e.getMessage()); - e.printStackTrace(); - } - } - - if(this.getSchemaVersion() <= 2) - { - statement = this.databaseConnection.createStatement(); - statement.execute("ALTER TABLE griefprevention_claimdata ADD inheritNothing BOOLEAN DEFAULT 0 AFTER managers;"); - } - - //load claims data into memory - - results = statement.executeQuery("SELECT * FROM griefprevention_claimdata;"); - - ArrayList claimsToRemove = new ArrayList(); - ArrayList subdivisionsToLoad = new ArrayList(); - List validWorlds = Bukkit.getServer().getWorlds(); - - Long claimID = null; - while(results.next()) - { - try - { - //problematic claims will be removed from secondary storage, and never added to in-memory data store - boolean removeClaim = false; - - long parentId = results.getLong("parentid"); - claimID = results.getLong("id"); - boolean inheritNothing = results.getBoolean("inheritNothing"); - Location lesserBoundaryCorner = null; - Location greaterBoundaryCorner = null; - String lesserCornerString = "(location not available)"; - try - { - lesserCornerString = results.getString("lessercorner"); - lesserBoundaryCorner = this.locationFromString(lesserCornerString, validWorlds); - String greaterCornerString = results.getString("greatercorner"); - greaterBoundaryCorner = this.locationFromString(greaterCornerString, validWorlds); - } - catch(Exception e) - { - if(e.getMessage() != null && e.getMessage().contains("World not found")) - { - GriefPrevention.AddLogEntry("Failed to load a claim (ID:" + claimID.toString() + ") because its world isn't loaded (yet?). Please delete the claim or contact the GriefPrevention developer with information about which plugin(s) you're using to load or create worlds. " + lesserCornerString); - continue; - } - else - { - throw e; - } - } - - String ownerName = results.getString("owner"); - UUID ownerID = null; - if(ownerName.isEmpty() || ownerName.startsWith("--")) - { - ownerID = null; //administrative land claim or subdivision - } - else if(this.getSchemaVersion() < 1) - { - try - { - ownerID = UUIDFetcher.getUUIDOf(ownerName); - } - catch(Exception ex) - { - GriefPrevention.AddLogEntry("This owner name did not convert to a UUID: " + ownerName + "."); - GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString()); - } - } - else - { - try - { - ownerID = UUID.fromString(ownerName); - } - catch(Exception ex) - { - GriefPrevention.AddLogEntry("This owner entry is not a UUID: " + ownerName + "."); - GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString()); - } - } - - String buildersString = results.getString("builders"); - List builderNames = Arrays.asList(buildersString.split(";")); - builderNames = this.convertNameListToUUIDList(builderNames); - - String containersString = results.getString("containers"); - List containerNames = Arrays.asList(containersString.split(";")); - containerNames = this.convertNameListToUUIDList(containerNames); - - String accessorsString = results.getString("accessors"); - List accessorNames = Arrays.asList(accessorsString.split(";")); - accessorNames = this.convertNameListToUUIDList(accessorNames); - - String managersString = results.getString("managers"); - List managerNames = Arrays.asList(managersString.split(";")); - managerNames = this.convertNameListToUUIDList(managerNames); - Claim claim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builderNames, containerNames, accessorNames, managerNames, inheritNothing, claimID); - - if(removeClaim) - { - claimsToRemove.add(claim); - } - else if(parentId == -1) - { - //top level claim - this.addClaim(claim, false); - } - else - { - //subdivision - subdivisionsToLoad.add(claim); - } - } - catch(SQLException e) - { - GriefPrevention.AddLogEntry("Unable to load a claim. Details: " + e.getMessage() + " ... " + results.toString()); - e.printStackTrace(); - } - } - - //add subdivisions to their parent claims - for(Claim childClaim : subdivisionsToLoad) - { - //find top level claim parent - Claim topLevelClaim = this.getClaimAt(childClaim.getLesserBoundaryCorner(), true, null); - - if(topLevelClaim == null) - { - claimsToRemove.add(childClaim); - GriefPrevention.AddLogEntry("Removing orphaned claim subdivision: " + childClaim.getLesserBoundaryCorner().toString()); - continue; - } - - //add this claim to the list of children of the current top level claim - childClaim.parent = topLevelClaim; - topLevelClaim.children.add(childClaim); - childClaim.inDataStore = true; - } - - for(Claim claim : claimsToRemove) - { - this.deleteClaimFromSecondaryStorage(claim); - } - - if(this.getSchemaVersion() <= 2) - { - this.refreshDataConnection(); - statement = this.databaseConnection.createStatement(); - statement.execute("DELETE FROM griefprevention_claimdata WHERE id='-1';"); - } - - super.initialize(); - } - - @Override - synchronized void writeClaimToStorage(Claim claim) //see datastore.cs. this will ALWAYS be a top level claim - { - try - { - this.refreshDataConnection(); - - //wipe out any existing data about this claim - this.deleteClaimFromSecondaryStorage(claim); - - //write claim data to the database - this.writeClaimData(claim); - } - catch(SQLException e) - { - GriefPrevention.AddLogEntry("Unable to save data for claim at " + this.locationToString(claim.lesserBoundaryCorner) + ". Details:"); - GriefPrevention.AddLogEntry(e.getMessage()); - } - } - - //actually writes claim data to the database - synchronized private void writeClaimData(Claim claim) throws SQLException - { - String lesserCornerString = this.locationToString(claim.getLesserBoundaryCorner()); - String greaterCornerString = this.locationToString(claim.getGreaterBoundaryCorner()); - String owner = ""; - if(claim.ownerID != null) owner = claim.ownerID.toString(); - - ArrayList builders = new ArrayList(); - ArrayList containers = new ArrayList(); - ArrayList accessors = new ArrayList(); - ArrayList managers = new ArrayList(); - - claim.getPermissions(builders, containers, accessors, managers); - - String buildersString = this.storageStringBuilder(builders); - String containersString = this.storageStringBuilder(containers); - String accessorsString = this.storageStringBuilder(accessors); - String managersString = this.storageStringBuilder(managers); - boolean inheritNothing = claim.getSubclaimRestrictions(); - long parentId = claim.parent == null ? -1 : claim.parent.id; - - try (PreparedStatement insertStmt = this.databaseConnection.prepareStatement(this.getInsertClaimSQL())) { - - insertStmt.setLong(1, claim.id); - insertStmt.setString(2, owner); - insertStmt.setString(3, lesserCornerString); - insertStmt.setString(4, greaterCornerString); - insertStmt.setString(5, buildersString); - insertStmt.setString(6, containersString); - insertStmt.setString(7, accessorsString); - insertStmt.setString(8, managersString); - insertStmt.setBoolean(9, inheritNothing); - insertStmt.setLong(10, parentId); - insertStmt.executeUpdate(); - } - catch(SQLException e) - { - GriefPrevention.AddLogEntry("Unable to save data for claim at " + this.locationToString(claim.lesserBoundaryCorner) + ". Details:"); - GriefPrevention.AddLogEntry(e.getMessage()); - } - } - - //deletes a claim from the database - @Override - synchronized void deleteClaimFromSecondaryStorage(Claim claim) - { - try(PreparedStatement deleteStmnt = this.databaseConnection.prepareStatement(this.getDeleteClaimSQL())) { - deleteStmnt.setLong(1, claim.id); - deleteStmnt.executeUpdate(); - } - catch(SQLException e) - { - GriefPrevention.AddLogEntry("Unable to delete data for claim " + claim.id + ". Details:"); - GriefPrevention.AddLogEntry(e.getMessage()); - e.printStackTrace(); - } - } - - @Override - PlayerData getPlayerDataFromStorage(UUID playerID) - { - PlayerData playerData = new PlayerData(); - playerData.playerID = playerID; - - try (PreparedStatement selectStmnt = this.databaseConnection.prepareStatement( this.getGetPlayerDataSQL())) - { - selectStmnt.setString(1, playerID.toString()); - ResultSet results = selectStmnt.executeQuery(); - - //if data for this player exists, use it - if(results.next()) - { - playerData.setAccruedClaimBlocks(results.getInt("accruedblocks")); - playerData.setBonusClaimBlocks(results.getInt("bonusblocks")); - } - } - catch(SQLException e) - { - StringWriter errors = new StringWriter(); - e.printStackTrace(new PrintWriter(errors)); - GriefPrevention.AddLogEntry(playerID + " " + errors.toString(), CustomLogEntryTypes.Exception); - } - - return playerData; - } - - //saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them - @Override - public void overrideSavePlayerData(UUID playerID, PlayerData playerData) - { - //never save data for the "administrative" account. an empty string for player name indicates administrative account - if(playerID == null) return; - - this.savePlayerData(playerID.toString(), playerData); - } - - private void savePlayerData(String playerID, PlayerData playerData) - { - try (PreparedStatement deleteStmnt = this.databaseConnection.prepareStatement(this.getDeletePlayerDataSQL()); - PreparedStatement insertStmnt = this.databaseConnection.prepareStatement(this.getInsertPlayerDataSQL())) { - OfflinePlayer player = Bukkit.getOfflinePlayer(UUID.fromString(playerID)); - - SimpleDateFormat sqlFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - String dateString = sqlFormat.format(new Date(player.getLastPlayed())); - deleteStmnt.setString(1, playerID); - deleteStmnt.executeUpdate(); - - insertStmnt.setString(1, playerID); - insertStmnt.setString(2, dateString); - insertStmnt.setInt(3, playerData.getAccruedClaimBlocks()); - insertStmnt.setInt(4, playerData.getBonusClaimBlocks()); - insertStmnt.executeUpdate(); - } - catch(SQLException e) - { - StringWriter errors = new StringWriter(); - e.printStackTrace(new PrintWriter(errors)); - GriefPrevention.AddLogEntry(playerID + " " + errors.toString(), CustomLogEntryTypes.Exception); - } - } - - @Override - synchronized void incrementNextClaimID() - { - this.setNextClaimID(this.nextClaimID + 1); - } - - //sets the next claim ID. used by incrementNextClaimID() above, and also while migrating data from a flat file data store - synchronized void setNextClaimID(long nextID) - { - this.nextClaimID = nextID; - - try (PreparedStatement deleteStmnt = this.databaseConnection.prepareStatement(this.getDeleteNextClaimIdSQL()); - PreparedStatement insertStmnt = this.databaseConnection.prepareStatement(this.getInsertNextClaimIdSQL())) { - deleteStmnt.execute(); - insertStmnt.setLong(1, nextID); - insertStmnt.executeUpdate(); - } - catch(SQLException e) - { - GriefPrevention.AddLogEntry("Unable to set next claim ID to " + nextID + ". Details:"); - GriefPrevention.AddLogEntry(e.getMessage()); - } - } - - //updates the database with a group's bonus blocks - @Override - synchronized void saveGroupBonusBlocks(String groupName, int currentValue) - { - //group bonus blocks are stored in the player data table, with player name = $groupName - try (PreparedStatement deleteStmnt = this.databaseConnection.prepareStatement(this.getDeleteGroupBonusSQL()); - PreparedStatement insertStmnt = this.databaseConnection.prepareStatement(this.getInsertPlayerDataSQL())) { - SimpleDateFormat sqlFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - String dateString = sqlFormat.format(new Date()); - deleteStmnt.setString(1, '$' + groupName); - deleteStmnt.executeUpdate(); - - insertStmnt.setString(1, '$' + groupName); - insertStmnt.setString(2, dateString); - insertStmnt.setInt(3, 0); - insertStmnt.setInt(4, currentValue); - insertStmnt.executeUpdate(); - } - catch(SQLException e) - { - GriefPrevention.AddLogEntry("Unable to save data for group " + groupName + ". Details:"); - GriefPrevention.AddLogEntry(e.getMessage()); - } - } - - @Override - synchronized void close() - { - if(this.databaseConnection != null) - { - try - { - if(!this.databaseConnection.isClosed()) - { - this.databaseConnection.close(); - } - } - catch(SQLException e){}; - } - - this.databaseConnection = null; - } - - private synchronized void refreshDataConnection() throws SQLException - { - if(this.databaseConnection == null || !this.databaseConnection.isValid(3)) - { - if(this.databaseConnection != null && !this.databaseConnection.isClosed()) - { - this.databaseConnection.close(); - } - - //set username/pass properties - Properties connectionProps = new Properties(); - connectionProps.put("user", this.userName); - connectionProps.put("password", this.password); - connectionProps.put("autoReconnect", "true"); - connectionProps.put("maxReconnects", String.valueOf(Integer.MAX_VALUE)); - - //establish connection - this.databaseConnection = DriverManager.getConnection(this.databaseUrl, connectionProps); - } - } - - @Override - protected int getSchemaVersionFromStorage() - { - try (PreparedStatement selectStmnt = this.databaseConnection.prepareStatement(this.getSelectSchemaVersionSQL())) { - ResultSet results = selectStmnt.executeQuery(); - - //if there's nothing yet, assume 0 and add it - if(!results.next()) - { - this.setSchemaVersion(0); - return 0; - } - //otherwise return the value that's in the table - else - { - return results.getInt("version"); - } - } - catch(SQLException e) - { - GriefPrevention.AddLogEntry("Unable to retrieve schema version from database. Details:"); - GriefPrevention.AddLogEntry(e.getMessage()); - e.printStackTrace(); - return 0; - } - } - - @Override - protected void updateSchemaVersionInStorage(int versionToSet) - { - try (PreparedStatement deleteStmnt = this.databaseConnection.prepareStatement(this.getDeleteSchemaVersionSQL()); - PreparedStatement insertStmnt = this.databaseConnection.prepareStatement(this.getInsertSchemaVerSQL())) { - deleteStmnt.execute(); - - insertStmnt.setInt(1, versionToSet); - insertStmnt.executeUpdate(); - } - catch(SQLException e) - { - GriefPrevention.AddLogEntry("Unable to set next schema version to " + versionToSet + ". Details:"); - GriefPrevention.AddLogEntry(e.getMessage()); - } - } - - /** - * Concats an array to a string divided with the ; sign - * @param input Arraylist with strings to concat - * @return String with all values from input array - */ - private String storageStringBuilder(ArrayList input) { - String output = ""; - for(String string : input) { - output += string + ";"; - } - return output; - } - - public String getUpdateNameSQL() { - return updateNameSQL; - } - - public String getInsertClaimSQL() { - return insertClaimSQL; - } - - public String getDeleteClaimSQL() { - return deleteClaimSQL; - } - - public String getGetPlayerDataSQL() { - return getPlayerDataSQL; - } - - public String getDeletePlayerDataSQL() { - return deletePlayerDataSQL; - } - - public String getInsertPlayerDataSQL() { - return insertPlayerDataSQL; - } - - public String getInsertNextClaimIdSQL() { - return insertNextClaimIdSQL; - } - - public String getDeleteGroupBonusSQL() { - return deleteGroupBonusSQL; - } - - public String getInsertSchemaVerSQL() { - return insertSchemaVerSQL; - } - - public String getDeleteNextClaimIdSQL() { - return deleteNextClaimIdSQL; - } - - public String getDeleteSchemaVersionSQL() { - return deleteSchemaVersionSQL; - } - - public String getSelectSchemaVersionSQL() { - return selectSchemaVersionSQL; - } + private Connection databaseConnection = null; + + private String databaseUrl; + private String userName; + private String password; + + private String updateNameSQL; + private String insertClaimSQL; + private String deleteClaimSQL; + private String getPlayerDataSQL; + private String deletePlayerDataSQL; + private String insertPlayerDataSQL; + private String insertNextClaimIdSQL; + private String deleteGroupBonusSQL; + private String insertSchemaVerSQL; + private String deleteNextClaimIdSQL; + private String deleteSchemaVersionSQL; + private String selectSchemaVersionSQL; + + DatabaseDataStore(String url, String userName, String password) throws Exception + { + this.databaseUrl = url; + this.userName = userName; + this.password = password; + + this.initialize(); + } + + @Override + void initialize() throws Exception + { + try + { + //load the java driver for mySQL + Class.forName("com.mysql.jdbc.Driver"); + } + catch (Exception e) + { + GriefPrevention.AddLogEntry("ERROR: Unable to load Java's mySQL database driver. Check to make sure you've installed it properly."); + throw e; + } + + try + { + this.refreshDataConnection(); + } + catch (Exception e2) + { + GriefPrevention.AddLogEntry("ERROR: Unable to connect to database. Check your config file settings."); + throw e2; + } + + try + { + //ensure the data tables exist + Statement statement = databaseConnection.createStatement(); + + statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_nextclaimid (nextid INT(15));"); + + statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_claimdata (id INT(15), owner VARCHAR(50), lessercorner VARCHAR(100), greatercorner VARCHAR(100), builders TEXT, containers TEXT, accessors TEXT, managers TEXT, inheritnothing BOOLEAN, parentid INT(15));"); + + statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_playerdata (name VARCHAR(50), lastlogin DATETIME, accruedblocks INT(15), bonusblocks INT(15));"); + + statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_schemaversion (version INT(15));"); + + statement.execute("ALTER TABLE griefprevention_claimdata MODIFY builders TEXT;"); + statement.execute("ALTER TABLE griefprevention_claimdata MODIFY containers TEXT;"); + statement.execute("ALTER TABLE griefprevention_claimdata MODIFY accessors TEXT;"); + statement.execute("ALTER TABLE griefprevention_claimdata MODIFY managers TEXT;"); + + //if the next claim id table is empty, this is a brand new database which will write using the latest schema + //otherwise, schema version is determined by schemaversion table (or =0 if table is empty, see getSchemaVersion()) + ResultSet results = statement.executeQuery("SELECT * FROM griefprevention_nextclaimid;"); + if (!results.next()) + { + this.setSchemaVersion(latestSchemaVersion); + } + } + catch (Exception e3) + { + GriefPrevention.AddLogEntry("ERROR: Unable to create the necessary database table. Details:"); + GriefPrevention.AddLogEntry(e3.getMessage()); + e3.printStackTrace(); + throw e3; + } + + this.updateNameSQL = "UPDATE griefprevention_playerdata SET name = ? WHERE name = ?;"; + this.insertClaimSQL = "INSERT INTO griefprevention_claimdata (id, owner, lessercorner, greatercorner, builders, containers, accessors, managers, inheritnothing, parentid) VALUES(?,?,?,?,?,?,?,?,?,?);"; + this.deleteClaimSQL = "DELETE FROM griefprevention_claimdata WHERE id=?;"; + this.getPlayerDataSQL = "SELECT * FROM griefprevention_playerdata WHERE name=?;"; + this.deletePlayerDataSQL = "DELETE FROM griefprevention_playerdata WHERE name=?;"; + this.insertPlayerDataSQL = "INSERT INTO griefprevention_playerdata (name, lastlogin, accruedblocks, bonusblocks) VALUES (?,?,?,?);"; + this.insertNextClaimIdSQL = "INSERT INTO griefprevention_nextclaimid VALUES (?);"; + this.deleteGroupBonusSQL = "DELETE FROM griefprevention_playerdata WHERE name=?;"; + this.insertSchemaVerSQL = "INSERT INTO griefprevention_schemaversion VALUES (?)"; + this.deleteNextClaimIdSQL = "DELETE FROM griefprevention_nextclaimid;"; + this.deleteSchemaVersionSQL = "DELETE FROM griefprevention_schemaversion;"; + this.selectSchemaVersionSQL = "SELECT * FROM griefprevention_schemaversion;"; + + //load group data into memory + Statement statement = databaseConnection.createStatement(); + ResultSet results = statement.executeQuery("SELECT * FROM griefprevention_playerdata;"); + + while (results.next()) + { + String name = results.getString("name"); + + //ignore non-groups. all group names start with a dollar sign. + if (!name.startsWith("$")) continue; + + String groupName = name.substring(1); + if (groupName == null || groupName.isEmpty()) continue; //defensive coding, avoid unlikely cases + + int groupBonusBlocks = results.getInt("bonusblocks"); + + this.permissionToBonusBlocksMap.put(groupName, groupBonusBlocks); + } + + //load next claim number into memory + results = statement.executeQuery("SELECT * FROM griefprevention_nextclaimid;"); + + //if there's nothing yet, add it + if (!results.next()) + { + statement.execute("INSERT INTO griefprevention_nextclaimid VALUES(0);"); + this.nextClaimID = (long) 0; + } + + //otherwise load it + else + { + this.nextClaimID = results.getLong("nextid"); + } + + if (this.getSchemaVersion() == 0) + { + try + { + this.refreshDataConnection(); + + //pull ALL player data from the database + statement = this.databaseConnection.createStatement(); + results = statement.executeQuery("SELECT * FROM griefprevention_playerdata;"); + + //make a list of changes to be made + HashMap changes = new HashMap(); + + ArrayList namesToConvert = new ArrayList(); + while (results.next()) + { + //get the id + String playerName = results.getString("name"); + + //add to list of names to convert to UUID + namesToConvert.add(playerName); + } + + //resolve and cache as many as possible through various means + try + { + UUIDFetcher fetcher = new UUIDFetcher(namesToConvert); + fetcher.call(); + } + catch (Exception e) + { + GriefPrevention.AddLogEntry("Failed to resolve a batch of names to UUIDs. Details:" + e.getMessage()); + e.printStackTrace(); + } + + //reset results cursor + results.beforeFirst(); + + //for each result + while (results.next()) + { + //get the id + String playerName = results.getString("name"); + + //try to convert player name to UUID + try + { + UUID playerID = UUIDFetcher.getUUIDOf(playerName); + + //if successful, update the playerdata row by replacing the player's name with the player's UUID + if (playerID != null) + { + changes.put(playerName, playerID); + } + } + //otherwise leave it as-is. no harm done - it won't be requested by name, and this update only happens once. + catch (Exception ex) { } + } + + //refresh data connection in case data migration took a long time + this.refreshDataConnection(); + + for (String name : changes.keySet()) + { + try (PreparedStatement updateStmnt = this.databaseConnection.prepareStatement(this.getUpdateNameSQL())) + { + updateStmnt.setString(1, changes.get(name).toString()); + updateStmnt.setString(2, name); + updateStmnt.executeUpdate(); + } + catch (SQLException e) + { + GriefPrevention.AddLogEntry("Unable to convert player data for " + name + ". Skipping."); + GriefPrevention.AddLogEntry(e.getMessage()); + } + } + } + catch (SQLException e) + { + GriefPrevention.AddLogEntry("Unable to convert player data. Details:"); + GriefPrevention.AddLogEntry(e.getMessage()); + e.printStackTrace(); + } + } + + if (this.getSchemaVersion() <= 2) + { + statement = this.databaseConnection.createStatement(); + statement.execute("ALTER TABLE griefprevention_claimdata ADD inheritNothing BOOLEAN DEFAULT 0 AFTER managers;"); + } + + //load claims data into memory + + results = statement.executeQuery("SELECT * FROM griefprevention_claimdata;"); + + ArrayList claimsToRemove = new ArrayList(); + ArrayList subdivisionsToLoad = new ArrayList(); + List validWorlds = Bukkit.getServer().getWorlds(); + + Long claimID = null; + while (results.next()) + { + try + { + //problematic claims will be removed from secondary storage, and never added to in-memory data store + boolean removeClaim = false; + + long parentId = results.getLong("parentid"); + claimID = results.getLong("id"); + boolean inheritNothing = results.getBoolean("inheritNothing"); + Location lesserBoundaryCorner = null; + Location greaterBoundaryCorner = null; + String lesserCornerString = "(location not available)"; + try + { + lesserCornerString = results.getString("lessercorner"); + lesserBoundaryCorner = this.locationFromString(lesserCornerString, validWorlds); + String greaterCornerString = results.getString("greatercorner"); + greaterBoundaryCorner = this.locationFromString(greaterCornerString, validWorlds); + } + catch (Exception e) + { + if (e.getMessage() != null && e.getMessage().contains("World not found")) + { + GriefPrevention.AddLogEntry("Failed to load a claim (ID:" + claimID.toString() + ") because its world isn't loaded (yet?). Please delete the claim or contact the GriefPrevention developer with information about which plugin(s) you're using to load or create worlds. " + lesserCornerString); + continue; + } else + { + throw e; + } + } + + String ownerName = results.getString("owner"); + UUID ownerID = null; + if (ownerName.isEmpty() || ownerName.startsWith("--")) + { + ownerID = null; //administrative land claim or subdivision + } else if (this.getSchemaVersion() < 1) + { + try + { + ownerID = UUIDFetcher.getUUIDOf(ownerName); + } + catch (Exception ex) + { + GriefPrevention.AddLogEntry("This owner name did not convert to a UUID: " + ownerName + "."); + GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString()); + } + } else + { + try + { + ownerID = UUID.fromString(ownerName); + } + catch (Exception ex) + { + GriefPrevention.AddLogEntry("This owner entry is not a UUID: " + ownerName + "."); + GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString()); + } + } + + String buildersString = results.getString("builders"); + List builderNames = Arrays.asList(buildersString.split(";")); + builderNames = this.convertNameListToUUIDList(builderNames); + + String containersString = results.getString("containers"); + List containerNames = Arrays.asList(containersString.split(";")); + containerNames = this.convertNameListToUUIDList(containerNames); + + String accessorsString = results.getString("accessors"); + List accessorNames = Arrays.asList(accessorsString.split(";")); + accessorNames = this.convertNameListToUUIDList(accessorNames); + + String managersString = results.getString("managers"); + List managerNames = Arrays.asList(managersString.split(";")); + managerNames = this.convertNameListToUUIDList(managerNames); + Claim claim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builderNames, containerNames, accessorNames, managerNames, inheritNothing, claimID); + + if (removeClaim) + { + claimsToRemove.add(claim); + } else if (parentId == -1) + { + //top level claim + this.addClaim(claim, false); + } else + { + //subdivision + subdivisionsToLoad.add(claim); + } + } + catch (SQLException e) + { + GriefPrevention.AddLogEntry("Unable to load a claim. Details: " + e.getMessage() + " ... " + results.toString()); + e.printStackTrace(); + } + } + + //add subdivisions to their parent claims + for (Claim childClaim : subdivisionsToLoad) + { + //find top level claim parent + Claim topLevelClaim = this.getClaimAt(childClaim.getLesserBoundaryCorner(), true, null); + + if (topLevelClaim == null) + { + claimsToRemove.add(childClaim); + GriefPrevention.AddLogEntry("Removing orphaned claim subdivision: " + childClaim.getLesserBoundaryCorner().toString()); + continue; + } + + //add this claim to the list of children of the current top level claim + childClaim.parent = topLevelClaim; + topLevelClaim.children.add(childClaim); + childClaim.inDataStore = true; + } + + for (Claim claim : claimsToRemove) + { + this.deleteClaimFromSecondaryStorage(claim); + } + + if (this.getSchemaVersion() <= 2) + { + this.refreshDataConnection(); + statement = this.databaseConnection.createStatement(); + statement.execute("DELETE FROM griefprevention_claimdata WHERE id='-1';"); + } + + super.initialize(); + } + + @Override + synchronized void writeClaimToStorage(Claim claim) //see datastore.cs. this will ALWAYS be a top level claim + { + try + { + this.refreshDataConnection(); + + //wipe out any existing data about this claim + this.deleteClaimFromSecondaryStorage(claim); + + //write claim data to the database + this.writeClaimData(claim); + } + catch (SQLException e) + { + GriefPrevention.AddLogEntry("Unable to save data for claim at " + this.locationToString(claim.lesserBoundaryCorner) + ". Details:"); + GriefPrevention.AddLogEntry(e.getMessage()); + } + } + + //actually writes claim data to the database + synchronized private void writeClaimData(Claim claim) throws SQLException + { + String lesserCornerString = this.locationToString(claim.getLesserBoundaryCorner()); + String greaterCornerString = this.locationToString(claim.getGreaterBoundaryCorner()); + String owner = ""; + if (claim.ownerID != null) owner = claim.ownerID.toString(); + + ArrayList builders = new ArrayList(); + ArrayList containers = new ArrayList(); + ArrayList accessors = new ArrayList(); + ArrayList managers = new ArrayList(); + + claim.getPermissions(builders, containers, accessors, managers); + + String buildersString = this.storageStringBuilder(builders); + String containersString = this.storageStringBuilder(containers); + String accessorsString = this.storageStringBuilder(accessors); + String managersString = this.storageStringBuilder(managers); + boolean inheritNothing = claim.getSubclaimRestrictions(); + long parentId = claim.parent == null ? -1 : claim.parent.id; + + try (PreparedStatement insertStmt = this.databaseConnection.prepareStatement(this.getInsertClaimSQL())) + { + + insertStmt.setLong(1, claim.id); + insertStmt.setString(2, owner); + insertStmt.setString(3, lesserCornerString); + insertStmt.setString(4, greaterCornerString); + insertStmt.setString(5, buildersString); + insertStmt.setString(6, containersString); + insertStmt.setString(7, accessorsString); + insertStmt.setString(8, managersString); + insertStmt.setBoolean(9, inheritNothing); + insertStmt.setLong(10, parentId); + insertStmt.executeUpdate(); + } + catch (SQLException e) + { + GriefPrevention.AddLogEntry("Unable to save data for claim at " + this.locationToString(claim.lesserBoundaryCorner) + ". Details:"); + GriefPrevention.AddLogEntry(e.getMessage()); + } + } + + //deletes a claim from the database + @Override + synchronized void deleteClaimFromSecondaryStorage(Claim claim) + { + try (PreparedStatement deleteStmnt = this.databaseConnection.prepareStatement(this.getDeleteClaimSQL())) + { + deleteStmnt.setLong(1, claim.id); + deleteStmnt.executeUpdate(); + } + catch (SQLException e) + { + GriefPrevention.AddLogEntry("Unable to delete data for claim " + claim.id + ". Details:"); + GriefPrevention.AddLogEntry(e.getMessage()); + e.printStackTrace(); + } + } + + @Override + PlayerData getPlayerDataFromStorage(UUID playerID) + { + PlayerData playerData = new PlayerData(); + playerData.playerID = playerID; + + try (PreparedStatement selectStmnt = this.databaseConnection.prepareStatement(this.getGetPlayerDataSQL())) + { + selectStmnt.setString(1, playerID.toString()); + ResultSet results = selectStmnt.executeQuery(); + + //if data for this player exists, use it + if (results.next()) + { + playerData.setAccruedClaimBlocks(results.getInt("accruedblocks")); + playerData.setBonusClaimBlocks(results.getInt("bonusblocks")); + } + } + catch (SQLException e) + { + StringWriter errors = new StringWriter(); + e.printStackTrace(new PrintWriter(errors)); + GriefPrevention.AddLogEntry(playerID + " " + errors.toString(), CustomLogEntryTypes.Exception); + } + + return playerData; + } + + //saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them + @Override + public void overrideSavePlayerData(UUID playerID, PlayerData playerData) + { + //never save data for the "administrative" account. an empty string for player name indicates administrative account + if (playerID == null) return; + + this.savePlayerData(playerID.toString(), playerData); + } + + private void savePlayerData(String playerID, PlayerData playerData) + { + try (PreparedStatement deleteStmnt = this.databaseConnection.prepareStatement(this.getDeletePlayerDataSQL()); + PreparedStatement insertStmnt = this.databaseConnection.prepareStatement(this.getInsertPlayerDataSQL())) + { + OfflinePlayer player = Bukkit.getOfflinePlayer(UUID.fromString(playerID)); + + SimpleDateFormat sqlFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String dateString = sqlFormat.format(new Date(player.getLastPlayed())); + deleteStmnt.setString(1, playerID); + deleteStmnt.executeUpdate(); + + insertStmnt.setString(1, playerID); + insertStmnt.setString(2, dateString); + insertStmnt.setInt(3, playerData.getAccruedClaimBlocks()); + insertStmnt.setInt(4, playerData.getBonusClaimBlocks()); + insertStmnt.executeUpdate(); + } + catch (SQLException e) + { + StringWriter errors = new StringWriter(); + e.printStackTrace(new PrintWriter(errors)); + GriefPrevention.AddLogEntry(playerID + " " + errors.toString(), CustomLogEntryTypes.Exception); + } + } + + @Override + synchronized void incrementNextClaimID() + { + this.setNextClaimID(this.nextClaimID + 1); + } + + //sets the next claim ID. used by incrementNextClaimID() above, and also while migrating data from a flat file data store + synchronized void setNextClaimID(long nextID) + { + this.nextClaimID = nextID; + + try (PreparedStatement deleteStmnt = this.databaseConnection.prepareStatement(this.getDeleteNextClaimIdSQL()); + PreparedStatement insertStmnt = this.databaseConnection.prepareStatement(this.getInsertNextClaimIdSQL())) + { + deleteStmnt.execute(); + insertStmnt.setLong(1, nextID); + insertStmnt.executeUpdate(); + } + catch (SQLException e) + { + GriefPrevention.AddLogEntry("Unable to set next claim ID to " + nextID + ". Details:"); + GriefPrevention.AddLogEntry(e.getMessage()); + } + } + + //updates the database with a group's bonus blocks + @Override + synchronized void saveGroupBonusBlocks(String groupName, int currentValue) + { + //group bonus blocks are stored in the player data table, with player name = $groupName + try (PreparedStatement deleteStmnt = this.databaseConnection.prepareStatement(this.getDeleteGroupBonusSQL()); + PreparedStatement insertStmnt = this.databaseConnection.prepareStatement(this.getInsertPlayerDataSQL())) + { + SimpleDateFormat sqlFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String dateString = sqlFormat.format(new Date()); + deleteStmnt.setString(1, '$' + groupName); + deleteStmnt.executeUpdate(); + + insertStmnt.setString(1, '$' + groupName); + insertStmnt.setString(2, dateString); + insertStmnt.setInt(3, 0); + insertStmnt.setInt(4, currentValue); + insertStmnt.executeUpdate(); + } + catch (SQLException e) + { + GriefPrevention.AddLogEntry("Unable to save data for group " + groupName + ". Details:"); + GriefPrevention.AddLogEntry(e.getMessage()); + } + } + + @Override + synchronized void close() + { + if (this.databaseConnection != null) + { + try + { + if (!this.databaseConnection.isClosed()) + { + this.databaseConnection.close(); + } + } + catch (SQLException e) {} + ; + } + + this.databaseConnection = null; + } + + private synchronized void refreshDataConnection() throws SQLException + { + if (this.databaseConnection == null || !this.databaseConnection.isValid(3)) + { + if (this.databaseConnection != null && !this.databaseConnection.isClosed()) + { + this.databaseConnection.close(); + } + + //set username/pass properties + Properties connectionProps = new Properties(); + connectionProps.put("user", this.userName); + connectionProps.put("password", this.password); + connectionProps.put("autoReconnect", "true"); + connectionProps.put("maxReconnects", String.valueOf(Integer.MAX_VALUE)); + + //establish connection + this.databaseConnection = DriverManager.getConnection(this.databaseUrl, connectionProps); + } + } + + @Override + protected int getSchemaVersionFromStorage() + { + try (PreparedStatement selectStmnt = this.databaseConnection.prepareStatement(this.getSelectSchemaVersionSQL())) + { + ResultSet results = selectStmnt.executeQuery(); + + //if there's nothing yet, assume 0 and add it + if (!results.next()) + { + this.setSchemaVersion(0); + return 0; + } + //otherwise return the value that's in the table + else + { + return results.getInt("version"); + } + } + catch (SQLException e) + { + GriefPrevention.AddLogEntry("Unable to retrieve schema version from database. Details:"); + GriefPrevention.AddLogEntry(e.getMessage()); + e.printStackTrace(); + return 0; + } + } + + @Override + protected void updateSchemaVersionInStorage(int versionToSet) + { + try (PreparedStatement deleteStmnt = this.databaseConnection.prepareStatement(this.getDeleteSchemaVersionSQL()); + PreparedStatement insertStmnt = this.databaseConnection.prepareStatement(this.getInsertSchemaVerSQL())) + { + deleteStmnt.execute(); + + insertStmnt.setInt(1, versionToSet); + insertStmnt.executeUpdate(); + } + catch (SQLException e) + { + GriefPrevention.AddLogEntry("Unable to set next schema version to " + versionToSet + ". Details:"); + GriefPrevention.AddLogEntry(e.getMessage()); + } + } + + /** + * Concats an array to a string divided with the ; sign + * + * @param input Arraylist with strings to concat + * @return String with all values from input array + */ + private String storageStringBuilder(ArrayList input) + { + String output = ""; + for (String string : input) + { + output += string + ";"; + } + return output; + } + + public String getUpdateNameSQL() + { + return updateNameSQL; + } + + public String getInsertClaimSQL() + { + return insertClaimSQL; + } + + public String getDeleteClaimSQL() + { + return deleteClaimSQL; + } + + public String getGetPlayerDataSQL() + { + return getPlayerDataSQL; + } + + public String getDeletePlayerDataSQL() + { + return deletePlayerDataSQL; + } + + public String getInsertPlayerDataSQL() + { + return insertPlayerDataSQL; + } + + public String getInsertNextClaimIdSQL() + { + return insertNextClaimIdSQL; + } + + public String getDeleteGroupBonusSQL() + { + return deleteGroupBonusSQL; + } + + public String getInsertSchemaVerSQL() + { + return insertSchemaVerSQL; + } + + public String getDeleteNextClaimIdSQL() + { + return deleteNextClaimIdSQL; + } + + public String getDeleteSchemaVersionSQL() + { + return deleteSchemaVersionSQL; + } + + public String getSelectSchemaVersionSQL() + { + return selectSchemaVersionSQL; + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/DeliverClaimBlocksTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/DeliverClaimBlocksTask.java index c732ff6..ddaec5c 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/DeliverClaimBlocksTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/DeliverClaimBlocksTask.java @@ -16,13 +16,13 @@ along with this program. If not, see . */ - package me.ryanhamshire.GriefPrevention; - -import java.util.Collection; +package me.ryanhamshire.GriefPrevention; import me.ryanhamshire.GriefPrevention.events.AccrueClaimBlocksEvent; import org.bukkit.entity.Player; +import java.util.Collection; + //FEATURE: give players claim blocks for playing, as long as they're not away from their computer //runs every 5 minutes in the main thread, grants blocks per hour / 12 to each online player who appears to be actively playing @@ -43,13 +43,13 @@ class DeliverClaimBlocksTask implements Runnable public void run() { //if no player specified, this task will create a player-specific task for each online player, scheduled one tick apart - if(this.player == null) + if (this.player == null) { @SuppressWarnings("unchecked") - Collection players = (Collection)GriefPrevention.instance.getServer().getOnlinePlayers(); + Collection players = (Collection) GriefPrevention.instance.getServer().getOnlinePlayers(); long i = 0; - for(Player onlinePlayer : players) + for (Player onlinePlayer : players) { DeliverClaimBlocksTask newTask = new DeliverClaimBlocksTask(onlinePlayer, instance); instance.getServer().getScheduler().scheduleSyncDelayedTask(instance, newTask, i++); @@ -59,7 +59,7 @@ class DeliverClaimBlocksTask implements Runnable } //deliver claim blocks to the specified player - if(!this.player.isOnline()) + if (!this.player.isOnline()) { return; //player is not online to receive claim blocks } @@ -76,7 +76,7 @@ class DeliverClaimBlocksTask implements Runnable isIdle = player.isInsideVehicle() || player.getLocation().getBlock().isLiquid() || !(playerData.lastAfkCheckLocation == null || playerData.lastAfkCheckLocation.distanceSquared(player.getLocation()) > idleThresholdSquared); } - catch(IllegalArgumentException ignore) //can't measure distance when to/from are different worlds + catch (IllegalArgumentException ignore) //can't measure distance when to/from are different worlds { } @@ -119,7 +119,7 @@ class DeliverClaimBlocksTask implements Runnable //many other operations will cause this player's data to save, including his eventual logout //dataStore.savePlayerData(player.getUniqueIdentifier(), playerData); } - catch(Exception e) + catch (Exception e) { GriefPrevention.AddLogEntry("Problem delivering claim blocks to player " + player.getName() + ":"); e.printStackTrace(); diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/EntityCleanupTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/EntityCleanupTask.java index 0aa9c95..3194a39 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/EntityCleanupTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/EntityCleanupTask.java @@ -32,119 +32,116 @@ import java.util.List; //this main thread task revisits the location of a partially chopped tree from several minutes ago //if any part of the tree is still there and nothing else has been built in its place, remove the remaining parts -class EntityCleanupTask implements Runnable +class EntityCleanupTask implements Runnable { - //where to start cleaning in the list of entities - private double percentageStart; - - public EntityCleanupTask(double percentageStart) - { - this.percentageStart = percentageStart; - } - - @Override - public void run() - { - ArrayList worlds = new ArrayList(); - for(World world : GriefPrevention.instance.getServer().getWorlds()) - { - if(GriefPrevention.instance.config_claims_worldModes.get(world) == ClaimsMode.Creative) - { - worlds.add(world); - } - } - - for(int i = 0; i < worlds.size(); i++) - { - World world = worlds.get(i); - - List entities = world.getEntities(); - - //starting and stopping point. each execution of the task scans 10% of the server's (loaded) entities - int j = (int)(entities.size() * this.percentageStart); - int k = (int)(entities.size() * (this.percentageStart + .1)); - Claim cachedClaim = null; - for(; j < entities.size() && j < k; j++) - { - Entity entity = entities.get(j); - - boolean remove = false; - if(entity instanceof Boat) //boats must be occupied - { - Boat boat = (Boat)entity; - if(boat.isEmpty()) remove = true; - } - - else if(entity instanceof Vehicle) - { - Vehicle vehicle = (Vehicle)entity; - - //minecarts in motion must be occupied by a player - if(vehicle.getVelocity().lengthSquared() != 0) - { - if(vehicle.isEmpty() || !(vehicle.getPassenger() instanceof Player)) - { - remove = true; - } - } - - //stationary carts must be on rails - else - { - Material material = world.getBlockAt(vehicle.getLocation()).getType(); - if(material != Material.RAIL && material != Material.POWERED_RAIL && material != Material.DETECTOR_RAIL) - { - remove = true; - } - } - } - - //all non-player entities must be in claims - else if(!(entity instanceof Player)) - { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(entity.getLocation(), false, cachedClaim); - if(claim != null) - { - cachedClaim = claim; - } - else - { - remove = true; - } - } - - if(remove) - { - GriefPrevention.AddLogEntry("Removing entity " + entity.getType().name() + " @ " + entity.getLocation(), CustomLogEntryTypes.Debug, true); - entity.remove(); - } - } - } - - //starting and stopping point. each execution of the task scans 5% of the server's claims - List claims = GriefPrevention.instance.dataStore.claims; - int j = (int)(claims.size() * this.percentageStart); - int k = (int)(claims.size() * (this.percentageStart + .05)); - for(; j < claims.size() && j < k; j++) - { - Claim claim = claims.get(j); - - //if it's a creative mode claim - if(GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner())) - { - //check its entity count and remove any extras - claim.allowMoreEntities(true); - } - } - - //schedule the next run of this task, in 3 minutes (20L is approximately 1 second) - double nextRunPercentageStart = this.percentageStart + .05; - if(nextRunPercentageStart > .99) - { - nextRunPercentageStart = 0; - } - - EntityCleanupTask task = new EntityCleanupTask(nextRunPercentageStart); - GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 20L * 60 * 1); - } + //where to start cleaning in the list of entities + private double percentageStart; + + public EntityCleanupTask(double percentageStart) + { + this.percentageStart = percentageStart; + } + + @Override + public void run() + { + ArrayList worlds = new ArrayList(); + for (World world : GriefPrevention.instance.getServer().getWorlds()) + { + if (GriefPrevention.instance.config_claims_worldModes.get(world) == ClaimsMode.Creative) + { + worlds.add(world); + } + } + + for (int i = 0; i < worlds.size(); i++) + { + World world = worlds.get(i); + + List entities = world.getEntities(); + + //starting and stopping point. each execution of the task scans 10% of the server's (loaded) entities + int j = (int) (entities.size() * this.percentageStart); + int k = (int) (entities.size() * (this.percentageStart + .1)); + Claim cachedClaim = null; + for (; j < entities.size() && j < k; j++) + { + Entity entity = entities.get(j); + + boolean remove = false; + if (entity instanceof Boat) //boats must be occupied + { + Boat boat = (Boat) entity; + if (boat.isEmpty()) remove = true; + } else if (entity instanceof Vehicle) + { + Vehicle vehicle = (Vehicle) entity; + + //minecarts in motion must be occupied by a player + if (vehicle.getVelocity().lengthSquared() != 0) + { + if (vehicle.isEmpty() || !(vehicle.getPassenger() instanceof Player)) + { + remove = true; + } + } + + //stationary carts must be on rails + else + { + Material material = world.getBlockAt(vehicle.getLocation()).getType(); + if (material != Material.RAIL && material != Material.POWERED_RAIL && material != Material.DETECTOR_RAIL) + { + remove = true; + } + } + } + + //all non-player entities must be in claims + else if (!(entity instanceof Player)) + { + Claim claim = GriefPrevention.instance.dataStore.getClaimAt(entity.getLocation(), false, cachedClaim); + if (claim != null) + { + cachedClaim = claim; + } else + { + remove = true; + } + } + + if (remove) + { + GriefPrevention.AddLogEntry("Removing entity " + entity.getType().name() + " @ " + entity.getLocation(), CustomLogEntryTypes.Debug, true); + entity.remove(); + } + } + } + + //starting and stopping point. each execution of the task scans 5% of the server's claims + List claims = GriefPrevention.instance.dataStore.claims; + int j = (int) (claims.size() * this.percentageStart); + int k = (int) (claims.size() * (this.percentageStart + .05)); + for (; j < claims.size() && j < k; j++) + { + Claim claim = claims.get(j); + + //if it's a creative mode claim + if (GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner())) + { + //check its entity count and remove any extras + claim.allowMoreEntities(true); + } + } + + //schedule the next run of this task, in 3 minutes (20L is approximately 1 second) + double nextRunPercentageStart = this.percentageStart + .05; + if (nextRunPercentageStart > .99) + { + nextRunPercentageStart = 0; + } + + EntityCleanupTask task = new EntityCleanupTask(nextRunPercentageStart); + GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 20L * 60 * 1); + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/EntityEventHandler.java b/src/main/java/me/ryanhamshire/GriefPrevention/EntityEventHandler.java index 8bc72d5..c789671 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/EntityEventHandler.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/EntityEventHandler.java @@ -29,6 +29,7 @@ import org.bukkit.World.Environment; import org.bukkit.block.Block; import org.bukkit.entity.Animals; import org.bukkit.entity.Creature; +import org.bukkit.entity.Donkey; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Explosive; @@ -38,7 +39,9 @@ import org.bukkit.entity.Horse; import org.bukkit.entity.Item; import org.bukkit.entity.LightningStrike; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Llama; import org.bukkit.entity.Monster; +import org.bukkit.entity.Mule; import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; import org.bukkit.entity.Rabbit; @@ -46,9 +49,6 @@ import org.bukkit.entity.Tameable; import org.bukkit.entity.ThrownPotion; import org.bukkit.entity.Vehicle; import org.bukkit.entity.WaterMob; -import org.bukkit.entity.Llama; -import org.bukkit.entity.Donkey; -import org.bukkit.entity.Mule; import org.bukkit.entity.Wolf; import org.bukkit.entity.minecart.ExplosiveMinecart; import org.bukkit.event.EventHandler; @@ -99,55 +99,51 @@ import java.util.UUID; //handles events related to entities public class EntityEventHandler implements Listener { - //convenience reference for the singleton datastore - private DataStore dataStore; - GriefPrevention instance; - - public EntityEventHandler(DataStore dataStore, GriefPrevention plugin) - { - this.dataStore = dataStore; - instance = plugin; - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onEntityFormBlock(EntityBlockFormEvent event) - { - Entity entity = event.getEntity(); - if(entity.getType() == EntityType.PLAYER) + //convenience reference for the singleton datastore + private DataStore dataStore; + GriefPrevention instance; + + public EntityEventHandler(DataStore dataStore, GriefPrevention plugin) + { + this.dataStore = dataStore; + instance = plugin; + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onEntityFormBlock(EntityBlockFormEvent event) + { + Entity entity = event.getEntity(); + if (entity.getType() == EntityType.PLAYER) { - Player player = (Player)event.getEntity(); + Player player = (Player) event.getEntity(); String noBuildReason = GriefPrevention.instance.allowBuild(player, event.getBlock().getLocation(), event.getNewState().getType()); - if(noBuildReason != null) + if (noBuildReason != null) { event.setCancelled(true); } } - } + } - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onLightningStrike(LightningStrikeEvent event){ - if(event.getCause() == LightningStrikeEvent.Cause.TRIDENT) - event.getLightning().setMetadata("GP_TRIDENT", new FixedMetadataValue(GriefPrevention.instance, event.getLightning().getLocation())); - } + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onLightningStrike(LightningStrikeEvent event) + { + if (event.getCause() == LightningStrikeEvent.Cause.TRIDENT) + event.getLightning().setMetadata("GP_TRIDENT", new FixedMetadataValue(GriefPrevention.instance, event.getLightning().getLocation())); + } - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onEntityChangeBLock(EntityChangeBlockEvent event) - { - if(!GriefPrevention.instance.config_endermenMoveBlocks && event.getEntityType() == EntityType.ENDERMAN) - { - event.setCancelled(true); - } - - else if(!GriefPrevention.instance.config_silverfishBreakBlocks && event.getEntityType() == EntityType.SILVERFISH) - { - event.setCancelled(true); - } - - else if(!GriefPrevention.instance.config_rabbitsEatCrops && event.getEntityType() == EntityType.RABBIT) - { - event.setCancelled(true); - } - else if(GriefPrevention.instance.config_claims_worldModes.get(event.getBlock().getWorld()) != ClaimsMode.Disabled) + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onEntityChangeBLock(EntityChangeBlockEvent event) + { + if (!GriefPrevention.instance.config_endermenMoveBlocks && event.getEntityType() == EntityType.ENDERMAN) + { + event.setCancelled(true); + } else if (!GriefPrevention.instance.config_silverfishBreakBlocks && event.getEntityType() == EntityType.SILVERFISH) + { + event.setCancelled(true); + } else if (!GriefPrevention.instance.config_rabbitsEatCrops && event.getEntityType() == EntityType.RABBIT) + { + event.setCancelled(true); + } else if (GriefPrevention.instance.config_claims_worldModes.get(event.getBlock().getWorld()) != ClaimsMode.Disabled) { if (event.getEntityType() == EntityType.WITHER) { @@ -156,9 +152,7 @@ public class EntityEventHandler implements Listener { event.setCancelled(true); } - } - - else if (!GriefPrevention.instance.config_claims_ravagersBreakBlocks && event.getEntityType() == EntityType.RAVAGER) + } else if (!GriefPrevention.instance.config_claims_ravagersBreakBlocks && event.getEntityType() == EntityType.RAVAGER) { event.setCancelled(true); } @@ -181,189 +175,191 @@ public class EntityEventHandler implements Listener } } - //Prevent breaking lilypads via collision with a boat. Thanks Jikoo. - else if (event.getEntity() instanceof Vehicle && !event.getEntity().getPassengers().isEmpty()) { - Entity driver = event.getEntity().getPassengers().get(0); - if (driver instanceof Player) { - Block block = event.getBlock(); - if (GriefPrevention.instance.allowBreak((Player) driver, block, block.getLocation()) != null) { - event.setCancelled(true); - } - } - } - - //sand cannon fix - when the falling block doesn't fall straight down, take additional anti-grief steps - else if (event.getEntityType() == EntityType.FALLING_BLOCK) + //Prevent breaking lilypads via collision with a boat. Thanks Jikoo. + else if (event.getEntity() instanceof Vehicle && !event.getEntity().getPassengers().isEmpty()) + { + Entity driver = event.getEntity().getPassengers().get(0); + if (driver instanceof Player) { - FallingBlock entity = (FallingBlock)event.getEntity(); Block block = event.getBlock(); - - //if changing a block TO air, this is when the falling block formed. note its original location - if(event.getTo() == Material.AIR) + if (GriefPrevention.instance.allowBreak((Player) driver, block, block.getLocation()) != null) { - entity.setMetadata("GP_FALLINGBLOCK", new FixedMetadataValue(GriefPrevention.instance, block.getLocation())); + event.setCancelled(true); } - //otherwise, the falling block is forming a block. compare new location to original source - else + } + } + + //sand cannon fix - when the falling block doesn't fall straight down, take additional anti-grief steps + else if (event.getEntityType() == EntityType.FALLING_BLOCK) + { + FallingBlock entity = (FallingBlock) event.getEntity(); + Block block = event.getBlock(); + + //if changing a block TO air, this is when the falling block formed. note its original location + if (event.getTo() == Material.AIR) + { + entity.setMetadata("GP_FALLINGBLOCK", new FixedMetadataValue(GriefPrevention.instance, block.getLocation())); + } + //otherwise, the falling block is forming a block. compare new location to original source + else + { + List values = entity.getMetadata("GP_FALLINGBLOCK"); + //if we're not sure where this entity came from (maybe another plugin didn't follow the standard?), allow the block to form + //Or if entity fell through an end portal, allow it to form, as the event is erroneously fired twice in this scenario. + if (values.size() < 1) return; + + Location originalLocation = (Location) (values.get(0).value()); + Location newLocation = block.getLocation(); + + //if did not fall straight down + if (originalLocation.getBlockX() != newLocation.getBlockX() || originalLocation.getBlockZ() != newLocation.getBlockZ()) { - List values = entity.getMetadata("GP_FALLINGBLOCK"); - //if we're not sure where this entity came from (maybe another plugin didn't follow the standard?), allow the block to form - //Or if entity fell through an end portal, allow it to form, as the event is erroneously fired twice in this scenario. - if(values.size() < 1) return; - - Location originalLocation = (Location)(values.get(0).value()); - Location newLocation = block.getLocation(); - - //if did not fall straight down - if(originalLocation.getBlockX() != newLocation.getBlockX() || originalLocation.getBlockZ() != newLocation.getBlockZ()) + //in creative mode worlds, never form the block + if (GriefPrevention.instance.config_claims_worldModes.get(newLocation.getWorld()) == ClaimsMode.Creative) { - //in creative mode worlds, never form the block - if(GriefPrevention.instance.config_claims_worldModes.get(newLocation.getWorld()) == ClaimsMode.Creative) - { - event.setCancelled(true); - return; - } + event.setCancelled(true); + return; + } - //in other worlds, if landing in land claim, only allow if source was also in the land claim - Claim claim = this.dataStore.getClaimAt(newLocation, false, null); - if(claim != null && !claim.contains(originalLocation, false, false)) - { - //when not allowed, drop as item instead of forming a block - event.setCancelled(true); - @SuppressWarnings("deprecation") - ItemStack itemStack = new ItemStack(entity.getMaterial(), 1); - Item item = block.getWorld().dropItem(entity.getLocation(), itemStack); - item.setVelocity(new Vector()); - } + //in other worlds, if landing in land claim, only allow if source was also in the land claim + Claim claim = this.dataStore.getClaimAt(newLocation, false, null); + if (claim != null && !claim.contains(originalLocation, false, false)) + { + //when not allowed, drop as item instead of forming a block + event.setCancelled(true); + @SuppressWarnings("deprecation") + ItemStack itemStack = new ItemStack(entity.getMaterial(), 1); + Item item = block.getWorld().dropItem(entity.getLocation(), itemStack); + item.setVelocity(new Vector()); } } } - } + } + } - //Used by "sand cannon" fix to ignore fallingblocks that fell through End Portals - //This is largely due to a CB issue with the above event - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onFallingBlockEnterPortal(EntityPortalEnterEvent event) - { - if (event.getEntityType() != EntityType.FALLING_BLOCK) - return; - event.getEntity().removeMetadata("GP_FALLINGBLOCK", instance); - } + //Used by "sand cannon" fix to ignore fallingblocks that fell through End Portals + //This is largely due to a CB issue with the above event + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onFallingBlockEnterPortal(EntityPortalEnterEvent event) + { + if (event.getEntityType() != EntityType.FALLING_BLOCK) + return; + event.getEntity().removeMetadata("GP_FALLINGBLOCK", instance); + } - //Don't let people drop in TNT through end portals - //Necessarily this shouldn't be an issue anyways since the platform is obsidian... - @EventHandler(ignoreCancelled = true) - void onTNTExitPortal(EntityPortalExitEvent event) - { - if (event.getEntityType() != EntityType.PRIMED_TNT) - return; - if (event.getTo().getWorld().getEnvironment() != Environment.THE_END) - return; - event.getEntity().remove(); - } - - //don't allow zombies to break down doors - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onZombieBreakDoor(EntityBreakDoorEvent event) - { - if(!GriefPrevention.instance.config_zombiesBreakDoors) event.setCancelled(true); - } - - //don't allow entities to trample crops - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onEntityInteract(EntityInteractEvent event) - { - Material material = event.getBlock().getType(); - if(material == Material.FARMLAND) - { - if(!GriefPrevention.instance.config_creaturesTrampleCrops) - { - event.setCancelled(true); - } - else - { - Entity rider = event.getEntity().getPassenger(); - if(rider != null && rider.getType() == EntityType.PLAYER) - { - event.setCancelled(true); - } - } - } - } - - //when an entity explodes... - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onEntityExplode(EntityExplodeEvent explodeEvent) - { - this.handleExplosion(explodeEvent.getLocation(), explodeEvent.getEntity(), explodeEvent.blockList()); - } - - //when a block explodes... + //Don't let people drop in TNT through end portals + //Necessarily this shouldn't be an issue anyways since the platform is obsidian... + @EventHandler(ignoreCancelled = true) + void onTNTExitPortal(EntityPortalExitEvent event) + { + if (event.getEntityType() != EntityType.PRIMED_TNT) + return; + if (event.getTo().getWorld().getEnvironment() != Environment.THE_END) + return; + event.getEntity().remove(); + } + + //don't allow zombies to break down doors + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onZombieBreakDoor(EntityBreakDoorEvent event) + { + if (!GriefPrevention.instance.config_zombiesBreakDoors) event.setCancelled(true); + } + + //don't allow entities to trample crops + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onEntityInteract(EntityInteractEvent event) + { + Material material = event.getBlock().getType(); + if (material == Material.FARMLAND) + { + if (!GriefPrevention.instance.config_creaturesTrampleCrops) + { + event.setCancelled(true); + } else + { + Entity rider = event.getEntity().getPassenger(); + if (rider != null && rider.getType() == EntityType.PLAYER) + { + event.setCancelled(true); + } + } + } + } + + //when an entity explodes... + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onEntityExplode(EntityExplodeEvent explodeEvent) + { + this.handleExplosion(explodeEvent.getLocation(), explodeEvent.getEntity(), explodeEvent.blockList()); + } + + //when a block explodes... @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onBlockExplode(BlockExplodeEvent explodeEvent) - { + { this.handleExplosion(explodeEvent.getBlock().getLocation(), null, explodeEvent.blockList()); } - + @SuppressWarnings("deprecation") void handleExplosion(Location location, Entity entity, List blocks) { //only applies to claims-enabled worlds World world = location.getWorld(); - - if(!GriefPrevention.instance.claimsEnabledForWorld(world)) return; - + + if (!GriefPrevention.instance.claimsEnabledForWorld(world)) return; + //FEATURE: explosions don't destroy surface blocks by default boolean isCreeper = (entity != null && entity.getType() == EntityType.CREEPER); - + boolean applySurfaceRules = world.getEnvironment() == Environment.NORMAL && ((isCreeper && GriefPrevention.instance.config_blockSurfaceCreeperExplosions) || (!isCreeper && GriefPrevention.instance.config_blockSurfaceOtherExplosions)); - + //special rule for creative worlds: explosions don't destroy anything - if(GriefPrevention.instance.creativeRulesApply(location)) + if (GriefPrevention.instance.creativeRulesApply(location)) { - for(int i = 0; i < blocks.size(); i++) + for (int i = 0; i < blocks.size(); i++) { Block block = blocks.get(i); - + blocks.remove(i--); } - + return; } - + //make a list of blocks which were allowed to explode List explodedBlocks = new ArrayList(); Claim cachedClaim = null; - for(int i = 0; i < blocks.size(); i++) + for (int i = 0; i < blocks.size(); i++) { Block block = blocks.get(i); - + //always ignore air blocks - if(block.getType() == Material.AIR) continue; - + if (block.getType() == Material.AIR) continue; + //is it in a land claim? Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, cachedClaim); - if(claim != null) + if (claim != null) { cachedClaim = claim; } - + //if yes, apply claim exemptions if they should apply - if(claim != null && (claim.areExplosivesAllowed || !GriefPrevention.instance.config_blockClaimExplosions)) + if (claim != null && (claim.areExplosivesAllowed || !GriefPrevention.instance.config_blockClaimExplosions)) { explodedBlocks.add(block); continue; } - + //if claim is under siege, allow soft blocks to be destroyed - if(claim != null && claim.siegeData != null) + if (claim != null && claim.siegeData != null) { Material material = block.getType(); boolean breakable = false; - for(int j = 0; j < GriefPrevention.instance.config_siege_blocks.size(); j++) + for (int j = 0; j < GriefPrevention.instance.config_siege_blocks.size(); j++) { Material breakableMaterial = GriefPrevention.instance.config_siege_blocks.get(j); - if(breakableMaterial == material) + if (breakableMaterial == material) { breakable = true; explodedBlocks.add(block); @@ -371,428 +367,430 @@ public class EntityEventHandler implements Listener } } - if(breakable) continue; + if (breakable) continue; } - + //if no, then also consider surface rules - if(claim == null) + if (claim == null) { - if(!applySurfaceRules || block.getLocation().getBlockY() < GriefPrevention.instance.getSeaLevel(world) - 7) + if (!applySurfaceRules || block.getLocation().getBlockY() < GriefPrevention.instance.getSeaLevel(world) - 7) { explodedBlocks.add(block); } } } - + //clear original damage list and replace with allowed damage list blocks.clear(); blocks.addAll(explodedBlocks); } - - //when an item spawns... - @EventHandler(priority = EventPriority.LOWEST) - public void onItemSpawn(ItemSpawnEvent event) - { - //if in a creative world, cancel the event (don't drop items on the ground) - if(GriefPrevention.instance.creativeRulesApply(event.getLocation())) - { - event.setCancelled(true); - } - - //if item is on watch list, apply protection - ArrayList watchList = GriefPrevention.instance.pendingItemWatchList; - Item newItem = event.getEntity(); - Long now = null; - for(int i = 0; i < watchList.size(); i++) - { - PendingItemProtection pendingProtection = watchList.get(i); - //ignore and remove any expired pending protections - if(now == null) now = System.currentTimeMillis(); - if(pendingProtection.expirationTimestamp < now) + + //when an item spawns... + @EventHandler(priority = EventPriority.LOWEST) + public void onItemSpawn(ItemSpawnEvent event) + { + //if in a creative world, cancel the event (don't drop items on the ground) + if (GriefPrevention.instance.creativeRulesApply(event.getLocation())) + { + event.setCancelled(true); + } + + //if item is on watch list, apply protection + ArrayList watchList = GriefPrevention.instance.pendingItemWatchList; + Item newItem = event.getEntity(); + Long now = null; + for (int i = 0; i < watchList.size(); i++) + { + PendingItemProtection pendingProtection = watchList.get(i); + //ignore and remove any expired pending protections + if (now == null) now = System.currentTimeMillis(); + if (pendingProtection.expirationTimestamp < now) { watchList.remove(i--); continue; } - //skip if item stack doesn't match - if(pendingProtection.itemStack.getAmount() != newItem.getItemStack().getAmount() || - pendingProtection.itemStack.getType() != newItem.getItemStack().getType()) - { - continue; - } - - //skip if new item location isn't near the expected spawn area - Location spawn = event.getLocation(); - Location expected = pendingProtection.location; - if(!spawn.getWorld().equals(expected.getWorld()) || - spawn.getX() < expected.getX() - 5 || - spawn.getX() > expected.getX() + 5 || - spawn.getZ() < expected.getZ() - 5 || - spawn.getZ() > expected.getZ() + 5 || - spawn.getY() < expected.getY() - 15 || - spawn.getY() > expected.getY() + 3) - { - continue; - } - - //otherwise, mark item with protection information - newItem.setMetadata("GP_ITEMOWNER", new FixedMetadataValue(GriefPrevention.instance, pendingProtection.owner)); - - //and remove pending protection data - watchList.remove(i); - break; - } - } - - //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) - { - //these rules apply only to creative worlds - if(!GriefPrevention.instance.creativeRulesApply(event.getLocation())) return; - - //chicken eggs and breeding could potentially make a mess in the wilderness, once griefers get involved - SpawnReason reason = event.getSpawnReason(); - if(reason != SpawnReason.SPAWNER_EGG && reason != SpawnReason.BUILD_IRONGOLEM && reason != SpawnReason.BUILD_SNOWMAN && event.getEntityType() != EntityType.ARMOR_STAND) - { - event.setCancelled(true); - return; - } + //skip if item stack doesn't match + if (pendingProtection.itemStack.getAmount() != newItem.getItemStack().getAmount() || + pendingProtection.itemStack.getType() != newItem.getItemStack().getType()) + { + continue; + } + + //skip if new item location isn't near the expected spawn area + Location spawn = event.getLocation(); + Location expected = pendingProtection.location; + if (!spawn.getWorld().equals(expected.getWorld()) || + spawn.getX() < expected.getX() - 5 || + spawn.getX() > expected.getX() + 5 || + spawn.getZ() < expected.getZ() - 5 || + spawn.getZ() > expected.getZ() + 5 || + spawn.getY() < expected.getY() - 15 || + spawn.getY() > expected.getY() + 3) + { + continue; + } + + //otherwise, mark item with protection information + newItem.setMetadata("GP_ITEMOWNER", new FixedMetadataValue(GriefPrevention.instance, pendingProtection.owner)); + + //and remove pending protection data + watchList.remove(i); + break; + } + } + + //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) + { + //these rules apply only to creative worlds + if (!GriefPrevention.instance.creativeRulesApply(event.getLocation())) return; + + //chicken eggs and breeding could potentially make a mess in the wilderness, once griefers get involved + SpawnReason reason = event.getSpawnReason(); + if (reason != SpawnReason.SPAWNER_EGG && reason != SpawnReason.BUILD_IRONGOLEM && reason != SpawnReason.BUILD_SNOWMAN && event.getEntityType() != EntityType.ARMOR_STAND) + { + event.setCancelled(true); + return; + } + + //otherwise, just apply the limit on total entities per claim (and no spawning in the wilderness!) + Claim claim = this.dataStore.getClaimAt(event.getLocation(), false, null); + if (claim == null || claim.allowMoreEntities(true) != null) + { + event.setCancelled(true); + return; + } + } + + //when an entity dies... + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onEntityDeath(EntityDeathEvent event) + { + LivingEntity entity = event.getEntity(); + + //don't do the rest in worlds where claims are not enabled + if (!GriefPrevention.instance.claimsEnabledForWorld(entity.getWorld())) return; + + //special rule for creative worlds: killed entities don't drop items or experience orbs + if (GriefPrevention.instance.creativeRulesApply(entity.getLocation())) + { + event.setDroppedExp(0); + event.getDrops().clear(); + } + + //FEATURE: when a player is involved in a siege (attacker or defender role) + //his death will end the siege + + if (entity.getType() != EntityType.PLAYER) return; //only tracking players + + Player player = (Player) entity; + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + + //if involved in a siege + if (playerData.siegeData != null) + { + //end it, with the dieing player being the loser + this.dataStore.endSiege(playerData.siegeData, null, player.getName(), event.getDrops()); + } + + //FEATURE: lock dropped items to player who dropped them - //otherwise, just apply the limit on total entities per claim (and no spawning in the wilderness!) - Claim claim = this.dataStore.getClaimAt(event.getLocation(), false, null); - if(claim == null || claim.allowMoreEntities(true) != null) - { - event.setCancelled(true); - return; - } - } - - //when an entity dies... - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void onEntityDeath(EntityDeathEvent event) - { - LivingEntity entity = event.getEntity(); - - //don't do the rest in worlds where claims are not enabled - if(!GriefPrevention.instance.claimsEnabledForWorld(entity.getWorld())) return; - - //special rule for creative worlds: killed entities don't drop items or experience orbs - if(GriefPrevention.instance.creativeRulesApply(entity.getLocation())) - { - event.setDroppedExp(0); - event.getDrops().clear(); - } - - //FEATURE: when a player is involved in a siege (attacker or defender role) - //his death will end the siege - - if(entity.getType() != EntityType.PLAYER) return; //only tracking players - - Player player = (Player)entity; - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - - //if involved in a siege - if(playerData.siegeData != null) - { - //end it, with the dieing player being the loser - this.dataStore.endSiege(playerData.siegeData, null, player.getName(), event.getDrops()); - } - - //FEATURE: lock dropped items to player who dropped them - World world = entity.getWorld(); - + //decide whether or not to apply this feature to this situation (depends on the world where it happens) boolean isPvPWorld = GriefPrevention.instance.pvpRulesApply(world); - if((isPvPWorld && GriefPrevention.instance.config_lockDeathDropsInPvpWorlds) || - (!isPvPWorld && GriefPrevention.instance.config_lockDeathDropsInNonPvpWorlds)) + if ((isPvPWorld && GriefPrevention.instance.config_lockDeathDropsInPvpWorlds) || + (!isPvPWorld && GriefPrevention.instance.config_lockDeathDropsInNonPvpWorlds)) { Claim claim = this.dataStore.getClaimAt(player.getLocation(), false, playerData.lastClaim); ProtectDeathDropsEvent protectionEvent = new ProtectDeathDropsEvent(claim); Bukkit.getPluginManager().callEvent(protectionEvent); - if(!protectionEvent.isCancelled()) + if (!protectionEvent.isCancelled()) { //remember information about these drops so that they can be marked when they spawn as items long expirationTime = System.currentTimeMillis() + 3000; //now + 3 seconds Location deathLocation = player.getLocation(); UUID playerID = player.getUniqueId(); List drops = event.getDrops(); - for(ItemStack stack : drops) + for (ItemStack stack : drops) { GriefPrevention.instance.pendingItemWatchList.add( - new PendingItemProtection(deathLocation, playerID, expirationTime, stack)); + new PendingItemProtection(deathLocation, playerID, expirationTime, stack)); } - + //allow the player to receive a message about how to unlock any drops playerData.dropsAreUnlocked = false; playerData.receivedDropUnlockAdvertisement = false; } } - } + } - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onItemMerge(ItemMergeEvent event) - { - Item item = event.getEntity(); - List data = item.getMetadata("GP_ITEMOWNER"); - event.setCancelled(data != null && data.size() > 0); - } - - //when an entity picks up an item - @EventHandler(priority = EventPriority.LOWEST) - public void onEntityPickup(EntityChangeBlockEvent event) - { - //FEATURE: endermen don't steal claimed blocks - - //if its an enderman - if(event.getEntity().getType() == EntityType.ENDERMAN) - { - //and the block is claimed - if(this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null) != null) - { - //he doesn't get to steal it - event.setCancelled(true); - } - } - } - - //when a painting is broken - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onHangingBreak(HangingBreakEvent event) + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onItemMerge(ItemMergeEvent event) { - //don't track in worlds where claims are not enabled - if(!GriefPrevention.instance.claimsEnabledForWorld(event.getEntity().getWorld())) return; + Item item = event.getEntity(); + List data = item.getMetadata("GP_ITEMOWNER"); + event.setCancelled(data != null && data.size() > 0); + } + + //when an entity picks up an item + @EventHandler(priority = EventPriority.LOWEST) + public void onEntityPickup(EntityChangeBlockEvent event) + { + //FEATURE: endermen don't steal claimed blocks + + //if its an enderman + if (event.getEntity().getType() == EntityType.ENDERMAN) + { + //and the block is claimed + if (this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null) != null) + { + //he doesn't get to steal it + event.setCancelled(true); + } + } + } + + //when a painting is broken + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onHangingBreak(HangingBreakEvent event) + { + //don't track in worlds where claims are not enabled + if (!GriefPrevention.instance.claimsEnabledForWorld(event.getEntity().getWorld())) return; //Ignore cases where itemframes should break due to no supporting blocks - if(event.getCause() == RemoveCause.PHYSICS) return; - - //FEATURE: claimed paintings are protected from breakage - - //explosions don't destroy hangings - if(event.getCause() == RemoveCause.EXPLOSION) - { - event.setCancelled(true); - return; - } - - //only allow players to break paintings, not anything else (like water and explosions) - if(!(event instanceof HangingBreakByEntityEvent)) - { - event.setCancelled(true); - return; - } - - HangingBreakByEntityEvent entityEvent = (HangingBreakByEntityEvent)event; - - //who is removing it? - Entity remover = entityEvent.getRemover(); - - //again, making sure the breaker is a player - if(remover.getType() != EntityType.PLAYER) + if (event.getCause() == RemoveCause.PHYSICS) return; + + //FEATURE: claimed paintings are protected from breakage + + //explosions don't destroy hangings + if (event.getCause() == RemoveCause.EXPLOSION) { - event.setCancelled(true); - return; + event.setCancelled(true); + return; } - - //if the player doesn't have build permission, don't allow the breakage - Player playerRemover = (Player)entityEvent.getRemover(); - String noBuildReason = GriefPrevention.instance.allowBuild(playerRemover, event.getEntity().getLocation(), Material.AIR); - if(noBuildReason != null) + + //only allow players to break paintings, not anything else (like water and explosions) + if (!(event instanceof HangingBreakByEntityEvent)) { - event.setCancelled(true); - GriefPrevention.sendMessage(playerRemover, TextMode.Err, noBuildReason); + event.setCancelled(true); + return; + } + + HangingBreakByEntityEvent entityEvent = (HangingBreakByEntityEvent) event; + + //who is removing it? + Entity remover = entityEvent.getRemover(); + + //again, making sure the breaker is a player + if (remover.getType() != EntityType.PLAYER) + { + event.setCancelled(true); + return; + } + + //if the player doesn't have build permission, don't allow the breakage + Player playerRemover = (Player) entityEvent.getRemover(); + String noBuildReason = GriefPrevention.instance.allowBuild(playerRemover, event.getEntity().getLocation(), Material.AIR); + if (noBuildReason != null) + { + event.setCancelled(true); + GriefPrevention.sendMessage(playerRemover, TextMode.Err, noBuildReason); } } - - //when a painting is placed... - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPaintingPlace(HangingPlaceEvent event) - { - //don't track in worlds where claims are not enabled - if(!GriefPrevention.instance.claimsEnabledForWorld(event.getBlock().getWorld())) return; - - //FEATURE: similar to above, placing a painting requires build permission in the claim - - //if the player doesn't have permission, don't allow the placement - String noBuildReason = GriefPrevention.instance.allowBuild(event.getPlayer(), event.getEntity().getLocation(), Material.PAINTING); - if(noBuildReason != null) - { - event.setCancelled(true); - GriefPrevention.sendMessage(event.getPlayer(), TextMode.Err, noBuildReason); - return; - } - - //otherwise, apply entity-count limitations for creative worlds - else if(GriefPrevention.instance.creativeRulesApply(event.getEntity().getLocation())) - { - PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getUniqueId()); - Claim claim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, playerData.lastClaim); - if(claim == null) return; - - String noEntitiesReason = claim.allowMoreEntities(false); - if(noEntitiesReason != null) - { - GriefPrevention.sendMessage(event.getPlayer(), TextMode.Err, noEntitiesReason); - event.setCancelled(true); - return; - } - } - } - - private boolean isMonster(Entity entity) + + //when a painting is placed... + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPaintingPlace(HangingPlaceEvent event) { - if(entity instanceof Monster) return true; - - EntityType type = entity.getType(); - if(type == EntityType.GHAST || type == EntityType.MAGMA_CUBE || type == EntityType.SHULKER || type == EntityType.POLAR_BEAR) return true; - - if(type == EntityType.RABBIT) + //don't track in worlds where claims are not enabled + if (!GriefPrevention.instance.claimsEnabledForWorld(event.getBlock().getWorld())) return; + + //FEATURE: similar to above, placing a painting requires build permission in the claim + + //if the player doesn't have permission, don't allow the placement + String noBuildReason = GriefPrevention.instance.allowBuild(event.getPlayer(), event.getEntity().getLocation(), Material.PAINTING); + if (noBuildReason != null) { - Rabbit rabbit = (Rabbit)entity; - if(rabbit.getRabbitType() == Rabbit.Type.THE_KILLER_BUNNY) return true; + event.setCancelled(true); + GriefPrevention.sendMessage(event.getPlayer(), TextMode.Err, noBuildReason); + return; } - + + //otherwise, apply entity-count limitations for creative worlds + else if (GriefPrevention.instance.creativeRulesApply(event.getEntity().getLocation())) + { + PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getUniqueId()); + Claim claim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, playerData.lastClaim); + if (claim == null) return; + + String noEntitiesReason = claim.allowMoreEntities(false); + if (noEntitiesReason != null) + { + GriefPrevention.sendMessage(event.getPlayer(), TextMode.Err, noEntitiesReason); + event.setCancelled(true); + return; + } + } + } + + private boolean isMonster(Entity entity) + { + if (entity instanceof Monster) return true; + + EntityType type = entity.getType(); + if (type == EntityType.GHAST || type == EntityType.MAGMA_CUBE || type == EntityType.SHULKER || type == EntityType.POLAR_BEAR) + return true; + + if (type == EntityType.RABBIT) + { + Rabbit rabbit = (Rabbit) entity; + if (rabbit.getRabbitType() == Rabbit.Type.THE_KILLER_BUNNY) return true; + } + return false; } - - //when an entity is damaged - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onEntityDamage (EntityDamageEvent event) - { - this.handleEntityDamageEvent(event, true); - } - - //when an entity is set on fire - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onEntityCombustByEntity (EntityCombustByEntityEvent event) - { - //handle it just like we would an entity damge by entity event, except don't send player messages to avoid double messages - //in cases like attacking with a flame sword or flame arrow, which would ALSO trigger the direct damage event handler - @SuppressWarnings("deprecation") + + //when an entity is damaged + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onEntityDamage(EntityDamageEvent event) + { + this.handleEntityDamageEvent(event, true); + } + + //when an entity is set on fire + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onEntityCombustByEntity(EntityCombustByEntityEvent event) + { + //handle it just like we would an entity damge by entity event, except don't send player messages to avoid double messages + //in cases like attacking with a flame sword or flame arrow, which would ALSO trigger the direct damage event handler + @SuppressWarnings("deprecation") EntityDamageByEntityEvent eventWrapper = new EntityDamageByEntityEvent(event.getCombuster(), event.getEntity(), DamageCause.FIRE_TICK, event.getDuration()); - this.handleEntityDamageEvent(eventWrapper, false); - event.setCancelled(eventWrapper.isCancelled()); - } - - private void handleEntityDamageEvent(EntityDamageEvent event, boolean sendErrorMessagesToPlayers) - { - //monsters are never protected - if(isMonster(event.getEntity())) return; - + this.handleEntityDamageEvent(eventWrapper, false); + event.setCancelled(eventWrapper.isCancelled()); + } + + private void handleEntityDamageEvent(EntityDamageEvent event, boolean sendErrorMessagesToPlayers) + { + //monsters are never protected + if (isMonster(event.getEntity())) return; + //horse protections can be disabled - if(event.getEntity() instanceof Horse && !GriefPrevention.instance.config_claims_protectHorses) return; - if(event.getEntity() instanceof Donkey && !GriefPrevention.instance.config_claims_protectDonkeys) return; - if(event.getEntity() instanceof Mule && !GriefPrevention.instance.config_claims_protectDonkeys) return; - if(event.getEntity() instanceof Llama && !GriefPrevention.instance.config_claims_protectLlamas) return; + if (event.getEntity() instanceof Horse && !GriefPrevention.instance.config_claims_protectHorses) return; + if (event.getEntity() instanceof Donkey && !GriefPrevention.instance.config_claims_protectDonkeys) return; + if (event.getEntity() instanceof Mule && !GriefPrevention.instance.config_claims_protectDonkeys) return; + if (event.getEntity() instanceof Llama && !GriefPrevention.instance.config_claims_protectLlamas) return; //protected death loot can't be destroyed, only picked up or despawned due to expiration - if(event.getEntityType() == EntityType.DROPPED_ITEM) + if (event.getEntityType() == EntityType.DROPPED_ITEM) { - if(event.getEntity().hasMetadata("GP_ITEMOWNER")) + if (event.getEntity().hasMetadata("GP_ITEMOWNER")) { event.setCancelled(true); } } - + //protect pets from environmental damage types which could be easily caused by griefers - if(event.getEntity() instanceof Tameable && !GriefPrevention.instance.pvpRulesApply(event.getEntity().getWorld())) + if (event.getEntity() instanceof Tameable && !GriefPrevention.instance.pvpRulesApply(event.getEntity().getWorld())) { - Tameable tameable = (Tameable)event.getEntity(); - if(tameable.isTamed()) + Tameable tameable = (Tameable) event.getEntity(); + if (tameable.isTamed()) { DamageCause cause = event.getCause(); - if( cause != null && ( - cause == DamageCause.ENTITY_EXPLOSION || - cause == DamageCause.FALLING_BLOCK || - cause == DamageCause.FIRE || - cause == DamageCause.FIRE_TICK || - cause == DamageCause.LAVA || - cause == DamageCause.SUFFOCATION || - cause == DamageCause.CONTACT || - cause == DamageCause.DROWNING)) + if (cause != null && ( + cause == DamageCause.ENTITY_EXPLOSION || + cause == DamageCause.FALLING_BLOCK || + cause == DamageCause.FIRE || + cause == DamageCause.FIRE_TICK || + cause == DamageCause.LAVA || + cause == DamageCause.SUFFOCATION || + cause == DamageCause.CONTACT || + cause == DamageCause.DROWNING)) { event.setCancelled(true); return; } } } - + //the rest is only interested in entities damaging entities (ignoring environmental damage) - if(!(event instanceof EntityDamageByEntityEvent)) return; - + if (!(event instanceof EntityDamageByEntityEvent)) return; + EntityDamageByEntityEvent subEvent = (EntityDamageByEntityEvent) event; - if(subEvent.getDamager() instanceof LightningStrike && subEvent.getDamager().hasMetadata("GP_TRIDENT")) + if (subEvent.getDamager() instanceof LightningStrike && subEvent.getDamager().hasMetadata("GP_TRIDENT")) { - event.setCancelled(true); - return; - } + event.setCancelled(true); + return; + } //determine which player is attacking, if any Player attacker = null; Projectile arrow = null; Firework firework = null; Entity damageSource = subEvent.getDamager(); - - if(damageSource != null) + + if (damageSource != null) { - if(damageSource.getType() == EntityType.PLAYER) + if (damageSource.getType() == EntityType.PLAYER) { - attacker = (Player)damageSource; - } - else if(damageSource instanceof Projectile) + attacker = (Player) damageSource; + } else if (damageSource instanceof Projectile) { - arrow = (Projectile)damageSource; - if(arrow.getShooter() instanceof Player) + arrow = (Projectile) damageSource; + if (arrow.getShooter() instanceof Player) { - attacker = (Player)arrow.getShooter(); + attacker = (Player) arrow.getShooter(); } - }else if(subEvent.getDamager() instanceof Firework) { - damageSource = subEvent.getDamager(); - if(damageSource.hasMetadata("GP_FIREWORK")) { - List data = damageSource.getMetadata("GP_FIREWORK"); - if(data != null && data.size() > 0) - { - firework = (Firework)damageSource; - attacker = (Player) data.get(0).value(); - } - } - } - - //protect players from lingering potion damage when protected from pvp - if(damageSource.getType() == EntityType.AREA_EFFECT_CLOUD && event.getEntityType() == EntityType.PLAYER && GriefPrevention.instance.pvpRulesApply(event.getEntity().getWorld())) + } else if (subEvent.getDamager() instanceof Firework) { - Player damaged = (Player)event.getEntity(); + damageSource = subEvent.getDamager(); + if (damageSource.hasMetadata("GP_FIREWORK")) + { + List data = damageSource.getMetadata("GP_FIREWORK"); + if (data != null && data.size() > 0) + { + firework = (Firework) damageSource; + attacker = (Player) data.get(0).value(); + } + } + } + + //protect players from lingering potion damage when protected from pvp + if (damageSource.getType() == EntityType.AREA_EFFECT_CLOUD && event.getEntityType() == EntityType.PLAYER && GriefPrevention.instance.pvpRulesApply(event.getEntity().getWorld())) + { + Player damaged = (Player) event.getEntity(); PlayerData damagedData = GriefPrevention.instance.dataStore.getPlayerData(damaged.getUniqueId()); - + //case 1: recently spawned - if(GriefPrevention.instance.config_pvp_protectFreshSpawns && damagedData.pvpImmune) + if (GriefPrevention.instance.config_pvp_protectFreshSpawns && damagedData.pvpImmune) { event.setCancelled(true); return; } - + //case 2: in a pvp safe zone else { Claim damagedClaim = GriefPrevention.instance.dataStore.getClaimAt(damaged.getLocation(), false, damagedData.lastClaim); - if(damagedClaim != null) + if (damagedClaim != null) { damagedData.lastClaim = damagedClaim; - if(GriefPrevention.instance.claimIsPvPSafeZone(damagedClaim)) + if (GriefPrevention.instance.claimIsPvPSafeZone(damagedClaim)) { PreventPvPEvent pvpEvent = new PreventPvPEvent(damagedClaim); Bukkit.getPluginManager().callEvent(pvpEvent); - if(!pvpEvent.isCancelled()) + if (!pvpEvent.isCancelled()) { event.setCancelled(true); return; @@ -802,81 +800,86 @@ public class EntityEventHandler implements Listener } } } - + //if the attacker is a firework from a crossbow by a player and defender is a player (nonpvp) - if(firework != null && event.getEntityType() == EntityType.PLAYER && !GriefPrevention.instance.pvpRulesApply(attacker.getWorld())) + if (firework != null && event.getEntityType() == EntityType.PLAYER && !GriefPrevention.instance.pvpRulesApply(attacker.getWorld())) { - Player defender = (Player)(event.getEntity()); - if(attacker != defender) { - event.setCancelled(true); - return; - } + Player defender = (Player) (event.getEntity()); + if (attacker != defender) + { + event.setCancelled(true); + return; + } } - - + + //if the attacker is a player and defender is a player (pvp combat) - if(attacker != null && event.getEntityType() == EntityType.PLAYER && GriefPrevention.instance.pvpRulesApply(attacker.getWorld())) + if (attacker != null && event.getEntityType() == EntityType.PLAYER && GriefPrevention.instance.pvpRulesApply(attacker.getWorld())) { //FEATURE: prevent pvp in the first minute after spawn, and prevent pvp when one or both players have no inventory - - Player defender = (Player)(event.getEntity()); - - if(attacker != defender) + + Player defender = (Player) (event.getEntity()); + + if (attacker != defender) { - PlayerData defenderData = this.dataStore.getPlayerData(((Player)event.getEntity()).getUniqueId()); + PlayerData defenderData = this.dataStore.getPlayerData(((Player) event.getEntity()).getUniqueId()); PlayerData attackerData = this.dataStore.getPlayerData(attacker.getUniqueId()); - + //otherwise if protecting spawning players - if(GriefPrevention.instance.config_pvp_protectFreshSpawns) + if (GriefPrevention.instance.config_pvp_protectFreshSpawns) { - if(defenderData.pvpImmune) + if (defenderData.pvpImmune) { event.setCancelled(true); - if(sendErrorMessagesToPlayers) GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.ThatPlayerPvPImmune); + if (sendErrorMessagesToPlayers) + GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.ThatPlayerPvPImmune); return; } - - if(attackerData.pvpImmune) + + if (attackerData.pvpImmune) { event.setCancelled(true); - if(sendErrorMessagesToPlayers) GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune); + if (sendErrorMessagesToPlayers) + GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune); return; - } + } } - + //FEATURE: prevent players from engaging in PvP combat inside land claims (when it's disabled) - if(GriefPrevention.instance.config_pvp_noCombatInPlayerLandClaims || GriefPrevention.instance.config_pvp_noCombatInAdminLandClaims) + if (GriefPrevention.instance.config_pvp_noCombatInPlayerLandClaims || GriefPrevention.instance.config_pvp_noCombatInAdminLandClaims) { Claim attackerClaim = this.dataStore.getClaimAt(attacker.getLocation(), false, attackerData.lastClaim); - if(!attackerData.ignoreClaims) + if (!attackerData.ignoreClaims) { - if( attackerClaim != null && //ignore claims mode allows for pvp inside land claims - !attackerData.inPvpCombat() && - GriefPrevention.instance.claimIsPvPSafeZone(attackerClaim)) + if (attackerClaim != null && //ignore claims mode allows for pvp inside land claims + !attackerData.inPvpCombat() && + GriefPrevention.instance.claimIsPvPSafeZone(attackerClaim)) { attackerData.lastClaim = attackerClaim; PreventPvPEvent pvpEvent = new PreventPvPEvent(attackerClaim); Bukkit.getPluginManager().callEvent(pvpEvent); - if(!pvpEvent.isCancelled()) + if (!pvpEvent.isCancelled()) { event.setCancelled(true); - if(sendErrorMessagesToPlayers) GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune); + if (sendErrorMessagesToPlayers) + GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune); return; } } - + Claim defenderClaim = this.dataStore.getClaimAt(defender.getLocation(), false, defenderData.lastClaim); - if( defenderClaim != null && - !defenderData.inPvpCombat() && - GriefPrevention.instance.claimIsPvPSafeZone(defenderClaim)) + if (defenderClaim != null && + !defenderData.inPvpCombat() && + GriefPrevention.instance.claimIsPvPSafeZone(defenderClaim)) { defenderData.lastClaim = defenderClaim; PreventPvPEvent pvpEvent = new PreventPvPEvent(defenderClaim); Bukkit.getPluginManager().callEvent(pvpEvent); - if(!pvpEvent.isCancelled()) + if (!pvpEvent.isCancelled()) { event.setCancelled(true); - if(sendErrorMessagesToPlayers) GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.PlayerInPvPSafeZone); + if (sendErrorMessagesToPlayers) + GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.PlayerInPvPSafeZone); return; } } @@ -884,43 +887,43 @@ public class EntityEventHandler implements Listener } } } - - if(event instanceof EntityDamageByEntityEvent) + + if (event instanceof EntityDamageByEntityEvent) { //don't track in worlds where claims are not enabled - if(!GriefPrevention.instance.claimsEnabledForWorld(event.getEntity().getWorld())) return; - + if (!GriefPrevention.instance.claimsEnabledForWorld(event.getEntity().getWorld())) return; + //protect players from being attacked by other players' pets when protected from pvp - if(event.getEntityType() == EntityType.PLAYER) + if (event.getEntityType() == EntityType.PLAYER) { - Player defender = (Player)event.getEntity(); - + Player defender = (Player) event.getEntity(); + //if attacker is a pet Entity damager = subEvent.getDamager(); - if(damager != null && damager instanceof Tameable) + if (damager != null && damager instanceof Tameable) { Tameable pet = (Tameable) damager; - if(pet.isTamed() && pet.getOwner() != null) + if (pet.isTamed() && pet.getOwner() != null) { //if defender is NOT in pvp combat and not immune to pvp right now due to recent respawn PlayerData defenderData = GriefPrevention.instance.dataStore.getPlayerData(event.getEntity().getUniqueId()); - if(!defenderData.pvpImmune && !defenderData.inPvpCombat()) + if (!defenderData.pvpImmune && !defenderData.inPvpCombat()) { //if defender is not in a protected area Claim defenderClaim = this.dataStore.getClaimAt(defender.getLocation(), false, defenderData.lastClaim); - if( defenderClaim != null && - !defenderData.inPvpCombat() && - GriefPrevention.instance.claimIsPvPSafeZone(defenderClaim)) + if (defenderClaim != null && + !defenderData.inPvpCombat() && + GriefPrevention.instance.claimIsPvPSafeZone(defenderClaim)) { defenderData.lastClaim = defenderClaim; PreventPvPEvent pvpEvent = new PreventPvPEvent(defenderClaim); Bukkit.getPluginManager().callEvent(pvpEvent); - + //if other plugins aren't making an exception to the rule - if(!pvpEvent.isCancelled()) + if (!pvpEvent.isCancelled()) { event.setCancelled(true); - if(damager instanceof Creature) ((Creature) damager).setTarget(null); + if (damager instanceof Creature) ((Creature) damager).setTarget(null); return; } } @@ -928,101 +931,105 @@ public class EntityEventHandler implements Listener } } } - + //if the damaged entity is a claimed item frame or armor stand, the damager needs to be a player with build trust in the claim - if(subEvent.getEntityType() == EntityType.ITEM_FRAME - || subEvent.getEntityType() == EntityType.ARMOR_STAND - || subEvent.getEntityType() == EntityType.VILLAGER - || subEvent.getEntityType() == EntityType.ENDER_CRYSTAL) + if (subEvent.getEntityType() == EntityType.ITEM_FRAME + || subEvent.getEntityType() == EntityType.ARMOR_STAND + || subEvent.getEntityType() == EntityType.VILLAGER + || subEvent.getEntityType() == EntityType.ENDER_CRYSTAL) { //allow for disabling villager protections in the config - if(subEvent.getEntityType() == EntityType.VILLAGER && !GriefPrevention.instance.config_claims_protectCreatures) return; - + if (subEvent.getEntityType() == EntityType.VILLAGER && !GriefPrevention.instance.config_claims_protectCreatures) + return; + //don't protect polar bears, they may be aggressive - if(subEvent.getEntityType() == EntityType.POLAR_BEAR) return; - + if (subEvent.getEntityType() == EntityType.POLAR_BEAR) return; + //decide whether it's claimed Claim cachedClaim = null; PlayerData playerData = null; - if(attacker != null) + if (attacker != null) { playerData = this.dataStore.getPlayerData(attacker.getUniqueId()); cachedClaim = playerData.lastClaim; } - + Claim claim = this.dataStore.getClaimAt(event.getEntity().getLocation(), false, cachedClaim); - + //if it's claimed - if(claim != null) + if (claim != null) { //if attacker isn't a player, cancel - if(attacker == null) + if (attacker == null) { //exception case - if(event.getEntityType() == EntityType.VILLAGER && damageSource != null && damageSource.getType() == EntityType.ZOMBIE) + if (event.getEntityType() == EntityType.VILLAGER && damageSource != null && damageSource.getType() == EntityType.ZOMBIE) { return; } - + event.setCancelled(true); return; } - + //otherwise player must have container trust in the claim String failureReason = claim.allowBuild(attacker, Material.AIR); - if(failureReason != null) + if (failureReason != null) { event.setCancelled(true); - if(sendErrorMessagesToPlayers) GriefPrevention.sendMessage(attacker, TextMode.Err, failureReason); + if (sendErrorMessagesToPlayers) + GriefPrevention.sendMessage(attacker, TextMode.Err, failureReason); return; } } } - + //if the entity is an non-monster creature (remember monsters disqualified above), or a vehicle if (((subEvent.getEntity() instanceof Creature || subEvent.getEntity() instanceof WaterMob) && GriefPrevention.instance.config_claims_protectCreatures)) { //if entity is tameable and has an owner, apply special rules - if(subEvent.getEntity() instanceof Tameable) + if (subEvent.getEntity() instanceof Tameable) { - Tameable tameable = (Tameable)subEvent.getEntity(); - if(tameable.isTamed() && tameable.getOwner() != null) + Tameable tameable = (Tameable) subEvent.getEntity(); + if (tameable.isTamed() && tameable.getOwner() != null) { //limit attacks by players to owners and admins in ignore claims mode - if(attacker != null) + if (attacker != null) { UUID ownerID = tameable.getOwner().getUniqueId(); - + //if the player interacting is the owner, always allow - if(attacker.getUniqueId().equals(ownerID)) return; - + if (attacker.getUniqueId().equals(ownerID)) return; + //allow for admin override PlayerData attackerData = this.dataStore.getPlayerData(attacker.getUniqueId()); - if(attackerData.ignoreClaims) return; - + if (attackerData.ignoreClaims) return; + //otherwise disallow in non-pvp worlds (and also pvp worlds if configured to do so) - if(!GriefPrevention.instance.pvpRulesApply(subEvent.getEntity().getLocation().getWorld()) || (GriefPrevention.instance.config_pvp_protectPets && subEvent.getEntityType() != EntityType.WOLF)) + if (!GriefPrevention.instance.pvpRulesApply(subEvent.getEntity().getLocation().getWorld()) || (GriefPrevention.instance.config_pvp_protectPets && subEvent.getEntityType() != EntityType.WOLF)) { - OfflinePlayer owner = GriefPrevention.instance.getServer().getOfflinePlayer(ownerID); + OfflinePlayer owner = GriefPrevention.instance.getServer().getOfflinePlayer(ownerID); String ownerName = owner.getName(); - if(ownerName == null) ownerName = "someone"; + if (ownerName == null) ownerName = "someone"; String message = GriefPrevention.instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, ownerName); - if(attacker.hasPermission("griefprevention.ignoreclaims")) + if (attacker.hasPermission("griefprevention.ignoreclaims")) message += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); - if(sendErrorMessagesToPlayers) GriefPrevention.sendMessage(attacker, TextMode.Err, message); + if (sendErrorMessagesToPlayers) + GriefPrevention.sendMessage(attacker, TextMode.Err, message); PreventPvPEvent pvpEvent = new PreventPvPEvent(new Claim(subEvent.getEntity().getLocation(), subEvent.getEntity().getLocation(), null, new ArrayList(), new ArrayList(), new ArrayList(), new ArrayList(), null)); Bukkit.getPluginManager().callEvent(pvpEvent); - if(!pvpEvent.isCancelled()) + if (!pvpEvent.isCancelled()) { event.setCancelled(true); } return; } //and disallow if attacker is pvp immune - else if(attackerData.pvpImmune) + else if (attackerData.pvpImmune) { event.setCancelled(true); - if(sendErrorMessagesToPlayers) GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune); + if (sendErrorMessagesToPlayers) + GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune); return; } // disallow players attacking tamed wolves (dogs) unless under attack by said wolf @@ -1047,158 +1054,161 @@ public class EntityEventHandler implements Listener } } } - + Claim cachedClaim = null; PlayerData playerData = null; - + //if not a player or an explosive, allow - //RoboMWM: Or a lingering potion, or a witch - if(attacker == null + //RoboMWM: Or a lingering potion, or a witch + if (attacker == null && damageSource != null && damageSource.getType() != EntityType.CREEPER && damageSource.getType() != EntityType.WITHER && damageSource.getType() != EntityType.ENDER_CRYSTAL && damageSource.getType() != EntityType.AREA_EFFECT_CLOUD - && damageSource.getType() != EntityType.WITCH + && damageSource.getType() != EntityType.WITCH && !(damageSource instanceof Projectile) && !(damageSource instanceof Explosive) && !(damageSource instanceof ExplosiveMinecart)) { return; } - - if(attacker != null) + + if (attacker != null) { playerData = this.dataStore.getPlayerData(attacker.getUniqueId()); cachedClaim = playerData.lastClaim; } - + Claim claim = this.dataStore.getClaimAt(event.getEntity().getLocation(), false, cachedClaim); - + //if it's claimed - if(claim != null) + if (claim != null) { //if damaged by anything other than a player (exception villagers injured by zombies in admin claims), cancel the event //why exception? so admins can set up a village which can't be CHANGED by players, but must be "protected" by players. - //TODO: Discuss if this should only apply to admin claims...? - if(attacker == null) + //TODO: Discuss if this should only apply to admin claims...? + if (attacker == null) { //exception case - if(event.getEntityType() == EntityType.VILLAGER && damageSource != null && (damageSource.getType() == EntityType.ZOMBIE || damageSource.getType() == EntityType.VINDICATOR || damageSource.getType() == EntityType.EVOKER || damageSource.getType() == EntityType.EVOKER_FANGS || damageSource.getType() == EntityType.VEX)) + if (event.getEntityType() == EntityType.VILLAGER && damageSource != null && (damageSource.getType() == EntityType.ZOMBIE || damageSource.getType() == EntityType.VINDICATOR || damageSource.getType() == EntityType.EVOKER || damageSource.getType() == EntityType.EVOKER_FANGS || damageSource.getType() == EntityType.VEX)) { return; } - + //all other cases else { event.setCancelled(true); - if(damageSource instanceof Projectile) + if (damageSource instanceof Projectile) { damageSource.remove(); } - } + } } - + //otherwise the player damaging the entity must have permission, unless it's a dog in a pvp world - else if(!(event.getEntity().getWorld().getPVP() && event.getEntity().getType() == EntityType.WOLF)) + else if (!(event.getEntity().getWorld().getPVP() && event.getEntity().getType() == EntityType.WOLF)) { String noContainersReason = claim.allowContainers(attacker); - if(noContainersReason != null) + if (noContainersReason != null) { event.setCancelled(true); - + //kill the arrow to avoid infinite bounce between crowded together animals //RoboMWM: except for tridents - if(arrow != null && arrow.getType() != EntityType.TRIDENT) arrow.remove(); - if(damageSource != null && damageSource.getType() == EntityType.FIREWORK && event.getEntity().getType() != EntityType.PLAYER) return; - - if(sendErrorMessagesToPlayers) + if (arrow != null && arrow.getType() != EntityType.TRIDENT) arrow.remove(); + if (damageSource != null && damageSource.getType() == EntityType.FIREWORK && event.getEntity().getType() != EntityType.PLAYER) + return; + + if (sendErrorMessagesToPlayers) { String message = GriefPrevention.instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, claim.getOwnerName()); - if(attacker.hasPermission("griefprevention.ignoreclaims")) + if (attacker.hasPermission("griefprevention.ignoreclaims")) message += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); GriefPrevention.sendMessage(attacker, TextMode.Err, message); } event.setCancelled(true); } - + //cache claim for later - if(playerData != null) + if (playerData != null) { playerData.lastClaim = claim; - } + } } } } } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onCrossbowFireWork(EntityShootBowEvent shootEvent) { - if(shootEvent.getEntity() instanceof Player && shootEvent.getProjectile() instanceof Firework) { - shootEvent.getProjectile().setMetadata("GP_FIREWORK", new FixedMetadataValue(GriefPrevention.instance, shootEvent.getEntity())); - } - } - - //when an entity is damaged + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onCrossbowFireWork(EntityShootBowEvent shootEvent) + { + if (shootEvent.getEntity() instanceof Player && shootEvent.getProjectile() instanceof Firework) + { + shootEvent.getProjectile().setMetadata("GP_FIREWORK", new FixedMetadataValue(GriefPrevention.instance, shootEvent.getEntity())); + } + } + + //when an entity is damaged @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onEntityDamageMonitor (EntityDamageEvent event) + public void onEntityDamageMonitor(EntityDamageEvent event) { //FEATURE: prevent players who very recently participated in pvp combat from hiding inventory to protect it from looting //FEATURE: prevent players who are in pvp combat from logging out to avoid being defeated - - if(event.getEntity().getType() != EntityType.PLAYER) return; - - Player defender = (Player)event.getEntity(); - - //only interested in entities damaging entities (ignoring environmental damage) - if(!(event instanceof EntityDamageByEntityEvent)) return; - //Ignore "damage" from snowballs, eggs, etc. from triggering the PvP timer - if (event.getDamage() == 0) return; - + if (event.getEntity().getType() != EntityType.PLAYER) return; + + Player defender = (Player) event.getEntity(); + + //only interested in entities damaging entities (ignoring environmental damage) + if (!(event instanceof EntityDamageByEntityEvent)) return; + + //Ignore "damage" from snowballs, eggs, etc. from triggering the PvP timer + if (event.getDamage() == 0) return; + EntityDamageByEntityEvent subEvent = (EntityDamageByEntityEvent) event; - + //if not in a pvp rules world, do nothing - if(!GriefPrevention.instance.pvpRulesApply(defender.getWorld())) return; - + if (!GriefPrevention.instance.pvpRulesApply(defender.getWorld())) return; + //determine which player is attacking, if any Player attacker = null; Projectile arrow = null; Entity damageSource = subEvent.getDamager(); - - if(damageSource != null) + + if (damageSource != null) { - if(damageSource.getType() == EntityType.PLAYER) + if (damageSource.getType() == EntityType.PLAYER) { - attacker = (Player)damageSource; - } - else if(damageSource instanceof Projectile) + attacker = (Player) damageSource; + } else if (damageSource instanceof Projectile) { - arrow = (Projectile)damageSource; - if(arrow.getShooter() instanceof Player) + arrow = (Projectile) damageSource; + if (arrow.getShooter() instanceof Player) { - attacker = (Player)arrow.getShooter(); + attacker = (Player) arrow.getShooter(); + } + } else if (damageSource instanceof Firework) + { + if (damageSource.hasMetadata("GP_FIREWORK")) + { + List data = damageSource.getMetadata("GP_FIREWORK"); + if (data != null && data.size() > 0) + { + attacker = (Player) data.get(0).value(); + } } } - else if(damageSource instanceof Firework) { - if(damageSource.hasMetadata("GP_FIREWORK")) { - List data = damageSource.getMetadata("GP_FIREWORK"); - if(data != null && data.size() > 0) - { - attacker = (Player) data.get(0).value(); - } - } - } } - + //if attacker not a player, do nothing - if(attacker == null) return; - + if (attacker == null) return; + PlayerData defenderData = this.dataStore.getPlayerData(defender.getUniqueId()); PlayerData attackerData = this.dataStore.getPlayerData(attacker.getUniqueId()); - - if(attacker != defender) + + if (attacker != defender) { long now = Calendar.getInstance().getTimeInMillis(); defenderData.lastPvpTimestamp = now; @@ -1207,213 +1217,213 @@ public class EntityEventHandler implements Listener attackerData.lastPvpPlayer = defender.getName(); } } - - //when a vehicle is damaged - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onVehicleDamage (VehicleDamageEvent event) - { - //all of this is anti theft code - if(!GriefPrevention.instance.config_claims_preventTheft) return; - - //input validation - if(event.getVehicle() == null) return; - - //don't track in worlds where claims are not enabled - if(!GriefPrevention.instance.claimsEnabledForWorld(event.getVehicle().getWorld())) return; - - //determine which player is attacking, if any - Player attacker = null; - Entity damageSource = event.getAttacker(); - EntityType damageSourceType = null; - //if damage source is null or a creeper, don't allow the damage when the vehicle is in a land claim - if(damageSource != null) - { - damageSourceType = damageSource.getType(); + //when a vehicle is damaged + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onVehicleDamage(VehicleDamageEvent event) + { + //all of this is anti theft code + if (!GriefPrevention.instance.config_claims_preventTheft) return; - if(damageSource.getType() == EntityType.PLAYER) - { - attacker = (Player)damageSource; - } - else if(damageSource instanceof Projectile) - { - Projectile arrow = (Projectile)damageSource; - if(arrow.getShooter() instanceof Player) - { - attacker = (Player)arrow.getShooter(); - } - } - else if(damageSource instanceof Firework) { - if(damageSource.hasMetadata("GP_FIREWORK")) { - List data = damageSource.getMetadata("GP_FIREWORK"); - if(data != null && data.size() > 0) - { - attacker = (Player) data.get(0).value(); - } - } + //input validation + if (event.getVehicle() == null) return; + + //don't track in worlds where claims are not enabled + if (!GriefPrevention.instance.claimsEnabledForWorld(event.getVehicle().getWorld())) return; + + //determine which player is attacking, if any + Player attacker = null; + Entity damageSource = event.getAttacker(); + EntityType damageSourceType = null; + + //if damage source is null or a creeper, don't allow the damage when the vehicle is in a land claim + if (damageSource != null) + { + damageSourceType = damageSource.getType(); + + if (damageSource.getType() == EntityType.PLAYER) + { + attacker = (Player) damageSource; + } else if (damageSource instanceof Projectile) + { + Projectile arrow = (Projectile) damageSource; + if (arrow.getShooter() instanceof Player) + { + attacker = (Player) arrow.getShooter(); + } + } else if (damageSource instanceof Firework) + { + if (damageSource.hasMetadata("GP_FIREWORK")) + { + List data = damageSource.getMetadata("GP_FIREWORK"); + if (data != null && data.size() > 0) + { + attacker = (Player) data.get(0).value(); + } + } } - } - - //if not a player and not an explosion, always allow - if(attacker == null && damageSourceType != EntityType.CREEPER && damageSourceType != EntityType.WITHER && damageSourceType != EntityType.PRIMED_TNT) + } + + //if not a player and not an explosion, always allow + if (attacker == null && damageSourceType != EntityType.CREEPER && damageSourceType != EntityType.WITHER && damageSourceType != EntityType.PRIMED_TNT) { return; } - - //NOTE: vehicles can be pushed around. - //so unless precautions are taken by the owner, a resourceful thief might find ways to steal anyway - Claim cachedClaim = null; - PlayerData playerData = null; - - if(attacker != null) - { - playerData = this.dataStore.getPlayerData(attacker.getUniqueId()); - cachedClaim = playerData.lastClaim; - } - - Claim claim = this.dataStore.getClaimAt(event.getVehicle().getLocation(), false, cachedClaim); - - //if it's claimed - if(claim != null) - { - //if damaged by anything other than a player, cancel the event - if(attacker == null) - { - event.setCancelled(true); - } - - //otherwise the player damaging the entity must have permission - else - { - String noContainersReason = claim.allowContainers(attacker); - if(noContainersReason != null) - { - event.setCancelled(true); - String message = GriefPrevention.instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, claim.getOwnerName()); - if(attacker.hasPermission("griefprevention.ignoreclaims")) + + //NOTE: vehicles can be pushed around. + //so unless precautions are taken by the owner, a resourceful thief might find ways to steal anyway + Claim cachedClaim = null; + PlayerData playerData = null; + + if (attacker != null) + { + playerData = this.dataStore.getPlayerData(attacker.getUniqueId()); + cachedClaim = playerData.lastClaim; + } + + Claim claim = this.dataStore.getClaimAt(event.getVehicle().getLocation(), false, cachedClaim); + + //if it's claimed + if (claim != null) + { + //if damaged by anything other than a player, cancel the event + if (attacker == null) + { + event.setCancelled(true); + } + + //otherwise the player damaging the entity must have permission + else + { + String noContainersReason = claim.allowContainers(attacker); + if (noContainersReason != null) + { + event.setCancelled(true); + String message = GriefPrevention.instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, claim.getOwnerName()); + if (attacker.hasPermission("griefprevention.ignoreclaims")) message += " " + GriefPrevention.instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); GriefPrevention.sendMessage(attacker, TextMode.Err, message); event.setCancelled(true); - } - - //cache claim for later - if(playerData != null) - { - playerData.lastClaim = claim; - } - } - } - } - - //when a splash potion effects one or more entities... - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPotionSplash (PotionSplashEvent event) - { - ThrownPotion potion = event.getPotion(); - - //ignore potions not thrown by players - ProjectileSource projectileSource = potion.getShooter(); - if(projectileSource == null) return; + } + + //cache claim for later + if (playerData != null) + { + playerData.lastClaim = claim; + } + } + } + } + + //when a splash potion effects one or more entities... + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPotionSplash(PotionSplashEvent event) + { + ThrownPotion potion = event.getPotion(); + + //ignore potions not thrown by players + ProjectileSource projectileSource = potion.getShooter(); + if (projectileSource == null) return; Player thrower = null; - if ((projectileSource instanceof Player)) - thrower = (Player)projectileSource; - - Collection effects = potion.getEffects(); - for(PotionEffect effect : effects) - { - PotionEffectType effectType = effect.getType(); + if ((projectileSource instanceof Player)) + thrower = (Player) projectileSource; - //restrict some potions on claimed animals (griefers could use this to kill or steal animals over fences) //RoboMWM: include villagers - if(effectType.getName().equals("JUMP") || effectType.getName().equals("POISON")) - { - Claim cachedClaim = null; - for(LivingEntity effected : event.getAffectedEntities()) - { - if(effected.getType() == EntityType.VILLAGER || effected instanceof Animals) - { - Claim claim = this.dataStore.getClaimAt(effected.getLocation(), false, cachedClaim); - if(claim != null) - { - cachedClaim = claim; - if(thrower == null || claim.allowContainers(thrower) != null) - { - event.setIntensity(effected, 0); - instance.sendMessage(thrower, TextMode.Err, Messages.NoDamageClaimedEntity, claim.getOwnerName()); - return; - } - } - } - } - } + Collection effects = potion.getEffects(); + for (PotionEffect effect : effects) + { + PotionEffectType effectType = effect.getType(); - //Otherwise, ignore potions not thrown by players - if (thrower == null) return; - - //otherwise, no restrictions for positive effects - if(positiveEffects.contains(effectType)) continue; - - for(LivingEntity effected : event.getAffectedEntities()) - { - //always impact the thrower - if(effected == thrower) continue; - - //always impact non players - if(effected.getType() != EntityType.PLAYER) continue; - - //otherwise if in no-pvp zone, stop effect - //FEATURE: prevent players from engaging in PvP combat inside land claims (when it's disabled) - else if(GriefPrevention.instance.config_pvp_noCombatInPlayerLandClaims || GriefPrevention.instance.config_pvp_noCombatInAdminLandClaims) - { - Player effectedPlayer = (Player)effected; - PlayerData defenderData = this.dataStore.getPlayerData(effectedPlayer.getUniqueId()); - PlayerData attackerData = this.dataStore.getPlayerData(thrower.getUniqueId()); - Claim attackerClaim = this.dataStore.getClaimAt(thrower.getLocation(), false, attackerData.lastClaim); - if(attackerClaim != null && GriefPrevention.instance.claimIsPvPSafeZone(attackerClaim)) - { - attackerData.lastClaim = attackerClaim; - PreventPvPEvent pvpEvent = new PreventPvPEvent(attackerClaim); - Bukkit.getPluginManager().callEvent(pvpEvent); - if(!pvpEvent.isCancelled()) + //restrict some potions on claimed animals (griefers could use this to kill or steal animals over fences) //RoboMWM: include villagers + if (effectType.getName().equals("JUMP") || effectType.getName().equals("POISON")) + { + Claim cachedClaim = null; + for (LivingEntity effected : event.getAffectedEntities()) + { + if (effected.getType() == EntityType.VILLAGER || effected instanceof Animals) + { + Claim claim = this.dataStore.getClaimAt(effected.getLocation(), false, cachedClaim); + if (claim != null) { - event.setIntensity(effected, 0); - GriefPrevention.sendMessage(thrower, TextMode.Err, Messages.CantFightWhileImmune); - continue; + cachedClaim = claim; + if (thrower == null || claim.allowContainers(thrower) != null) + { + event.setIntensity(effected, 0); + instance.sendMessage(thrower, TextMode.Err, Messages.NoDamageClaimedEntity, claim.getOwnerName()); + return; + } } - } - - Claim defenderClaim = this.dataStore.getClaimAt(effectedPlayer.getLocation(), false, defenderData.lastClaim); - if(defenderClaim != null && GriefPrevention.instance.claimIsPvPSafeZone(defenderClaim)) - { - defenderData.lastClaim = defenderClaim; - PreventPvPEvent pvpEvent = new PreventPvPEvent(defenderClaim); + } + } + } + + //Otherwise, ignore potions not thrown by players + if (thrower == null) return; + + //otherwise, no restrictions for positive effects + if (positiveEffects.contains(effectType)) continue; + + for (LivingEntity effected : event.getAffectedEntities()) + { + //always impact the thrower + if (effected == thrower) continue; + + //always impact non players + if (effected.getType() != EntityType.PLAYER) continue; + + //otherwise if in no-pvp zone, stop effect + //FEATURE: prevent players from engaging in PvP combat inside land claims (when it's disabled) + else if (GriefPrevention.instance.config_pvp_noCombatInPlayerLandClaims || GriefPrevention.instance.config_pvp_noCombatInAdminLandClaims) + { + Player effectedPlayer = (Player) effected; + PlayerData defenderData = this.dataStore.getPlayerData(effectedPlayer.getUniqueId()); + PlayerData attackerData = this.dataStore.getPlayerData(thrower.getUniqueId()); + Claim attackerClaim = this.dataStore.getClaimAt(thrower.getLocation(), false, attackerData.lastClaim); + if (attackerClaim != null && GriefPrevention.instance.claimIsPvPSafeZone(attackerClaim)) + { + attackerData.lastClaim = attackerClaim; + PreventPvPEvent pvpEvent = new PreventPvPEvent(attackerClaim); Bukkit.getPluginManager().callEvent(pvpEvent); - if(!pvpEvent.isCancelled()) + if (!pvpEvent.isCancelled()) { - event.setIntensity(effected, 0); - GriefPrevention.sendMessage(thrower, TextMode.Err, Messages.PlayerInPvPSafeZone); - continue; + event.setIntensity(effected, 0); + GriefPrevention.sendMessage(thrower, TextMode.Err, Messages.CantFightWhileImmune); + continue; } - } - } - } - } - } - - public static final HashSet positiveEffects = new HashSet(Arrays.asList - ( - PotionEffectType.ABSORPTION, - PotionEffectType.DAMAGE_RESISTANCE, - PotionEffectType.FAST_DIGGING, - PotionEffectType.FIRE_RESISTANCE, - PotionEffectType.HEAL, - PotionEffectType.HEALTH_BOOST, - PotionEffectType.INCREASE_DAMAGE, - PotionEffectType.INVISIBILITY, - PotionEffectType.JUMP, - PotionEffectType.NIGHT_VISION, - PotionEffectType.REGENERATION, - PotionEffectType.SATURATION, - PotionEffectType.SPEED, - PotionEffectType.WATER_BREATHING - )); + } + + Claim defenderClaim = this.dataStore.getClaimAt(effectedPlayer.getLocation(), false, defenderData.lastClaim); + if (defenderClaim != null && GriefPrevention.instance.claimIsPvPSafeZone(defenderClaim)) + { + defenderData.lastClaim = defenderClaim; + PreventPvPEvent pvpEvent = new PreventPvPEvent(defenderClaim); + Bukkit.getPluginManager().callEvent(pvpEvent); + if (!pvpEvent.isCancelled()) + { + event.setIntensity(effected, 0); + GriefPrevention.sendMessage(thrower, TextMode.Err, Messages.PlayerInPvPSafeZone); + continue; + } + } + } + } + } + } + + public static final HashSet positiveEffects = new HashSet(Arrays.asList + ( + PotionEffectType.ABSORPTION, + PotionEffectType.DAMAGE_RESISTANCE, + PotionEffectType.FAST_DIGGING, + PotionEffectType.FIRE_RESISTANCE, + PotionEffectType.HEAL, + PotionEffectType.HEALTH_BOOST, + PotionEffectType.INCREASE_DAMAGE, + PotionEffectType.INVISIBILITY, + PotionEffectType.JUMP, + PotionEffectType.NIGHT_VISION, + PotionEffectType.REGENERATION, + PotionEffectType.SATURATION, + PotionEffectType.SPEED, + PotionEffectType.WATER_BREATHING + )); } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java index 0385505..59673b6 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - + package me.ryanhamshire.GriefPrevention; import org.bukkit.entity.Player; @@ -24,55 +24,55 @@ import org.bukkit.inventory.EquipmentSlot; //tells a player about how many claim blocks he has, etc //implemented as a task so that it can be delayed //otherwise, it's spammy when players mouse-wheel past the shovel in their hot bars -class EquipShovelProcessingTask implements Runnable +class EquipShovelProcessingTask implements Runnable { - //player data - private Player player; - - public EquipShovelProcessingTask(Player player) - { - this.player = player; - } - - @Override - public void run() - { - //if he's not holding the golden shovel anymore, do nothing - if(GriefPrevention.instance.getItemInHand(player, EquipmentSlot.HAND).getType() != GriefPrevention.instance.config_claims_modificationTool) return; - - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); - + //player data + private Player player; + + public EquipShovelProcessingTask(Player player) + { + this.player = player; + } + + @Override + public void run() + { + //if he's not holding the golden shovel anymore, do nothing + if (GriefPrevention.instance.getItemInHand(player, EquipmentSlot.HAND).getType() != GriefPrevention.instance.config_claims_modificationTool) + return; + + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); + //reset any work he might have been doing playerData.lastShovelLocation = null; playerData.claimResizing = null; - - //always reset to basic claims mode - if(playerData.shovelMode != ShovelMode.Basic) - { + + //always reset to basic claims mode + if (playerData.shovelMode != ShovelMode.Basic) + { playerData.shovelMode = ShovelMode.Basic; GriefPrevention.sendMessage(player, TextMode.Info, Messages.ShovelBasicClaimMode); } - - //tell him how many claim blocks he has available + + //tell him how many claim blocks he has available int remainingBlocks = playerData.getRemainingClaimBlocks(); GriefPrevention.sendMessage(player, TextMode.Instr, Messages.RemainingBlocks, String.valueOf(remainingBlocks)); - - //link to a video demo of land claiming, based on world type - if(GriefPrevention.instance.creativeRulesApply(player.getLocation())) - { - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.CreativeBasicsVideo2, DataStore.CREATIVE_VIDEO_URL); - } - else if(GriefPrevention.instance.claimsEnabledForWorld(player.getLocation().getWorld())) - { - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsVideo2, DataStore.SURVIVAL_VIDEO_URL); - } - - //if standing in a claim owned by the player, visualize it - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(player.getLocation(), true, playerData.lastClaim); - if(claim != null && claim.allowEdit(player) == null) - { - playerData.lastClaim = claim; - Visualization.Apply(player, Visualization.FromClaim(claim, player.getEyeLocation().getBlockY(), VisualizationType.Claim, player.getLocation())); - } - } + + //link to a video demo of land claiming, based on world type + if (GriefPrevention.instance.creativeRulesApply(player.getLocation())) + { + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.CreativeBasicsVideo2, DataStore.CREATIVE_VIDEO_URL); + } else if (GriefPrevention.instance.claimsEnabledForWorld(player.getLocation().getWorld())) + { + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsVideo2, DataStore.SURVIVAL_VIDEO_URL); + } + + //if standing in a claim owned by the player, visualize it + Claim claim = GriefPrevention.instance.dataStore.getClaimAt(player.getLocation(), true, playerData.lastClaim); + if (claim != null && claim.allowEdit(player) == null) + { + playerData.lastClaim = claim; + Visualization.Apply(player, Visualization.FromClaim(claim, player.getEyeLocation().getBlockY(), VisualizationType.Claim, player.getLocation())); + } + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/FindUnusedClaimsTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/FindUnusedClaimsTask.java index 66fa539..a7a3179 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/FindUnusedClaimsTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/FindUnusedClaimsTask.java @@ -31,47 +31,49 @@ import java.util.stream.Collectors; //...because the player has been gone a REALLY long time, and that expiration has been configured in config.yml //runs every 1 minute in the main thread -class FindUnusedClaimsTask implements Runnable +class FindUnusedClaimsTask implements Runnable { - private List claimOwnerUUIDs; - private Iterator claimOwnerIterator; - - FindUnusedClaimsTask() - { - refreshUUIDs(); - } - - @Override - public void run() - { - //don't do anything when there are no claims - if(claimOwnerUUIDs.isEmpty()) return; + private List claimOwnerUUIDs; + private Iterator claimOwnerIterator; - //wrap search around to beginning - if(!claimOwnerIterator.hasNext()) - { - refreshUUIDs(); - return; - } - - GriefPrevention.instance.getServer().getScheduler().runTaskAsynchronously(GriefPrevention.instance, new CleanupUnusedClaimPreTask(claimOwnerIterator.next())); - } + FindUnusedClaimsTask() + { + refreshUUIDs(); + } - public void refreshUUIDs() { - // Fetch owner UUIDs from list of claims - claimOwnerUUIDs = GriefPrevention.instance.dataStore.claims.stream().map(claim -> claim.ownerID) - .distinct().filter(Objects::nonNull).collect(Collectors.toList()); + @Override + public void run() + { + //don't do anything when there are no claims + if (claimOwnerUUIDs.isEmpty()) return; - if (!claimOwnerUUIDs.isEmpty()) { - // Randomize order - Collections.shuffle(claimOwnerUUIDs); - } + //wrap search around to beginning + if (!claimOwnerIterator.hasNext()) + { + refreshUUIDs(); + return; + } - GriefPrevention.AddLogEntry("The following UUIDs own a claim and will be checked for inactivity in the following order:", CustomLogEntryTypes.Debug, true); + GriefPrevention.instance.getServer().getScheduler().runTaskAsynchronously(GriefPrevention.instance, new CleanupUnusedClaimPreTask(claimOwnerIterator.next())); + } - for (UUID uuid : claimOwnerUUIDs) - GriefPrevention.AddLogEntry(uuid.toString(), CustomLogEntryTypes.Debug, true); + public void refreshUUIDs() + { + // Fetch owner UUIDs from list of claims + claimOwnerUUIDs = GriefPrevention.instance.dataStore.claims.stream().map(claim -> claim.ownerID) + .distinct().filter(Objects::nonNull).collect(Collectors.toList()); - claimOwnerIterator = claimOwnerUUIDs.iterator(); - } + if (!claimOwnerUUIDs.isEmpty()) + { + // Randomize order + Collections.shuffle(claimOwnerUUIDs); + } + + GriefPrevention.AddLogEntry("The following UUIDs own a claim and will be checked for inactivity in the following order:", CustomLogEntryTypes.Debug, true); + + for (UUID uuid : claimOwnerUUIDs) + GriefPrevention.AddLogEntry(uuid.toString(), CustomLogEntryTypes.Debug, true); + + claimOwnerIterator = claimOwnerUUIDs.iterator(); + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java b/src/main/java/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java index 3084ad9..1241e99 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java @@ -46,197 +46,196 @@ import java.util.regex.Matcher; //manages data stored in the file system public class FlatFileDataStore extends DataStore { - private final static String claimDataFolderPath = dataLayerFolderPath + File.separator + "ClaimData"; - private final static String nextClaimIdFilePath = claimDataFolderPath + File.separator + "_nextClaimID"; - private final static String schemaVersionFilePath = dataLayerFolderPath + File.separator + "_schemaVersion"; - - static boolean hasData() - { - File claimsDataFolder = new File(claimDataFolderPath); - - return claimsDataFolder.exists(); - } - - //initialization! - FlatFileDataStore() throws Exception - { - this.initialize(); - } - - @Override - void initialize() throws Exception - { - //ensure data folders exist - boolean newDataStore = false; - File playerDataFolder = new File(playerDataFolderPath); - File claimDataFolder = new File(claimDataFolderPath); - if(!playerDataFolder.exists() || !claimDataFolder.exists()) - { - newDataStore = true; - playerDataFolder.mkdirs(); - claimDataFolder.mkdirs(); - } - - //if there's no data yet, then anything written will use the schema implemented by this code - if(newDataStore) - { - this.setSchemaVersion(DataStore.latestSchemaVersion); - } - - //load group data into memory - 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 a dollar sign. 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) - { - StringWriter errors = new StringWriter(); - e.printStackTrace(new PrintWriter(errors)); - GriefPrevention.AddLogEntry(errors.toString(), CustomLogEntryTypes.Exception); - } - - try - { - if(inStream != null) inStream.close(); - } - catch(IOException exception) {} - } - - //load next claim number from file - File nextClaimIdFile = new File(nextClaimIdFilePath); - if(nextClaimIdFile.exists()) - { - BufferedReader inStream = null; - try - { - inStream = new BufferedReader(new FileReader(nextClaimIdFile.getAbsolutePath())); - - //read the id - String line = inStream.readLine(); - - //try to parse into a long value - this.nextClaimID = Long.parseLong(line); - } - catch(Exception e){ } - - try - { - if(inStream != null) inStream.close(); - } - catch(IOException exception) {} - } - - //if converting up from schema version 0, rename player data files using UUIDs instead of player names + private final static String claimDataFolderPath = dataLayerFolderPath + File.separator + "ClaimData"; + private final static String nextClaimIdFilePath = claimDataFolderPath + File.separator + "_nextClaimID"; + private final static String schemaVersionFilePath = dataLayerFolderPath + File.separator + "_schemaVersion"; + + static boolean hasData() + { + File claimsDataFolder = new File(claimDataFolderPath); + + return claimsDataFolder.exists(); + } + + //initialization! + FlatFileDataStore() throws Exception + { + this.initialize(); + } + + @Override + void initialize() throws Exception + { + //ensure data folders exist + boolean newDataStore = false; + File playerDataFolder = new File(playerDataFolderPath); + File claimDataFolder = new File(claimDataFolderPath); + if (!playerDataFolder.exists() || !claimDataFolder.exists()) + { + newDataStore = true; + playerDataFolder.mkdirs(); + claimDataFolder.mkdirs(); + } + + //if there's no data yet, then anything written will use the schema implemented by this code + if (newDataStore) + { + this.setSchemaVersion(DataStore.latestSchemaVersion); + } + + //load group data into memory + 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 a dollar sign. 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) + { + StringWriter errors = new StringWriter(); + e.printStackTrace(new PrintWriter(errors)); + GriefPrevention.AddLogEntry(errors.toString(), CustomLogEntryTypes.Exception); + } + + try + { + if (inStream != null) inStream.close(); + } + catch (IOException exception) {} + } + + //load next claim number from file + File nextClaimIdFile = new File(nextClaimIdFilePath); + if (nextClaimIdFile.exists()) + { + BufferedReader inStream = null; + try + { + inStream = new BufferedReader(new FileReader(nextClaimIdFile.getAbsolutePath())); + + //read the id + String line = inStream.readLine(); + + //try to parse into a long value + this.nextClaimID = Long.parseLong(line); + } + catch (Exception e) { } + + try + { + if (inStream != null) inStream.close(); + } + catch (IOException exception) {} + } + + //if converting up from schema version 0, rename player data files using UUIDs instead of player names //get a list of all the files in the claims data folder - if(this.getSchemaVersion() == 0) + if (this.getSchemaVersion() == 0) { files = playerDataFolder.listFiles(); ArrayList namesToConvert = new ArrayList(); - for(File playerFile : files) + for (File playerFile : files) { namesToConvert.add(playerFile.getName()); } - + //resolve and cache as many as possible through various means try { UUIDFetcher fetcher = new UUIDFetcher(namesToConvert); fetcher.call(); } - catch(Exception e) + catch (Exception e) { GriefPrevention.AddLogEntry("Failed to resolve a batch of names to UUIDs. Details:" + e.getMessage()); e.printStackTrace(); } - + //rename files - for(File playerFile : files) + for (File playerFile : files) { String currentFilename = playerFile.getName(); - + //if corrected casing and a record already exists using the correct casing, skip this one String correctedCasing = UUIDFetcher.correctedNames.get(currentFilename); - if(correctedCasing != null && !currentFilename.equals(correctedCasing)) + if (correctedCasing != null && !currentFilename.equals(correctedCasing)) { File correctedCasingFile = new File(playerDataFolder.getPath() + File.separator + correctedCasing); - if(correctedCasingFile.exists()) + if (correctedCasingFile.exists()) { continue; } } - + //try to convert player name to UUID UUID playerID = null; try { playerID = UUIDFetcher.getUUIDOf(currentFilename); - + //if successful, rename the file using the UUID - if(playerID != null) + if (playerID != null) { playerFile.renameTo(new File(playerDataFolder, playerID.toString())); } } - catch(Exception ex){ } + catch (Exception ex) { } } } - - //load claims data into memory - //get a list of all the files in the claims data folder - files = claimDataFolder.listFiles(); - - if(this.getSchemaVersion() <= 1) - { - this.loadClaimData_Legacy(files); - } - else - { - this.loadClaimData(files); - } - - super.initialize(); - } - - void loadClaimData_Legacy(File [] files) throws Exception - { - List validWorlds = Bukkit.getServer().getWorlds(); - - for(int i = 0; i < files.length; i++) - { - if(files[i].isFile()) //avoids folders + + //load claims data into memory + //get a list of all the files in the claims data folder + files = claimDataFolder.listFiles(); + + if (this.getSchemaVersion() <= 1) + { + this.loadClaimData_Legacy(files); + } else + { + this.loadClaimData(files); + } + + super.initialize(); + } + + void loadClaimData_Legacy(File[] files) throws Exception + { + List validWorlds = Bukkit.getServer().getWorlds(); + + for (int i = 0; i < files.length; i++) + { + if (files[i].isFile()) //avoids folders { //skip any file starting with an underscore, to avoid special files not representing land claims - if(files[i].getName().startsWith("_")) continue; - + if (files[i].getName().startsWith("_")) continue; + //the filename is the claim ID. try to parse it long claimID; - + try { claimID = Long.parseLong(files[i].getName()); } - + //because some older versions used a different file name pattern before claim IDs were introduced, //those files need to be "converted" by renaming them to a unique ID - catch(Exception e) + catch (Exception e) { claimID = this.nextClaimID; this.incrementNextClaimID(); @@ -244,137 +243,134 @@ public class FlatFileDataStore extends DataStore files[i].renameTo(newFile); files[i] = newFile; } - + BufferedReader inStream = null; String lesserCornerString = ""; try - { + { Claim topLevelClaim = null; - + inStream = new BufferedReader(new FileReader(files[i].getAbsolutePath())); String line = inStream.readLine(); - - while(line != null) - { + + while (line != null) + { //skip any SUB:### lines from previous versions - if(line.toLowerCase().startsWith("sub:")) + if (line.toLowerCase().startsWith("sub:")) { line = inStream.readLine(); } - + //skip any UUID lines from previous versions Matcher match = uuidpattern.matcher(line.trim()); - if(match.find()) + if (match.find()) { line = inStream.readLine(); } - + //first line is lesser boundary corner location lesserCornerString = line; Location lesserBoundaryCorner = this.locationFromString(lesserCornerString, validWorlds); - + //second line is greater boundary corner location line = inStream.readLine(); Location greaterBoundaryCorner = this.locationFromString(line, validWorlds); - + //third line is owner name - line = inStream.readLine(); + line = inStream.readLine(); String ownerName = line; UUID ownerID = null; - if(ownerName.isEmpty() || ownerName.startsWith("--")) + if (ownerName.isEmpty() || ownerName.startsWith("--")) { ownerID = null; //administrative land claim or subdivision - } - else if(this.getSchemaVersion() == 0) + } else if (this.getSchemaVersion() == 0) { try { ownerID = UUIDFetcher.getUUIDOf(ownerName); } - catch(Exception ex) + catch (Exception ex) { GriefPrevention.AddLogEntry("Couldn't resolve this name to a UUID: " + ownerName + "."); GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString()); } - } - else + } else { try { ownerID = UUID.fromString(ownerName); } - catch(Exception ex) + catch (Exception ex) { GriefPrevention.AddLogEntry("Error - this is not a valid UUID: " + ownerName + "."); GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString()); } } - + //fourth line is list of builders line = inStream.readLine(); List builderNames = Arrays.asList(line.split(";")); builderNames = this.convertNameListToUUIDList(builderNames); - + //fifth line is list of players who can access containers line = inStream.readLine(); List containerNames = Arrays.asList(line.split(";")); containerNames = this.convertNameListToUUIDList(containerNames); - + //sixth line is list of players who can use buttons and switches line = inStream.readLine(); List accessorNames = Arrays.asList(line.split(";")); accessorNames = this.convertNameListToUUIDList(accessorNames); - + //seventh line is list of players who can grant permissions line = inStream.readLine(); - if(line == null) line = ""; + if (line == null) line = ""; List managerNames = Arrays.asList(line.split(";")); managerNames = this.convertNameListToUUIDList(managerNames); - + //skip any remaining extra lines, until the "===" string, indicating the end of this claim or subdivision line = inStream.readLine(); - while(line != null && !line.contains("===")) + while (line != null && !line.contains("===")) line = inStream.readLine(); - + //build a claim instance from those data //if this is the first claim loaded from this file, it's the top level claim - if(topLevelClaim == null) + if (topLevelClaim == null) { //instantiate topLevelClaim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builderNames, containerNames, accessorNames, managerNames, claimID); - + topLevelClaim.modifiedDate = new Date(files[i].lastModified()); this.addClaim(topLevelClaim, false); } - + //otherwise there's already a top level claim, so this must be a subdivision of that top level claim else { Claim subdivision = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, null, builderNames, containerNames, accessorNames, managerNames, null); - + subdivision.modifiedDate = new Date(files[i].lastModified()); subdivision.parent = topLevelClaim; topLevelClaim.children.add(subdivision); subdivision.inDataStore = true; } - + //move up to the first line in the next subdivision line = inStream.readLine(); } - + inStream.close(); } - + //if there's any problem with the file's content, log an error message and skip it - catch(Exception e) + catch (Exception e) { - if(e.getMessage() != null && e.getMessage().contains("World not found")) + if (e.getMessage() != null && e.getMessage().contains("World not found")) { GriefPrevention.AddLogEntry("Failed to load a claim " + files[i].getName() + " because its world isn't loaded (yet?). Please delete the claim file or contact the GriefPrevention developer with information about which plugin(s) you're using to load or create worlds. " + lesserCornerString); inStream.close(); - - } - else + + } else { StringWriter errors = new StringWriter(); e.printStackTrace(new PrintWriter(errors)); @@ -382,44 +378,44 @@ public class FlatFileDataStore extends DataStore GriefPrevention.AddLogEntry(files[i].getName() + " " + errors.toString(), CustomLogEntryTypes.Exception); } } - + try { - if(inStream != null) inStream.close(); + if (inStream != null) inStream.close(); } - catch(IOException exception) {} + catch (IOException exception) {} } } - } - - void loadClaimData(File [] files) throws Exception - { - ConcurrentHashMap orphans = new ConcurrentHashMap(); - for(int i = 0; i < files.length; i++) - { - if(files[i].isFile()) //avoids folders + } + + void loadClaimData(File[] files) throws Exception + { + ConcurrentHashMap orphans = new ConcurrentHashMap(); + for (int i = 0; i < files.length; i++) + { + if (files[i].isFile()) //avoids folders { //skip any file starting with an underscore, to avoid special files not representing land claims - if(files[i].getName().startsWith("_")) continue; - + if (files[i].getName().startsWith("_")) continue; + //delete any which don't end in .yml - if(!files[i].getName().endsWith(".yml")) + if (!files[i].getName().endsWith(".yml")) { files[i].delete(); continue; } - + //the filename is the claim ID. try to parse it long claimID; - + try { claimID = Long.parseLong(files[i].getName().split("\\.")[0]); } - + //because some older versions used a different file name pattern before claim IDs were introduced, //those files need to be "converted" by renaming them to a unique ID - catch(Exception e) + catch (Exception e) { claimID = this.nextClaimID; this.incrementNextClaimID(); @@ -427,29 +423,27 @@ public class FlatFileDataStore extends DataStore files[i].renameTo(newFile); files[i] = newFile; } - + try - { + { ArrayList out_parentID = new ArrayList(); //hacky output parameter Claim claim = this.loadClaim(files[i], out_parentID, claimID); - if(out_parentID.size() == 0 || out_parentID.get(0) == -1) - { + if (out_parentID.size() == 0 || out_parentID.get(0) == -1) + { this.addClaim(claim, false); - } - else + } else { orphans.put(claim, out_parentID.get(0)); } } - + //if there's any problem with the file's content, log an error message and skip it - catch(Exception e) + catch (Exception e) { - if(e.getMessage() != null && e.getMessage().contains("World not found")) + if (e.getMessage() != null && e.getMessage().contains("World not found")) { files[i].delete(); - } - else + } else { StringWriter errors = new StringWriter(); e.printStackTrace(new PrintWriter(errors)); @@ -458,178 +452,178 @@ public class FlatFileDataStore extends DataStore } } } - + //link children to parents - for(Claim child : orphans.keySet()) + for (Claim child : orphans.keySet()) { Claim parent = this.getClaim(orphans.get(child)); - if(parent != null) + if (parent != null) { child.parent = parent; this.addClaim(child, false); } } - } - - Claim loadClaim(File file, ArrayList out_parentID, long claimID) throws IOException, InvalidConfigurationException, Exception - { - List lines = Files.readLines(file, Charset.forName("UTF-8")); + } + + Claim loadClaim(File file, ArrayList out_parentID, long claimID) throws IOException, InvalidConfigurationException, Exception + { + List lines = Files.readLines(file, Charset.forName("UTF-8")); StringBuilder builder = new StringBuilder(); - for(String line : lines) + for (String line : lines) { builder.append(line).append('\n'); } - + return this.loadClaim(builder.toString(), out_parentID, file.lastModified(), claimID, Bukkit.getServer().getWorlds()); - } - - Claim loadClaim(String input, ArrayList out_parentID, long lastModifiedDate, long claimID, List validWorlds) throws InvalidConfigurationException, Exception - { - Claim claim = null; - YamlConfiguration yaml = new YamlConfiguration(); + } + + Claim loadClaim(String input, ArrayList out_parentID, long lastModifiedDate, long claimID, List validWorlds) throws InvalidConfigurationException, Exception + { + Claim claim = null; + YamlConfiguration yaml = new YamlConfiguration(); yaml.loadFromString(input); - + //boundaries Location lesserBoundaryCorner = this.locationFromString(yaml.getString("Lesser Boundary Corner"), validWorlds); Location greaterBoundaryCorner = this.locationFromString(yaml.getString("Greater Boundary Corner"), validWorlds); - + //owner String ownerIdentifier = yaml.getString("Owner"); UUID ownerID = null; - if(!ownerIdentifier.isEmpty()) + if (!ownerIdentifier.isEmpty()) { try { ownerID = UUID.fromString(ownerIdentifier); } - catch(Exception ex) + catch (Exception ex) { GriefPrevention.AddLogEntry("Error - this is not a valid UUID: " + ownerIdentifier + "."); GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString()); } } - + List builders = yaml.getStringList("Builders"); - + List containers = yaml.getStringList("Containers"); - + List accessors = yaml.getStringList("Accessors"); - + List managers = yaml.getStringList("Managers"); - + boolean inheritNothing = yaml.getBoolean("inheritNothing"); out_parentID.add(yaml.getLong("Parent Claim ID", -1L)); - + //instantiate claim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builders, containers, accessors, managers, inheritNothing, claimID); claim.modifiedDate = new Date(lastModifiedDate); claim.id = claimID; - + return claim; - } - - String getYamlForClaim(Claim claim) - { + } + + String getYamlForClaim(Claim claim) + { YamlConfiguration yaml = new YamlConfiguration(); - + //boundaries - yaml.set("Lesser Boundary Corner", this.locationToString(claim.lesserBoundaryCorner)); - yaml.set("Greater Boundary Corner", this.locationToString(claim.greaterBoundaryCorner)); - + yaml.set("Lesser Boundary Corner", this.locationToString(claim.lesserBoundaryCorner)); + yaml.set("Greater Boundary Corner", this.locationToString(claim.greaterBoundaryCorner)); + //owner String ownerID = ""; - if(claim.ownerID != null) ownerID = claim.ownerID.toString(); + if (claim.ownerID != null) ownerID = claim.ownerID.toString(); yaml.set("Owner", ownerID); - + ArrayList builders = new ArrayList(); ArrayList containers = new ArrayList(); ArrayList accessors = new ArrayList(); ArrayList managers = new ArrayList(); claim.getPermissions(builders, containers, accessors, managers); - + yaml.set("Builders", builders); yaml.set("Containers", containers); yaml.set("Accessors", accessors); yaml.set("Managers", managers); - + Long parentID = -1L; - if(claim.parent != null) + if (claim.parent != null) { parentID = claim.parent.id; } - + yaml.set("Parent Claim ID", parentID); - + yaml.set("inheritNothing", claim.getSubclaimRestrictions()); return yaml.saveToString(); - } - - @Override - synchronized void writeClaimToStorage(Claim claim) - { - String claimID = String.valueOf(claim.id); - - String yaml = this.getYamlForClaim(claim); - - try - { - //open the claim's file - File claimFile = new File(claimDataFolderPath + File.separator + claimID + ".yml"); - claimFile.createNewFile(); - Files.write(yaml.getBytes("UTF-8"), claimFile); - } - - //if any problem, log it - catch(Exception e) - { - StringWriter errors = new StringWriter(); + } + + @Override + synchronized void writeClaimToStorage(Claim claim) + { + String claimID = String.valueOf(claim.id); + + String yaml = this.getYamlForClaim(claim); + + try + { + //open the claim's file + File claimFile = new File(claimDataFolderPath + File.separator + claimID + ".yml"); + claimFile.createNewFile(); + Files.write(yaml.getBytes("UTF-8"), claimFile); + } + + //if any problem, log it + catch (Exception e) + { + StringWriter errors = new StringWriter(); e.printStackTrace(new PrintWriter(errors)); GriefPrevention.AddLogEntry(claimID + " " + errors.toString(), CustomLogEntryTypes.Exception); - } - } - - //deletes a claim from the file system - @Override - synchronized void deleteClaimFromSecondaryStorage(Claim claim) - { - String claimID = String.valueOf(claim.id); - - //remove from disk - File claimFile = new File(claimDataFolderPath + File.separator + claimID + ".yml"); - if(claimFile.exists() && !claimFile.delete()) - { - GriefPrevention.AddLogEntry("Error: Unable to delete claim file \"" + claimFile.getAbsolutePath() + "\"."); - } - } - - @Override - synchronized PlayerData getPlayerDataFromStorage(UUID playerID) - { - File playerFile = new File(playerDataFolderPath + File.separator + playerID.toString()); - - PlayerData playerData = new PlayerData(); - playerData.playerID = playerID; - - //if it exists as a file, read the file - if(playerFile.exists()) - { - boolean needRetry = false; - int retriesRemaining = 5; - Exception latestException = null; - do - { - try - { - needRetry = false; - - //read the file content and immediately close it - List lines = Files.readLines(playerFile, Charset.forName("UTF-8")); - Iterator iterator = lines.iterator(); - + } + } - iterator.next(); + //deletes a claim from the file system + @Override + synchronized void deleteClaimFromSecondaryStorage(Claim claim) + { + String claimID = String.valueOf(claim.id); + + //remove from disk + File claimFile = new File(claimDataFolderPath + File.separator + claimID + ".yml"); + if (claimFile.exists() && !claimFile.delete()) + { + GriefPrevention.AddLogEntry("Error: Unable to delete claim file \"" + claimFile.getAbsolutePath() + "\"."); + } + } + + @Override + synchronized PlayerData getPlayerDataFromStorage(UUID playerID) + { + File playerFile = new File(playerDataFolderPath + File.separator + playerID.toString()); + + PlayerData playerData = new PlayerData(); + playerData.playerID = playerID; + + //if it exists as a file, read the file + if (playerFile.exists()) + { + boolean needRetry = false; + int retriesRemaining = 5; + Exception latestException = null; + do + { + try + { + needRetry = false; + + //read the file content and immediately close it + List lines = Files.readLines(playerFile, Charset.forName("UTF-8")); + Iterator iterator = lines.iterator(); + + + iterator.next(); //first line is last login timestamp //RoboMWM - not using this anymore // // //convert that to a date and store it @@ -643,268 +637,267 @@ public class FlatFileDataStore extends DataStore // GriefPrevention.AddLogEntry("Unable to load last login for \"" + playerFile.getName() + "\"."); // playerData.setLastLogin(null); // } - - //second line is accrued claim blocks - String accruedBlocksString = iterator.next(); - - //convert that to a number and store it - playerData.setAccruedClaimBlocks(Integer.parseInt(accruedBlocksString)); - - //third line is any bonus claim blocks granted by administrators - String bonusBlocksString = iterator.next(); - - //convert that to a number and store it - playerData.setBonusClaimBlocks(Integer.parseInt(bonusBlocksString)); - - //fourth line is a double-semicolon-delimited list of claims, which is currently ignored - //String claimsString = inStream.readLine(); - //iterator.next(); - } - - //if there's any problem with the file's content, retry up to 5 times with 5 milliseconds between - catch(Exception e) - { - latestException = e; - needRetry = true; - retriesRemaining--; - } - - try - { - if(needRetry) Thread.sleep(5); - } - catch(InterruptedException exception) {} - - }while(needRetry && retriesRemaining >= 0); - - //if last attempt failed, log information about the problem - if(needRetry) - { - StringWriter errors = new StringWriter(); - latestException.printStackTrace(new PrintWriter(errors)); - GriefPrevention.AddLogEntry("Failed to load PlayerData for " + playerID + ". This usually occurs when your server runs out of storage space, causing any file saves to corrupt. Fix or delete the file in GriefPrevetionData/PlayerData/" + playerID, CustomLogEntryTypes.Debug, false); - GriefPrevention.AddLogEntry(playerID + " " + errors.toString(), CustomLogEntryTypes.Exception); - } - } - - return playerData; - } - - //saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them - @Override - public void overrideSavePlayerData(UUID playerID, PlayerData playerData) - { - //never save data for the "administrative" account. null for claim owner ID indicates administrative account - if(playerID == null) return; - - StringBuilder fileContent = new StringBuilder(); - try - { - //first line is last login timestamp //RoboMWM - no longer storing/using - //if(playerData.getLastLogin() == null) playerData.setLastLogin(new Date()); - //DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss"); - //fileContent.append(dateFormat.format(playerData.getLastLogin())); - fileContent.append("\n"); - - //second line is accrued claim blocks - fileContent.append(String.valueOf(playerData.getAccruedClaimBlocks())); - fileContent.append("\n"); - - //third line is bonus claim blocks - fileContent.append(String.valueOf(playerData.getBonusClaimBlocks())); - fileContent.append("\n"); - - //fourth line is blank - fileContent.append("\n"); - - //write data to file + + //second line is accrued claim blocks + String accruedBlocksString = iterator.next(); + + //convert that to a number and store it + playerData.setAccruedClaimBlocks(Integer.parseInt(accruedBlocksString)); + + //third line is any bonus claim blocks granted by administrators + String bonusBlocksString = iterator.next(); + + //convert that to a number and store it + playerData.setBonusClaimBlocks(Integer.parseInt(bonusBlocksString)); + + //fourth line is a double-semicolon-delimited list of claims, which is currently ignored + //String claimsString = inStream.readLine(); + //iterator.next(); + } + + //if there's any problem with the file's content, retry up to 5 times with 5 milliseconds between + catch (Exception e) + { + latestException = e; + needRetry = true; + retriesRemaining--; + } + + try + { + if (needRetry) Thread.sleep(5); + } + catch (InterruptedException exception) {} + + } while (needRetry && retriesRemaining >= 0); + + //if last attempt failed, log information about the problem + if (needRetry) + { + StringWriter errors = new StringWriter(); + latestException.printStackTrace(new PrintWriter(errors)); + GriefPrevention.AddLogEntry("Failed to load PlayerData for " + playerID + ". This usually occurs when your server runs out of storage space, causing any file saves to corrupt. Fix or delete the file in GriefPrevetionData/PlayerData/" + playerID, CustomLogEntryTypes.Debug, false); + GriefPrevention.AddLogEntry(playerID + " " + errors.toString(), CustomLogEntryTypes.Exception); + } + } + + return playerData; + } + + //saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them + @Override + public void overrideSavePlayerData(UUID playerID, PlayerData playerData) + { + //never save data for the "administrative" account. null for claim owner ID indicates administrative account + if (playerID == null) return; + + StringBuilder fileContent = new StringBuilder(); + try + { + //first line is last login timestamp //RoboMWM - no longer storing/using + //if(playerData.getLastLogin() == null) playerData.setLastLogin(new Date()); + //DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss"); + //fileContent.append(dateFormat.format(playerData.getLastLogin())); + fileContent.append("\n"); + + //second line is accrued claim blocks + fileContent.append(String.valueOf(playerData.getAccruedClaimBlocks())); + fileContent.append("\n"); + + //third line is bonus claim blocks + fileContent.append(String.valueOf(playerData.getBonusClaimBlocks())); + fileContent.append("\n"); + + //fourth line is blank + fileContent.append("\n"); + + //write data to file File playerDataFile = new File(playerDataFolderPath + File.separator + playerID.toString()); Files.write(fileContent.toString().getBytes("UTF-8"), playerDataFile); - } - - //if any problem, log it - catch(Exception e) - { - GriefPrevention.AddLogEntry("GriefPrevention: Unexpected exception saving data for player \"" + playerID.toString() + "\": " + e.getMessage()); - e.printStackTrace(); - } - } - - @Override - synchronized void incrementNextClaimID() - { - //increment in memory - this.nextClaimID++; - - BufferedWriter outStream = null; - - try - { - //open the file and write the new value - File nextClaimIdFile = new File(nextClaimIdFilePath); - nextClaimIdFile.createNewFile(); - outStream = new BufferedWriter(new FileWriter(nextClaimIdFile)); - - outStream.write(String.valueOf(this.nextClaimID)); - } - - //if any problem, log it - catch(Exception e) - { - GriefPrevention.AddLogEntry("Unexpected exception saving next claim ID: " + e.getMessage()); - e.printStackTrace(); - } - - //close the file - try - { - if(outStream != null) outStream.close(); - } - catch(IOException exception) {} - } - - //grants a group (players with a specific permission) bonus claim blocks as long as they're still members of the group - @Override - synchronized void saveGroupBonusBlocks(String groupName, int 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(String.valueOf(currentValue)); - 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){} - } - - synchronized void migrateData(DatabaseDataStore databaseStore) - { - //migrate claims - for(int i = 0; i < this.claims.size(); i++) - { - Claim claim = this.claims.get(i); - databaseStore.addClaim(claim, true); - for(Claim child : claim.children) - { - databaseStore.addClaim(child, true); - } - } - - //migrate groups - Iterator groupNamesEnumerator = this.permissionToBonusBlocksMap.keySet().iterator(); - while(groupNamesEnumerator.hasNext()) - { - String groupName = groupNamesEnumerator.next(); - databaseStore.saveGroupBonusBlocks(groupName, this.permissionToBonusBlocksMap.get(groupName)); - } - - //migrate players - 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 - if(file.isHidden()) continue; //avoid hidden files, which are likely not created by GriefPrevention - - //all group data files start with a dollar sign. ignoring those, already handled above - if(file.getName().startsWith("$")) continue; - - //ignore special files - if(file.getName().startsWith("_")) continue; - if(file.getName().endsWith(".ignore")) continue; - - UUID playerID = UUID.fromString(file.getName()); - databaseStore.savePlayerData(playerID, this.getPlayerData(playerID)); - this.clearCachedPlayerData(playerID); - } - - //migrate next claim ID - if(this.nextClaimID > databaseStore.nextClaimID) - { - databaseStore.setNextClaimID(this.nextClaimID); - } - - //rename player and claim data folders so the migration won't run again - int i = 0; - File claimsBackupFolder; - File playersBackupFolder; - do - { - String claimsFolderBackupPath = claimDataFolderPath; - if(i > 0) claimsFolderBackupPath += String.valueOf(i); - claimsBackupFolder = new File(claimsFolderBackupPath); - - String playersFolderBackupPath = playerDataFolderPath; - if(i > 0) playersFolderBackupPath += String.valueOf(i); - playersBackupFolder = new File(playersFolderBackupPath); - i++; - } while(claimsBackupFolder.exists() || playersBackupFolder.exists()); - - File claimsFolder = new File(claimDataFolderPath); - File playersFolder = new File(playerDataFolderPath); - - claimsFolder.renameTo(claimsBackupFolder); - playersFolder.renameTo(playersBackupFolder); - - GriefPrevention.AddLogEntry("Backed your file system data up to " + claimsBackupFolder.getName() + " and " + playersBackupFolder.getName() + "."); - GriefPrevention.AddLogEntry("If your migration encountered any problems, you can restore those data with a quick copy/paste."); - GriefPrevention.AddLogEntry("When you're satisfied that all your data have been safely migrated, consider deleting those folders."); - } + } - @Override - synchronized void close() { } + //if any problem, log it + catch (Exception e) + { + GriefPrevention.AddLogEntry("GriefPrevention: Unexpected exception saving data for player \"" + playerID.toString() + "\": " + e.getMessage()); + e.printStackTrace(); + } + } + + @Override + synchronized void incrementNextClaimID() + { + //increment in memory + this.nextClaimID++; + + BufferedWriter outStream = null; + + try + { + //open the file and write the new value + File nextClaimIdFile = new File(nextClaimIdFilePath); + nextClaimIdFile.createNewFile(); + outStream = new BufferedWriter(new FileWriter(nextClaimIdFile)); + + outStream.write(String.valueOf(this.nextClaimID)); + } + + //if any problem, log it + catch (Exception e) + { + GriefPrevention.AddLogEntry("Unexpected exception saving next claim ID: " + e.getMessage()); + e.printStackTrace(); + } + + //close the file + try + { + if (outStream != null) outStream.close(); + } + catch (IOException exception) {} + } + + //grants a group (players with a specific permission) bonus claim blocks as long as they're still members of the group + @Override + synchronized void saveGroupBonusBlocks(String groupName, int 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(String.valueOf(currentValue)); + 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) {} + } + + synchronized void migrateData(DatabaseDataStore databaseStore) + { + //migrate claims + for (int i = 0; i < this.claims.size(); i++) + { + Claim claim = this.claims.get(i); + databaseStore.addClaim(claim, true); + for (Claim child : claim.children) + { + databaseStore.addClaim(child, true); + } + } + + //migrate groups + Iterator groupNamesEnumerator = this.permissionToBonusBlocksMap.keySet().iterator(); + while (groupNamesEnumerator.hasNext()) + { + String groupName = groupNamesEnumerator.next(); + databaseStore.saveGroupBonusBlocks(groupName, this.permissionToBonusBlocksMap.get(groupName)); + } + + //migrate players + 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 + if (file.isHidden()) continue; //avoid hidden files, which are likely not created by GriefPrevention + + //all group data files start with a dollar sign. ignoring those, already handled above + if (file.getName().startsWith("$")) continue; + + //ignore special files + if (file.getName().startsWith("_")) continue; + if (file.getName().endsWith(".ignore")) continue; + + UUID playerID = UUID.fromString(file.getName()); + databaseStore.savePlayerData(playerID, this.getPlayerData(playerID)); + this.clearCachedPlayerData(playerID); + } + + //migrate next claim ID + if (this.nextClaimID > databaseStore.nextClaimID) + { + databaseStore.setNextClaimID(this.nextClaimID); + } + + //rename player and claim data folders so the migration won't run again + int i = 0; + File claimsBackupFolder; + File playersBackupFolder; + do + { + String claimsFolderBackupPath = claimDataFolderPath; + if (i > 0) claimsFolderBackupPath += String.valueOf(i); + claimsBackupFolder = new File(claimsFolderBackupPath); + + String playersFolderBackupPath = playerDataFolderPath; + if (i > 0) playersFolderBackupPath += String.valueOf(i); + playersBackupFolder = new File(playersFolderBackupPath); + i++; + } while (claimsBackupFolder.exists() || playersBackupFolder.exists()); + + File claimsFolder = new File(claimDataFolderPath); + File playersFolder = new File(playerDataFolderPath); + + claimsFolder.renameTo(claimsBackupFolder); + playersFolder.renameTo(playersBackupFolder); + + GriefPrevention.AddLogEntry("Backed your file system data up to " + claimsBackupFolder.getName() + " and " + playersBackupFolder.getName() + "."); + GriefPrevention.AddLogEntry("If your migration encountered any problems, you can restore those data with a quick copy/paste."); + GriefPrevention.AddLogEntry("When you're satisfied that all your data have been safely migrated, consider deleting those folders."); + } + + @Override + synchronized void close() { } @Override int getSchemaVersionFromStorage() { File schemaVersionFile = new File(schemaVersionFilePath); - if(schemaVersionFile.exists()) + if (schemaVersionFile.exists()) { BufferedReader inStream = null; int schemaVersion = 0; try { inStream = new BufferedReader(new FileReader(schemaVersionFile.getAbsolutePath())); - + //read the version number String line = inStream.readLine(); - + //try to parse into an int value schemaVersion = Integer.parseInt(line); } - catch(Exception e){ } - + catch (Exception e) { } + try { - if(inStream != null) inStream.close(); + if (inStream != null) inStream.close(); } - catch(IOException exception) {} - + catch (IOException exception) {} + return schemaVersion; - } - else + } else { this.updateSchemaVersionInStorage(0); return 0; @@ -915,29 +908,29 @@ public class FlatFileDataStore extends DataStore void updateSchemaVersionInStorage(int versionToSet) { BufferedWriter outStream = null; - + try { //open the file and write the new value File schemaVersionFile = new File(schemaVersionFilePath); schemaVersionFile.createNewFile(); outStream = new BufferedWriter(new FileWriter(schemaVersionFile)); - + outStream.write(String.valueOf(versionToSet)); - } - + } + //if any problem, log it - catch(Exception e) + catch (Exception e) { GriefPrevention.AddLogEntry("Unexpected exception saving schema version: " + e.getMessage()); } - + //close the file try { - if(outStream != null) outStream.close(); + if (outStream != null) outStream.close(); } - catch(IOException exception) {} - + catch (IOException exception) {} + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/GriefPrevention.java b/src/main/java/me/ryanhamshire/GriefPrevention/GriefPrevention.java index 1c51fdd..0dc8c19 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/GriefPrevention.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/GriefPrevention.java @@ -18,6 +18,42 @@ package me.ryanhamshire.GriefPrevention; +import me.ryanhamshire.GriefPrevention.DataStore.NoTransferException; +import me.ryanhamshire.GriefPrevention.events.PreventBlockBreakEvent; +import me.ryanhamshire.GriefPrevention.events.SaveTrappedPlayerEvent; +import me.ryanhamshire.GriefPrevention.events.TrustChangedEvent; +import me.ryanhamshire.GriefPrevention.metrics.MetricsHandler; +import net.milkbowl.vault.economy.Economy; +import org.bukkit.BanList; +import org.bukkit.BanList.Type; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Chunk; +import org.bukkit.ChunkSnapshot; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.Statistic; +import org.bukkit.World; +import org.bukkit.World.Environment; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.util.BlockIterator; + import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -32,524 +68,481 @@ import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; -import me.ryanhamshire.GriefPrevention.DataStore.NoTransferException; -import me.ryanhamshire.GriefPrevention.events.PreventBlockBreakEvent; -import me.ryanhamshire.GriefPrevention.events.SaveTrappedPlayerEvent; -import me.ryanhamshire.GriefPrevention.events.TrustChangedEvent; -import me.ryanhamshire.GriefPrevention.metrics.MetricsHandler; -import net.milkbowl.vault.economy.Economy; - -import org.bukkit.BanList; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Chunk; -import org.bukkit.ChunkSnapshot; -import org.bukkit.GameMode; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.Statistic; -import org.bukkit.World; -import org.bukkit.BanList.Type; -import org.bukkit.World.Environment; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.command.*; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.plugin.PluginManager; -import org.bukkit.plugin.RegisteredServiceProvider; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitTask; -import org.bukkit.util.BlockIterator; - public class GriefPrevention extends JavaPlugin { - //for convenience, a reference to the instance of this plugin - public static GriefPrevention instance; - - //for logging to the console and log file - private static Logger log; - - //this handles data storage, like player and region data - public DataStore dataStore; - - //this tracks item stacks expected to drop which will need protection + //for convenience, a reference to the instance of this plugin + public static GriefPrevention instance; + + //for logging to the console and log file + private static Logger log; + + //this handles data storage, like player and region data + public DataStore dataStore; + + //this tracks item stacks expected to drop which will need protection ArrayList pendingItemWatchList = new ArrayList(); - + //log entry manager for GP's custom log files CustomLogger customLogger; - - //configuration variables, loaded/saved from a config.yml - - //claim mode for each world - public ConcurrentHashMap config_claims_worldModes; - private boolean config_creativeWorldsExist; //note on whether there are any creative mode worlds, to save cpu cycles on a common hash lookup - - public boolean config_claims_preventGlobalMonsterEggs; //whether monster eggs can be placed regardless of trust. - 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_protectHorses; //whether horses on a claim should be protected by that claim's rules - public boolean config_claims_protectDonkeys; //whether donkeys on a claim should be protected by that claim's rules - public boolean config_claims_protectLlamas; //whether llamas on a claim should be protected by that claim's rules - public boolean config_claims_preventButtonsSwitches; //whether buttons and switches are protectable - public boolean config_claims_lockWoodenDoors; //whether wooden doors should be locked by default (require /accesstrust) - public boolean config_claims_lockTrapDoors; //whether trap doors should be locked by default (require /accesstrust) - public boolean config_claims_lockFenceGates; //whether fence gates should be locked by default (require /accesstrust) - public boolean config_claims_enderPearlsRequireAccessTrust; //whether teleporting into a claim with a pearl requires access trust - public int config_claims_maxClaimsPerPlayer; //maximum number of claims per player - public boolean config_claims_respectWorldGuard; //whether claim creations requires WG build permission in creation area - public boolean config_claims_villagerTradingRequiresTrust; //whether trading with a claimed villager requires permission - - public int config_claims_initialBlocks; //the number of claim blocks a new player starts with - public double config_claims_abandonReturnRatio; //the portion of claim blocks returned to a player when a claim is abandoned - public int config_claims_blocksAccruedPerHour_default; //how many additional blocks players get each hour of play (can be zero) without any special permissions - public int config_claims_maxAccruedBlocks_default; //the limit on accrued blocks (over time) for players without any special permissions. doesn't limit purchased or admin-gifted blocks - public int config_claims_accruedIdleThreshold; //how far (in blocks) a player must move in order to not be considered afk/idle when determining accrued claim blocks - public int config_claims_accruedIdlePercent; //how much percentage of claim block accruals should idle players get - public int config_claims_maxDepth; //limit on how deep claims can go - public int config_claims_expirationDays; //how many days of inactivity before a player loses his claims - public int config_claims_expirationExemptionTotalBlocks; //total claim blocks amount which will exempt a player from claim expiration - public int config_claims_expirationExemptionBonusBlocks; //bonus claim blocks amount which will exempt a player from claim expiration - - public int config_claims_automaticClaimsForNewPlayersRadius; //how big automatic new player claims (when they place a chest) should be. 0 to disable - public int config_claims_claimsExtendIntoGroundDistance; //how far below the shoveled block a new claim will reach - public int config_claims_minWidth; //minimum width for non-admin claims - public int config_claims_minArea; //minimum area for non-admin claims - - public int config_claims_chestClaimExpirationDays; //number of days of inactivity before an automatic chest claim will be deleted - public int config_claims_unusedClaimExpirationDays; //number of days of inactivity before an unused (nothing build) claim will be deleted - public boolean config_claims_survivalAutoNatureRestoration; //whether survival claims will be automatically restored to nature when auto-deleted - public boolean config_claims_allowTrappedInAdminClaims; //whether it should be allowed to use /trapped in adminclaims. - - public Material config_claims_investigationTool; //which material will be used to investigate claims with a right click - public Material config_claims_modificationTool; //which material will be used to create/resize claims with a right click - - public ArrayList config_claims_commandsRequiringAccessTrust; //the list of slash commands requiring access trust when in a claim - public boolean config_claims_supplyPlayerManual; //whether to give new players a book with land claim help in it - public int config_claims_manualDeliveryDelaySeconds; //how long to wait before giving a book to a new player - public boolean config_claims_firespreads; //whether fire will spread in claims - public boolean config_claims_firedamages; //whether fire will damage in claims + //configuration variables, loaded/saved from a config.yml - public boolean config_claims_lecternReadingRequiresAccessTrust; //reading lecterns requires access trust - - public ArrayList config_siege_enabledWorlds; //whether or not /siege is enabled on this server - public ArrayList config_siege_blocks; //which blocks will be breakable in siege mode - public int config_siege_doorsOpenSeconds; // how before claim is re-secured after siege win - public int config_siege_cooldownEndInMinutes; - public boolean config_spam_enabled; //whether or not to monitor for spam - public int config_spam_loginCooldownSeconds; //how long players must wait between logins. combats login spam. - public int config_spam_loginLogoutNotificationsPerMinute; //how many login/logout notifications to show per minute (global, not per player) - public ArrayList config_spam_monitorSlashCommands; //the list of slash commands monitored for spam - public boolean config_spam_banOffenders; //whether or not to ban spammers automatically - public String config_spam_banMessage; //message to show an automatically banned player - public String config_spam_warningMessage; //message to show a player who is close to spam level - public String config_spam_allowedIpAddresses; //IP addresses which will not be censored - public int config_spam_deathMessageCooldownSeconds; //cooldown period for death messages (per player) in seconds - public int config_spam_logoutMessageDelaySeconds; //delay before a logout message will be shown (only if the player stays offline that long) - - HashMap config_pvp_specifiedWorlds; //list of worlds where pvp anti-grief rules apply, according to the config file - public boolean config_pvp_protectFreshSpawns; //whether to make newly spawned players immune until they pick up an item - public boolean config_pvp_punishLogout; //whether to kill players who log out during PvP combat - public int config_pvp_combatTimeoutSeconds; //how long combat is considered to continue after the most recent damage - public boolean config_pvp_allowCombatItemDrop; //whether a player can drop items during combat to hide them - public ArrayList config_pvp_blockedCommands; //list of commands which may not be used during pvp combat - public boolean config_pvp_noCombatInPlayerLandClaims; //whether players may fight in player-owned land claims - public boolean config_pvp_noCombatInAdminLandClaims; //whether players may fight in admin-owned land claims - public boolean config_pvp_noCombatInAdminSubdivisions; //whether players may fight in subdivisions of admin-owned land claims - public boolean config_pvp_allowLavaNearPlayers; //whether players may dump lava near other players in pvp worlds - public boolean config_pvp_allowLavaNearPlayers_NonPvp; //whather this applies in non-PVP rules worlds - public boolean config_pvp_allowFireNearPlayers; //whether players may start flint/steel fires near other players in pvp worlds - public boolean config_pvp_allowFireNearPlayers_NonPvp; //whether this applies in non-PVP rules worlds - public boolean config_pvp_protectPets; //whether players may damage pets outside of land claims in pvp worlds - - public boolean config_lockDeathDropsInPvpWorlds; //whether players' dropped on death items are protected in pvp worlds - public boolean config_lockDeathDropsInNonPvpWorlds; //whether players' dropped on death items are protected in non-pvp worlds + //claim mode for each world + public ConcurrentHashMap config_claims_worldModes; + private boolean config_creativeWorldsExist; //note on whether there are any creative mode worlds, to save cpu cycles on a common hash lookup - public int config_economy_claimBlocksMaxBonus; //max "bonus" blocks a player can buy. set to zero for no limit. - public double config_economy_claimBlocksPurchaseCost; //cost to purchase a claim block. set to zero to disable purchase. - public double config_economy_claimBlocksSellValue; //return on a sold claim block. set to zero to disable sale. - - public boolean config_blockClaimExplosions; //whether explosions may destroy claimed blocks - public boolean config_blockSurfaceCreeperExplosions; //whether creeper explosions near or above the surface destroy blocks - public boolean config_blockSurfaceOtherExplosions; //whether non-creeper explosions near or above the surface destroy blocks - 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 - - public boolean config_whisperNotifications; //whether whispered messages will broadcast to administrators in game - public boolean config_signNotifications; //whether sign content will broadcast to administrators in game - public ArrayList config_eavesdrop_whisperCommands; //list of whisper commands to eavesdrop on - - public boolean config_smartBan; //whether to ban accounts which very likely owned by a banned player - - public boolean config_endermenMoveBlocks; //whether or not endermen may move blocks around - public boolean config_claims_ravagersBreakBlocks; //whether or not ravagers may break blocks in claims - public boolean config_silverfishBreakBlocks; //whether silverfish may break blocks - public boolean config_creaturesTrampleCrops; //whether or not non-player entities may trample crops - public boolean config_rabbitsEatCrops; //whether or not rabbits may eat crops - public boolean config_zombiesBreakDoors; //whether or not hard-mode zombies may break down wooden doors - - public int config_ipLimit; //how many players can share an IP address - - public boolean config_trollFilterEnabled; //whether to auto-mute new players who use banned words right after joining + public boolean config_claims_preventGlobalMonsterEggs; //whether monster eggs can be placed regardless of trust. + 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_protectHorses; //whether horses on a claim should be protected by that claim's rules + public boolean config_claims_protectDonkeys; //whether donkeys on a claim should be protected by that claim's rules + public boolean config_claims_protectLlamas; //whether llamas on a claim should be protected by that claim's rules + public boolean config_claims_preventButtonsSwitches; //whether buttons and switches are protectable + public boolean config_claims_lockWoodenDoors; //whether wooden doors should be locked by default (require /accesstrust) + public boolean config_claims_lockTrapDoors; //whether trap doors should be locked by default (require /accesstrust) + public boolean config_claims_lockFenceGates; //whether fence gates should be locked by default (require /accesstrust) + public boolean config_claims_enderPearlsRequireAccessTrust; //whether teleporting into a claim with a pearl requires access trust + public int config_claims_maxClaimsPerPlayer; //maximum number of claims per player + public boolean config_claims_respectWorldGuard; //whether claim creations requires WG build permission in creation area + public boolean config_claims_villagerTradingRequiresTrust; //whether trading with a claimed villager requires permission - public HashMap config_seaLevelOverride; //override for sea level, because bukkit doesn't report the right value for all situations - - public boolean config_limitTreeGrowth; //whether trees should be prevented from growing into a claim from outside - public boolean config_checkPistonMovement; //whether to check piston movement - public boolean config_pistonsInClaimsOnly; //whether pistons are limited to only move blocks located within the piston's land claim + public int config_claims_initialBlocks; //the number of claim blocks a new player starts with + public double config_claims_abandonReturnRatio; //the portion of claim blocks returned to a player when a claim is abandoned + public int config_claims_blocksAccruedPerHour_default; //how many additional blocks players get each hour of play (can be zero) without any special permissions + public int config_claims_maxAccruedBlocks_default; //the limit on accrued blocks (over time) for players without any special permissions. doesn't limit purchased or admin-gifted blocks + public int config_claims_accruedIdleThreshold; //how far (in blocks) a player must move in order to not be considered afk/idle when determining accrued claim blocks + public int config_claims_accruedIdlePercent; //how much percentage of claim block accruals should idle players get + public int config_claims_maxDepth; //limit on how deep claims can go + public int config_claims_expirationDays; //how many days of inactivity before a player loses his claims + public int config_claims_expirationExemptionTotalBlocks; //total claim blocks amount which will exempt a player from claim expiration + public int config_claims_expirationExemptionBonusBlocks; //bonus claim blocks amount which will exempt a player from claim expiration - public boolean config_advanced_fixNegativeClaimblockAmounts; //whether to attempt to fix negative claim block amounts (some addons cause/assume players can go into negative amounts) - public int config_advanced_claim_expiration_check_rate; //How often GP should check for expired claims, amount in seconds - public int config_advanced_offlineplayer_cache_days; //Cache players who have logged in within the last x number of days - - //custom log settings - public int config_logs_daysToKeep; + public int config_claims_automaticClaimsForNewPlayersRadius; //how big automatic new player claims (when they place a chest) should be. 0 to disable + public int config_claims_claimsExtendIntoGroundDistance; //how far below the shoveled block a new claim will reach + public int config_claims_minWidth; //minimum width for non-admin claims + public int config_claims_minArea; //minimum area for non-admin claims + + public int config_claims_chestClaimExpirationDays; //number of days of inactivity before an automatic chest claim will be deleted + public int config_claims_unusedClaimExpirationDays; //number of days of inactivity before an unused (nothing build) claim will be deleted + public boolean config_claims_survivalAutoNatureRestoration; //whether survival claims will be automatically restored to nature when auto-deleted + public boolean config_claims_allowTrappedInAdminClaims; //whether it should be allowed to use /trapped in adminclaims. + + public Material config_claims_investigationTool; //which material will be used to investigate claims with a right click + public Material config_claims_modificationTool; //which material will be used to create/resize claims with a right click + + public ArrayList config_claims_commandsRequiringAccessTrust; //the list of slash commands requiring access trust when in a claim + public boolean config_claims_supplyPlayerManual; //whether to give new players a book with land claim help in it + public int config_claims_manualDeliveryDelaySeconds; //how long to wait before giving a book to a new player + + public boolean config_claims_firespreads; //whether fire will spread in claims + public boolean config_claims_firedamages; //whether fire will damage in claims + + public boolean config_claims_lecternReadingRequiresAccessTrust; //reading lecterns requires access trust + + public ArrayList config_siege_enabledWorlds; //whether or not /siege is enabled on this server + public ArrayList config_siege_blocks; //which blocks will be breakable in siege mode + public int config_siege_doorsOpenSeconds; // how before claim is re-secured after siege win + public int config_siege_cooldownEndInMinutes; + public boolean config_spam_enabled; //whether or not to monitor for spam + public int config_spam_loginCooldownSeconds; //how long players must wait between logins. combats login spam. + public int config_spam_loginLogoutNotificationsPerMinute; //how many login/logout notifications to show per minute (global, not per player) + public ArrayList config_spam_monitorSlashCommands; //the list of slash commands monitored for spam + public boolean config_spam_banOffenders; //whether or not to ban spammers automatically + public String config_spam_banMessage; //message to show an automatically banned player + public String config_spam_warningMessage; //message to show a player who is close to spam level + public String config_spam_allowedIpAddresses; //IP addresses which will not be censored + public int config_spam_deathMessageCooldownSeconds; //cooldown period for death messages (per player) in seconds + public int config_spam_logoutMessageDelaySeconds; //delay before a logout message will be shown (only if the player stays offline that long) + + HashMap config_pvp_specifiedWorlds; //list of worlds where pvp anti-grief rules apply, according to the config file + public boolean config_pvp_protectFreshSpawns; //whether to make newly spawned players immune until they pick up an item + public boolean config_pvp_punishLogout; //whether to kill players who log out during PvP combat + public int config_pvp_combatTimeoutSeconds; //how long combat is considered to continue after the most recent damage + public boolean config_pvp_allowCombatItemDrop; //whether a player can drop items during combat to hide them + public ArrayList config_pvp_blockedCommands; //list of commands which may not be used during pvp combat + public boolean config_pvp_noCombatInPlayerLandClaims; //whether players may fight in player-owned land claims + public boolean config_pvp_noCombatInAdminLandClaims; //whether players may fight in admin-owned land claims + public boolean config_pvp_noCombatInAdminSubdivisions; //whether players may fight in subdivisions of admin-owned land claims + public boolean config_pvp_allowLavaNearPlayers; //whether players may dump lava near other players in pvp worlds + public boolean config_pvp_allowLavaNearPlayers_NonPvp; //whather this applies in non-PVP rules worlds + public boolean config_pvp_allowFireNearPlayers; //whether players may start flint/steel fires near other players in pvp worlds + public boolean config_pvp_allowFireNearPlayers_NonPvp; //whether this applies in non-PVP rules worlds + public boolean config_pvp_protectPets; //whether players may damage pets outside of land claims in pvp worlds + + public boolean config_lockDeathDropsInPvpWorlds; //whether players' dropped on death items are protected in pvp worlds + public boolean config_lockDeathDropsInNonPvpWorlds; //whether players' dropped on death items are protected in non-pvp worlds + + public int config_economy_claimBlocksMaxBonus; //max "bonus" blocks a player can buy. set to zero for no limit. + public double config_economy_claimBlocksPurchaseCost; //cost to purchase a claim block. set to zero to disable purchase. + public double config_economy_claimBlocksSellValue; //return on a sold claim block. set to zero to disable sale. + + public boolean config_blockClaimExplosions; //whether explosions may destroy claimed blocks + public boolean config_blockSurfaceCreeperExplosions; //whether creeper explosions near or above the surface destroy blocks + public boolean config_blockSurfaceOtherExplosions; //whether non-creeper explosions near or above the surface destroy blocks + 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 + + public boolean config_whisperNotifications; //whether whispered messages will broadcast to administrators in game + public boolean config_signNotifications; //whether sign content will broadcast to administrators in game + public ArrayList config_eavesdrop_whisperCommands; //list of whisper commands to eavesdrop on + + public boolean config_smartBan; //whether to ban accounts which very likely owned by a banned player + + public boolean config_endermenMoveBlocks; //whether or not endermen may move blocks around + public boolean config_claims_ravagersBreakBlocks; //whether or not ravagers may break blocks in claims + public boolean config_silverfishBreakBlocks; //whether silverfish may break blocks + public boolean config_creaturesTrampleCrops; //whether or not non-player entities may trample crops + public boolean config_rabbitsEatCrops; //whether or not rabbits may eat crops + public boolean config_zombiesBreakDoors; //whether or not hard-mode zombies may break down wooden doors + + public int config_ipLimit; //how many players can share an IP address + + public boolean config_trollFilterEnabled; //whether to auto-mute new players who use banned words right after joining + + public HashMap config_seaLevelOverride; //override for sea level, because bukkit doesn't report the right value for all situations + + public boolean config_limitTreeGrowth; //whether trees should be prevented from growing into a claim from outside + public boolean config_checkPistonMovement; //whether to check piston movement + public boolean config_pistonsInClaimsOnly; //whether pistons are limited to only move blocks located within the piston's land claim + + public boolean config_advanced_fixNegativeClaimblockAmounts; //whether to attempt to fix negative claim block amounts (some addons cause/assume players can go into negative amounts) + public int config_advanced_claim_expiration_check_rate; //How often GP should check for expired claims, amount in seconds + public int config_advanced_offlineplayer_cache_days; //Cache players who have logged in within the last x number of days + + //custom log settings + public int config_logs_daysToKeep; public boolean config_logs_socialEnabled; public boolean config_logs_suspiciousEnabled; public boolean config_logs_adminEnabled; public boolean config_logs_debugEnabled; public boolean config_logs_mutedChatEnabled; - + //ban management plugin interop settings public boolean config_ban_useCommand; public String config_ban_commandFormat; - - private String databaseUrl; - private String databaseUserName; - private String databasePassword; - - //reference to the economy plugin, if economy integration is enabled - public static Economy economy = null; - - //how far away to search from a tree trunk for its branch blocks - public static final int TREE_RADIUS = 5; - - //how long to wait before deciding a player is staying online or staying offline, for notication messages - public static final int NOTIFICATION_SECONDS = 20; - - //adds a server log entry - public static synchronized void AddLogEntry(String entry, CustomLogEntryTypes customLogType, boolean excludeFromServerLogs) - { - if(customLogType != null && GriefPrevention.instance.customLogger != null) - { - GriefPrevention.instance.customLogger.AddEntry(entry, customLogType); - } - if(!excludeFromServerLogs) log.info(entry); - } - - public static synchronized void AddLogEntry(String entry, CustomLogEntryTypes customLogType) + private String databaseUrl; + private String databaseUserName; + private String databasePassword; + + + //reference to the economy plugin, if economy integration is enabled + public static Economy economy = null; + + //how far away to search from a tree trunk for its branch blocks + public static final int TREE_RADIUS = 5; + + //how long to wait before deciding a player is staying online or staying offline, for notication messages + public static final int NOTIFICATION_SECONDS = 20; + + //adds a server log entry + public static synchronized void AddLogEntry(String entry, CustomLogEntryTypes customLogType, boolean excludeFromServerLogs) + { + if (customLogType != null && GriefPrevention.instance.customLogger != null) + { + GriefPrevention.instance.customLogger.AddEntry(entry, customLogType); + } + if (!excludeFromServerLogs) log.info(entry); + } + + public static synchronized void AddLogEntry(String entry, CustomLogEntryTypes customLogType) { AddLogEntry(entry, customLogType, false); } - - public static synchronized void AddLogEntry(String entry) + + public static synchronized void AddLogEntry(String entry) { AddLogEntry(entry, CustomLogEntryTypes.Debug); } - - //initializes well... everything - public void onEnable() - { - instance = this; - log = instance.getLogger(); - - this.loadConfig(); - - this.customLogger = new CustomLogger(); - - AddLogEntry("Finished loading configuration."); - - //when datastore initializes, it loads player and claim data, and posts some stats to the log - if(this.databaseUrl.length() > 0) - { - try - { - DatabaseDataStore databaseStore = new DatabaseDataStore(this.databaseUrl, this.databaseUserName, this.databasePassword); - - if(FlatFileDataStore.hasData()) - { - GriefPrevention.AddLogEntry("There appears to be some data on the hard drive. Migrating those data to the database..."); - FlatFileDataStore flatFileStore = new FlatFileDataStore(); - this.dataStore = flatFileStore; - flatFileStore.migrateData(databaseStore); - GriefPrevention.AddLogEntry("Data migration process complete."); - } - - this.dataStore = databaseStore; - } - catch(Exception e) - { - GriefPrevention.AddLogEntry("Because there was a problem with the database, GriefPrevention will not function properly. Either update the database config settings resolve the issue, or delete those lines from your config.yml so that GriefPrevention can use the file system to store data."); - e.printStackTrace(); - this.getServer().getPluginManager().disablePlugin(this); - return; - } - } - - //if not using the database because it's not configured or because there was a problem, use the file system to store data - //this is the preferred method, as it's simpler than the database scenario - if(this.dataStore == null) - { - File oldclaimdata = new File(getDataFolder(), "ClaimData"); - if(oldclaimdata.exists()) { - if(!FlatFileDataStore.hasData()) { - File claimdata = new File("plugins" + File.separator + "GriefPreventionData" + File.separator + "ClaimData"); - oldclaimdata.renameTo(claimdata); - File oldplayerdata = new File(getDataFolder(), "PlayerData"); - File playerdata = new File("plugins" + File.separator + "GriefPreventionData" + File.separator + "PlayerData"); - oldplayerdata.renameTo(playerdata); - } - } - try - { - this.dataStore = new FlatFileDataStore(); - } - catch(Exception e) - { - GriefPrevention.AddLogEntry("Unable to initialize the file system data store. Details:"); - GriefPrevention.AddLogEntry(e.getMessage()); - e.printStackTrace(); - } - } - - String dataMode = (this.dataStore instanceof FlatFileDataStore)?"(File Mode)":"(Database Mode)"; - AddLogEntry("Finished loading data " + dataMode + "."); - - //unless claim block accrual is disabled, start the recurring per 10 minute event to give claim blocks to online players - //20L ~ 1 second - if(this.config_claims_blocksAccruedPerHour_default > 0) - { - DeliverClaimBlocksTask task = new DeliverClaimBlocksTask(null, this); - this.getServer().getScheduler().scheduleSyncRepeatingTask(this, task, 20L * 60 * 10, 20L * 60 * 10); - } - - //start the recurring cleanup event for entities in creative worlds - EntityCleanupTask task = new EntityCleanupTask(0); - this.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 20L * 60 * 2); - - //start recurring cleanup scan for unused claims belonging to inactive players - FindUnusedClaimsTask task2 = new FindUnusedClaimsTask(); - this.getServer().getScheduler().scheduleSyncRepeatingTask(this, task2, 20L * 60, 20L * config_advanced_claim_expiration_check_rate); - - //register for events - PluginManager pluginManager = this.getServer().getPluginManager(); - - //player events - PlayerEventHandler playerEventHandler = new PlayerEventHandler(this.dataStore, this); - pluginManager.registerEvents(playerEventHandler, this); - - //block events - BlockEventHandler blockEventHandler = new BlockEventHandler(this.dataStore); - pluginManager.registerEvents(blockEventHandler, this); - - //entity events - EntityEventHandler entityEventHandler = new EntityEventHandler(this.dataStore, this); - pluginManager.registerEvents(entityEventHandler, this); - - //if economy is enabled - if(this.config_economy_claimBlocksPurchaseCost > 0 || this.config_economy_claimBlocksSellValue > 0) - { - //try to load Vault - GriefPrevention.AddLogEntry("GriefPrevention requires Vault for economy integration."); - GriefPrevention.AddLogEntry("Attempting to load Vault..."); - RegisteredServiceProvider economyProvider = getServer().getServicesManager().getRegistration(net.milkbowl.vault.economy.Economy.class); - GriefPrevention.AddLogEntry("Vault loaded successfully!"); - - //ask Vault to hook into an economy plugin - GriefPrevention.AddLogEntry("Looking for a Vault-compatible economy plugin..."); - if (economyProvider != null) - { - GriefPrevention.economy = economyProvider.getProvider(); - - //on success, display success message - if(GriefPrevention.economy != null) - { - GriefPrevention.AddLogEntry("Hooked into economy: " + GriefPrevention.economy.getName() + "."); - GriefPrevention.AddLogEntry("Ready to buy/sell claim blocks!"); - } - - //otherwise error message - else - { - GriefPrevention.AddLogEntry("ERROR: Vault was unable to find a supported economy plugin. Either install a Vault-compatible economy plugin, or set both of the economy config variables to zero."); - } - } - - //another error case - else - { - GriefPrevention.AddLogEntry("ERROR: Vault was unable to find a supported economy plugin. Either install a Vault-compatible economy plugin, or set both of the economy config variables to zero."); - } - } - - //cache offline players - OfflinePlayer [] offlinePlayers = this.getServer().getOfflinePlayers(); - CacheOfflinePlayerNamesThread namesThread = new CacheOfflinePlayerNamesThread(offlinePlayers, this.playerNameToIDMap); - namesThread.setPriority(Thread.MIN_PRIORITY); - namesThread.start(); - - //load ignore lists for any already-online players - @SuppressWarnings("unchecked") - Collection players = (Collection)GriefPrevention.instance.getServer().getOnlinePlayers(); - for(Player player : players) - { - new IgnoreLoaderThread(player.getUniqueId(), this.dataStore.getPlayerData(player.getUniqueId()).ignoredPlayers).start(); - } - - AddLogEntry("Boot finished."); - try - { - new MetricsHandler(this, dataMode); - } - catch (Throwable ignored){} - } - - private void loadConfig() - { - //load the config if it exists + //initializes well... everything + public void onEnable() + { + instance = this; + log = instance.getLogger(); + + this.loadConfig(); + + this.customLogger = new CustomLogger(); + + AddLogEntry("Finished loading configuration."); + + //when datastore initializes, it loads player and claim data, and posts some stats to the log + if (this.databaseUrl.length() > 0) + { + try + { + DatabaseDataStore databaseStore = new DatabaseDataStore(this.databaseUrl, this.databaseUserName, this.databasePassword); + + if (FlatFileDataStore.hasData()) + { + GriefPrevention.AddLogEntry("There appears to be some data on the hard drive. Migrating those data to the database..."); + FlatFileDataStore flatFileStore = new FlatFileDataStore(); + this.dataStore = flatFileStore; + flatFileStore.migrateData(databaseStore); + GriefPrevention.AddLogEntry("Data migration process complete."); + } + + this.dataStore = databaseStore; + } + catch (Exception e) + { + GriefPrevention.AddLogEntry("Because there was a problem with the database, GriefPrevention will not function properly. Either update the database config settings resolve the issue, or delete those lines from your config.yml so that GriefPrevention can use the file system to store data."); + e.printStackTrace(); + this.getServer().getPluginManager().disablePlugin(this); + return; + } + } + + //if not using the database because it's not configured or because there was a problem, use the file system to store data + //this is the preferred method, as it's simpler than the database scenario + if (this.dataStore == null) + { + File oldclaimdata = new File(getDataFolder(), "ClaimData"); + if (oldclaimdata.exists()) + { + if (!FlatFileDataStore.hasData()) + { + File claimdata = new File("plugins" + File.separator + "GriefPreventionData" + File.separator + "ClaimData"); + oldclaimdata.renameTo(claimdata); + File oldplayerdata = new File(getDataFolder(), "PlayerData"); + File playerdata = new File("plugins" + File.separator + "GriefPreventionData" + File.separator + "PlayerData"); + oldplayerdata.renameTo(playerdata); + } + } + try + { + this.dataStore = new FlatFileDataStore(); + } + catch (Exception e) + { + GriefPrevention.AddLogEntry("Unable to initialize the file system data store. Details:"); + GriefPrevention.AddLogEntry(e.getMessage()); + e.printStackTrace(); + } + } + + String dataMode = (this.dataStore instanceof FlatFileDataStore) ? "(File Mode)" : "(Database Mode)"; + AddLogEntry("Finished loading data " + dataMode + "."); + + //unless claim block accrual is disabled, start the recurring per 10 minute event to give claim blocks to online players + //20L ~ 1 second + if (this.config_claims_blocksAccruedPerHour_default > 0) + { + DeliverClaimBlocksTask task = new DeliverClaimBlocksTask(null, this); + this.getServer().getScheduler().scheduleSyncRepeatingTask(this, task, 20L * 60 * 10, 20L * 60 * 10); + } + + //start the recurring cleanup event for entities in creative worlds + EntityCleanupTask task = new EntityCleanupTask(0); + this.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 20L * 60 * 2); + + //start recurring cleanup scan for unused claims belonging to inactive players + FindUnusedClaimsTask task2 = new FindUnusedClaimsTask(); + this.getServer().getScheduler().scheduleSyncRepeatingTask(this, task2, 20L * 60, 20L * config_advanced_claim_expiration_check_rate); + + //register for events + PluginManager pluginManager = this.getServer().getPluginManager(); + + //player events + PlayerEventHandler playerEventHandler = new PlayerEventHandler(this.dataStore, this); + pluginManager.registerEvents(playerEventHandler, this); + + //block events + BlockEventHandler blockEventHandler = new BlockEventHandler(this.dataStore); + pluginManager.registerEvents(blockEventHandler, this); + + //entity events + EntityEventHandler entityEventHandler = new EntityEventHandler(this.dataStore, this); + pluginManager.registerEvents(entityEventHandler, this); + + //if economy is enabled + if (this.config_economy_claimBlocksPurchaseCost > 0 || this.config_economy_claimBlocksSellValue > 0) + { + //try to load Vault + GriefPrevention.AddLogEntry("GriefPrevention requires Vault for economy integration."); + GriefPrevention.AddLogEntry("Attempting to load Vault..."); + RegisteredServiceProvider economyProvider = getServer().getServicesManager().getRegistration(net.milkbowl.vault.economy.Economy.class); + GriefPrevention.AddLogEntry("Vault loaded successfully!"); + + //ask Vault to hook into an economy plugin + GriefPrevention.AddLogEntry("Looking for a Vault-compatible economy plugin..."); + if (economyProvider != null) + { + GriefPrevention.economy = economyProvider.getProvider(); + + //on success, display success message + if (GriefPrevention.economy != null) + { + GriefPrevention.AddLogEntry("Hooked into economy: " + GriefPrevention.economy.getName() + "."); + GriefPrevention.AddLogEntry("Ready to buy/sell claim blocks!"); + } + + //otherwise error message + else + { + GriefPrevention.AddLogEntry("ERROR: Vault was unable to find a supported economy plugin. Either install a Vault-compatible economy plugin, or set both of the economy config variables to zero."); + } + } + + //another error case + else + { + GriefPrevention.AddLogEntry("ERROR: Vault was unable to find a supported economy plugin. Either install a Vault-compatible economy plugin, or set both of the economy config variables to zero."); + } + } + + //cache offline players + OfflinePlayer[] offlinePlayers = this.getServer().getOfflinePlayers(); + CacheOfflinePlayerNamesThread namesThread = new CacheOfflinePlayerNamesThread(offlinePlayers, this.playerNameToIDMap); + namesThread.setPriority(Thread.MIN_PRIORITY); + namesThread.start(); + + //load ignore lists for any already-online players + @SuppressWarnings("unchecked") + Collection players = (Collection) GriefPrevention.instance.getServer().getOnlinePlayers(); + for (Player player : players) + { + new IgnoreLoaderThread(player.getUniqueId(), this.dataStore.getPlayerData(player.getUniqueId()).ignoredPlayers).start(); + } + + AddLogEntry("Boot finished."); + + try + { + new MetricsHandler(this, dataMode); + } + catch (Throwable ignored) {} + } + + private void loadConfig() + { + //load the config if it exists FileConfiguration config = YamlConfiguration.loadConfiguration(new File(DataStore.configFilePath)); FileConfiguration outConfig = new YamlConfiguration(); outConfig.options().header("Default values are perfect for most servers. If you want to customize and have a question, look for the answer here first: http://dev.bukkit.org/bukkit-plugins/grief-prevention/pages/setup-and-configuration/"); - + //read configuration settings (note defaults) - + //get (deprecated node) claims world names from the config file List worlds = this.getServer().getWorlds(); List deprecated_claimsEnabledWorldNames = config.getStringList("GriefPrevention.Claims.Worlds"); - + //validate that list - for(int i = 0; i < deprecated_claimsEnabledWorldNames.size(); i++) + for (int i = 0; i < deprecated_claimsEnabledWorldNames.size(); i++) { String worldName = deprecated_claimsEnabledWorldNames.get(i); World world = this.getServer().getWorld(worldName); - if(world == null) + if (world == null) { deprecated_claimsEnabledWorldNames.remove(i--); } } - + //get (deprecated node) creative world names from the config file List deprecated_creativeClaimsEnabledWorldNames = config.getStringList("GriefPrevention.Claims.CreativeRulesWorlds"); - + //validate that list - for(int i = 0; i < deprecated_creativeClaimsEnabledWorldNames.size(); i++) + for (int i = 0; i < deprecated_creativeClaimsEnabledWorldNames.size(); i++) { String worldName = deprecated_creativeClaimsEnabledWorldNames.get(i); World world = this.getServer().getWorld(worldName); - if(world == null) + if (world == null) { deprecated_claimsEnabledWorldNames.remove(i--); } } - + //get (deprecated) pvp fire placement proximity note and use it if it exists (in the new config format it will be overwritten later). - config_pvp_allowFireNearPlayers = config.getBoolean("GriefPrevention.PvP.AllowFlintAndSteelNearOtherPlayers",false); + config_pvp_allowFireNearPlayers = config.getBoolean("GriefPrevention.PvP.AllowFlintAndSteelNearOtherPlayers", false); //get (deprecated) pvp lava dump proximity note and use it if it exists (in the new config format it will be overwritten later). - config_pvp_allowLavaNearPlayers = config.getBoolean("GriefPrevention.PvP.AllowLavaDumpingNearOtherPlayers",false); - + config_pvp_allowLavaNearPlayers = config.getBoolean("GriefPrevention.PvP.AllowLavaDumpingNearOtherPlayers", false); + //decide claim mode for each world this.config_claims_worldModes = new ConcurrentHashMap(); this.config_creativeWorldsExist = false; - for(World world : worlds) + for (World world : worlds) { //is it specified in the config file? String configSetting = config.getString("GriefPrevention.Claims.Mode." + world.getName()); - if(configSetting != null) + if (configSetting != null) { ClaimsMode claimsMode = this.configStringToClaimsMode(configSetting); - if(claimsMode != null) + if (claimsMode != null) { this.config_claims_worldModes.put(world, claimsMode); - if(claimsMode == ClaimsMode.Creative) this.config_creativeWorldsExist = true; + if (claimsMode == ClaimsMode.Creative) this.config_creativeWorldsExist = true; continue; - } - else + } else { GriefPrevention.AddLogEntry("Error: Invalid claim mode \"" + configSetting + "\". Options are Survival, Creative, and Disabled."); this.config_claims_worldModes.put(world, ClaimsMode.Creative); this.config_creativeWorldsExist = true; } } - + //was it specified in a deprecated config node? - if(deprecated_creativeClaimsEnabledWorldNames.contains(world.getName())) + if (deprecated_creativeClaimsEnabledWorldNames.contains(world.getName())) { this.config_claims_worldModes.put(world, ClaimsMode.Creative); this.config_creativeWorldsExist = true; - } - - else if(deprecated_claimsEnabledWorldNames.contains(world.getName())) + } else if (deprecated_claimsEnabledWorldNames.contains(world.getName())) { this.config_claims_worldModes.put(world, ClaimsMode.Survival); } - + //does the world's name indicate its purpose? - else if(world.getName().toLowerCase().contains("survival")) + else if (world.getName().toLowerCase().contains("survival")) { this.config_claims_worldModes.put(world, ClaimsMode.Survival); - } - - else if(world.getName().toLowerCase().contains("creative")) + } else if (world.getName().toLowerCase().contains("creative")) { this.config_claims_worldModes.put(world, ClaimsMode.Creative); this.config_creativeWorldsExist = true; } - + //decide a default based on server type and world type - else if(this.getServer().getDefaultGameMode() == GameMode.CREATIVE) + else if (this.getServer().getDefaultGameMode() == GameMode.CREATIVE) { this.config_claims_worldModes.put(world, ClaimsMode.Creative); this.config_creativeWorldsExist = true; - } - - else if(world.getEnvironment() == Environment.NORMAL) + } else if (world.getEnvironment() == Environment.NORMAL) { this.config_claims_worldModes.put(world, ClaimsMode.Survival); - } - - else + } else { this.config_claims_worldModes.put(world, ClaimsMode.Disabled); } - + //if the setting WOULD be disabled but this is a server upgrading from the old config format, //then default to survival mode for safety's sake (to protect any admin claims which may //have been created there) - if(this.config_claims_worldModes.get(world) == ClaimsMode.Disabled && - deprecated_claimsEnabledWorldNames.size() > 0) + if (this.config_claims_worldModes.get(world) == ClaimsMode.Disabled && + deprecated_claimsEnabledWorldNames.size() > 0) { this.config_claims_worldModes.put(world, ClaimsMode.Survival); } } - + //pvp worlds list this.config_pvp_specifiedWorlds = new HashMap(); - for(World world : worlds) + for (World world : worlds) { boolean pvpWorld = config.getBoolean("GriefPrevention.PvP.RulesEnabledInWorld." + world.getName(), world.getPVP()); this.config_pvp_specifiedWorlds.put(world, pvpWorld); } - + //sea level this.config_seaLevelOverride = new HashMap(); - for(int i = 0; i < worlds.size(); i++) + for (int i = 0; i < worlds.size(); i++) { int seaLevelOverride = config.getInt("GriefPrevention.SeaLevelOverrides." + worlds.get(i).getName(), -1); outConfig.set("GriefPrevention.SeaLevelOverrides." + worlds.get(i).getName(), seaLevelOverride); this.config_seaLevelOverride.put(worlds.get(i).getName(), seaLevelOverride); } - + this.config_claims_preventGlobalMonsterEggs = config.getBoolean("GriefPrevention.Claims.PreventGlobalMonsterEggs", true); this.config_claims_preventTheft = config.getBoolean("GriefPrevention.Claims.PreventTheft", true); this.config_claims_protectCreatures = config.getBoolean("GriefPrevention.Claims.ProtectCreatures", true); @@ -582,7 +575,7 @@ public class GriefPrevention extends JavaPlugin this.config_claims_expirationExemptionBonusBlocks = config.getInt("GriefPrevention.Claims.Expiration.AllClaims.ExceptWhenOwnerHasBonusClaimBlocks", 5000); this.config_claims_survivalAutoNatureRestoration = config.getBoolean("GriefPrevention.Claims.Expiration.AutomaticNatureRestoration.SurvivalWorlds", false); this.config_claims_allowTrappedInAdminClaims = config.getBoolean("GriefPrevention.Claims.AllowTrappedInAdminClaims", false); - + this.config_claims_maxClaimsPerPlayer = config.getInt("GriefPrevention.Claims.MaximumNumberOfClaimsPerPlayer", 0); this.config_claims_respectWorldGuard = config.getBoolean("GriefPrevention.Claims.CreationRequiresWorldGuardBuildPermission", true); this.config_claims_villagerTradingRequiresTrust = config.getBoolean("GriefPrevention.Claims.VillagerTradingRequiresPermission", true); @@ -593,20 +586,20 @@ public class GriefPrevention extends JavaPlugin this.config_claims_firespreads = config.getBoolean("GriefPrevention.Claims.FireSpreadsInClaims", false); this.config_claims_firedamages = config.getBoolean("GriefPrevention.Claims.FireDamagesInClaims", false); - this.config_claims_lecternReadingRequiresAccessTrust = config.getBoolean("GriefPrevention.Claims.LecternReadingRequiresAccessTrust", true); + this.config_claims_lecternReadingRequiresAccessTrust = config.getBoolean("GriefPrevention.Claims.LecternReadingRequiresAccessTrust", true); this.config_spam_enabled = config.getBoolean("GriefPrevention.Spam.Enabled", true); this.config_spam_loginCooldownSeconds = config.getInt("GriefPrevention.Spam.LoginCooldownSeconds", 60); this.config_spam_loginLogoutNotificationsPerMinute = config.getInt("GriefPrevention.Spam.LoginLogoutNotificationsPerMinute", 5); this.config_spam_warningMessage = config.getString("GriefPrevention.Spam.WarningMessage", "Please reduce your noise level. Spammers will be banned."); this.config_spam_allowedIpAddresses = config.getString("GriefPrevention.Spam.AllowedIpAddresses", "1.2.3.4; 5.6.7.8"); - this.config_spam_banOffenders = config.getBoolean("GriefPrevention.Spam.BanOffenders", true); + this.config_spam_banOffenders = config.getBoolean("GriefPrevention.Spam.BanOffenders", true); this.config_spam_banMessage = config.getString("GriefPrevention.Spam.BanMessage", "Banned for spam."); String slashCommandsToMonitor = config.getString("GriefPrevention.Spam.MonitorSlashCommands", "/me;/global;/local"); slashCommandsToMonitor = config.getString("GriefPrevention.Spam.ChatSlashCommands", slashCommandsToMonitor); this.config_spam_deathMessageCooldownSeconds = config.getInt("GriefPrevention.Spam.DeathMessageCooldownSeconds", 120); this.config_spam_logoutMessageDelaySeconds = config.getInt("GriefPrevention.Spam.Logout Message Delay In Seconds", 0); - + this.config_pvp_protectFreshSpawns = config.getBoolean("GriefPrevention.PvP.ProtectFreshSpawns", true); this.config_pvp_punishLogout = config.getBoolean("GriefPrevention.PvP.PunishLogout", true); this.config_pvp_combatTimeoutSeconds = config.getInt("GriefPrevention.PvP.CombatTimeoutSeconds", 15); @@ -616,10 +609,10 @@ public class GriefPrevention extends JavaPlugin this.config_economy_claimBlocksMaxBonus = config.getInt("GriefPrevention.Economy.ClaimBlocksMaxBonus", 0); this.config_economy_claimBlocksPurchaseCost = config.getDouble("GriefPrevention.Economy.ClaimBlocksPurchaseCost", 0); this.config_economy_claimBlocksSellValue = config.getDouble("GriefPrevention.Economy.ClaimBlocksSellValue", 0); - + this.config_lockDeathDropsInPvpWorlds = config.getBoolean("GriefPrevention.ProtectItemsDroppedOnDeath.PvPWorlds", false); this.config_lockDeathDropsInNonPvpWorlds = config.getBoolean("GriefPrevention.ProtectItemsDroppedOnDeath.NonPvPWorlds", true); - + this.config_blockClaimExplosions = config.getBoolean("GriefPrevention.BlockLandClaimExplosions", true); this.config_blockSurfaceCreeperExplosions = config.getBoolean("GriefPrevention.BlockSurfaceCreeperExplosions", true); this.config_blockSurfaceOtherExplosions = config.getBoolean("GriefPrevention.BlockSurfaceOtherExplosions", true); @@ -627,19 +620,19 @@ public class GriefPrevention extends JavaPlugin this.config_limitTreeGrowth = config.getBoolean("GriefPrevention.LimitTreeGrowth", false); this.config_checkPistonMovement = config.getBoolean("GriefPrevention.CheckPistonMovement", true); this.config_pistonsInClaimsOnly = config.getBoolean("GriefPrevention.LimitPistonsToLandClaims", true); - + this.config_fireSpreads = config.getBoolean("GriefPrevention.FireSpreads", false); this.config_fireDestroys = config.getBoolean("GriefPrevention.FireDestroys", false); - + this.config_whisperNotifications = config.getBoolean("GriefPrevention.AdminsGetWhispers", true); this.config_signNotifications = config.getBoolean("GriefPrevention.AdminsGetSignNotifications", true); String whisperCommandsToMonitor = config.getString("GriefPrevention.WhisperCommands", "/tell;/pm;/r;/whisper;/msg"); whisperCommandsToMonitor = config.getString("GriefPrevention.Spam.WhisperSlashCommands", whisperCommandsToMonitor); - + this.config_smartBan = config.getBoolean("GriefPrevention.SmartBan", true); this.config_trollFilterEnabled = config.getBoolean("GriefPrevention.Mute New Players Using Banned Words", true); - this.config_ipLimit = config.getInt("GriefPrevention.MaxPlayersPerIpAddress", 3); - + this.config_ipLimit = config.getInt("GriefPrevention.MaxPlayersPerIpAddress", 3); + this.config_endermenMoveBlocks = config.getBoolean("GriefPrevention.EndermenMoveBlocks", false); this.config_silverfishBreakBlocks = config.getBoolean("GriefPrevention.SilverfishBreakBlocks", false); this.config_creaturesTrampleCrops = config.getBoolean("GriefPrevention.CreaturesTrampleCrops", false); @@ -647,61 +640,60 @@ public class GriefPrevention extends JavaPlugin this.config_zombiesBreakDoors = config.getBoolean("GriefPrevention.HardModeZombiesBreakDoors", false); this.config_ban_useCommand = config.getBoolean("GriefPrevention.UseBanCommand", false); this.config_ban_commandFormat = config.getString("GriefPrevention.BanCommandPattern", "ban %name% %reason%"); - + //default for claim investigation tool String investigationToolMaterialName = Material.STICK.name(); - + //get investigation tool from config investigationToolMaterialName = config.getString("GriefPrevention.Claims.InvestigationTool", investigationToolMaterialName); - + //validate investigation tool this.config_claims_investigationTool = Material.getMaterial(investigationToolMaterialName); - if(this.config_claims_investigationTool == null) + if (this.config_claims_investigationTool == null) { GriefPrevention.AddLogEntry("ERROR: Material " + investigationToolMaterialName + " not found. Defaulting to the stick. Please update your config.yml."); this.config_claims_investigationTool = Material.STICK; } - + //default for claim creation/modification tool String modificationToolMaterialName = Material.GOLDEN_SHOVEL.name(); - + //get modification tool from config modificationToolMaterialName = config.getString("GriefPrevention.Claims.ModificationTool", modificationToolMaterialName); - + //validate modification tool this.config_claims_modificationTool = Material.getMaterial(modificationToolMaterialName); - if(this.config_claims_modificationTool == null) + if (this.config_claims_modificationTool == null) { GriefPrevention.AddLogEntry("ERROR: Material " + modificationToolMaterialName + " not found. Defaulting to the golden shovel. Please update your config.yml."); this.config_claims_modificationTool = Material.GOLDEN_SHOVEL; } - + //default for siege worlds list ArrayList defaultSiegeWorldNames = new ArrayList(); - + //get siege world names from the config file List siegeEnabledWorldNames = config.getStringList("GriefPrevention.Siege.Worlds"); - if(siegeEnabledWorldNames == null) - { + if (siegeEnabledWorldNames == null) + { siegeEnabledWorldNames = defaultSiegeWorldNames; } - + //validate that list this.config_siege_enabledWorlds = new ArrayList(); - for(int i = 0; i < siegeEnabledWorldNames.size(); i++) + for (int i = 0; i < siegeEnabledWorldNames.size(); i++) { String worldName = siegeEnabledWorldNames.get(i); World world = this.getServer().getWorld(worldName); - if(world == null) + if (world == null) { AddLogEntry("Error: Siege Configuration: There's no world named \"" + worldName + "\". Please update your config.yml."); - } - else + } else { this.config_siege_enabledWorlds.add(world); } } - + //default siege blocks this.config_siege_blocks = new ArrayList(); this.config_siege_blocks.add(Material.DIRT); @@ -737,40 +729,39 @@ public class GriefPrevention extends JavaPlugin this.config_siege_blocks.add(Material.RED_WOOL); this.config_siege_blocks.add(Material.BLACK_WOOL); this.config_siege_blocks.add(Material.SNOW); - + //build a default config entry ArrayList defaultBreakableBlocksList = new ArrayList(); - for(int i = 0; i < this.config_siege_blocks.size(); i++) + for (int i = 0; i < this.config_siege_blocks.size(); i++) { defaultBreakableBlocksList.add(this.config_siege_blocks.get(i).name()); } - + //try to load the list from the config file List breakableBlocksList = config.getStringList("GriefPrevention.Siege.BreakableBlocks"); - + //if it fails, use default list instead - if(breakableBlocksList == null || breakableBlocksList.size() == 0) + if (breakableBlocksList == null || breakableBlocksList.size() == 0) { breakableBlocksList = defaultBreakableBlocksList; } - + //parse the list of siege-breakable blocks this.config_siege_blocks = new ArrayList(); - for(int i = 0; i < breakableBlocksList.size(); i++) + for (int i = 0; i < breakableBlocksList.size(); i++) { String blockName = breakableBlocksList.get(i); Material material = Material.getMaterial(blockName); - if(material == null) + if (material == null) { GriefPrevention.AddLogEntry("Siege Configuration: Material not found: " + blockName + "."); - } - else + } else { this.config_siege_blocks.add(material); } } - - this.config_siege_doorsOpenSeconds = config.getInt("GriefPrevention.Siege.DoorsOpenDelayInSeconds", 5*60); + + this.config_siege_doorsOpenSeconds = config.getInt("GriefPrevention.Siege.DoorsOpenDelayInSeconds", 5 * 60); this.config_siege_cooldownEndInMinutes = config.getInt("GriefPrevention.Siege.CooldownEndInMinutes", 60); this.config_pvp_noCombatInPlayerLandClaims = config.getBoolean("GriefPrevention.PvP.ProtectPlayersInLandClaims.PlayerOwnedClaims", this.config_siege_enabledWorlds.size() == 0); this.config_pvp_noCombatInAdminLandClaims = config.getBoolean("GriefPrevention.PvP.ProtectPlayersInLandClaims.AdministrativeClaims", this.config_siege_enabledWorlds.size() == 0); @@ -780,7 +771,7 @@ public class GriefPrevention extends JavaPlugin this.config_pvp_allowFireNearPlayers = config.getBoolean("GriefPrevention.PvP.AllowFlintAndSteelNearOtherPlayers.PvPWorlds", true); this.config_pvp_allowFireNearPlayers_NonPvp = config.getBoolean("GriefPrevention.PvP.AllowFlintAndSteelNearOtherPlayers.NonPvPWorlds", false); this.config_pvp_protectPets = config.getBoolean("GriefPrevention.PvP.ProtectPetsOutsideLandClaims", false); - + //optional database settings this.databaseUrl = config.getString("GriefPrevention.Database.URL", ""); this.databaseUserName = config.getString("GriefPrevention.Database.UserName", ""); @@ -789,7 +780,7 @@ public class GriefPrevention extends JavaPlugin this.config_advanced_fixNegativeClaimblockAmounts = config.getBoolean("GriefPrevention.Advanced.fixNegativeClaimblockAmounts", true); this.config_advanced_claim_expiration_check_rate = config.getInt("GriefPrevention.Advanced.ClaimExpirationCheckRate", 60); this.config_advanced_offlineplayer_cache_days = config.getInt("GriefPrevention.Advanced.OfflinePlayer_cache_days", 90); - + //custom logger settings this.config_logs_daysToKeep = config.getInt("GriefPrevention.Abridged Logs.Days To Keep", 7); this.config_logs_socialEnabled = config.getBoolean("GriefPrevention.Abridged Logs.Included Entry Types.Social Activity", true); @@ -797,18 +788,18 @@ public class GriefPrevention extends JavaPlugin this.config_logs_adminEnabled = config.getBoolean("GriefPrevention.Abridged Logs.Included Entry Types.Administrative Activity", false); this.config_logs_debugEnabled = config.getBoolean("GriefPrevention.Abridged Logs.Included Entry Types.Debug", false); this.config_logs_mutedChatEnabled = config.getBoolean("GriefPrevention.Abridged Logs.Included Entry Types.Muted Chat Messages", false); - - //claims mode by world - for(World world : this.config_claims_worldModes.keySet()) - { - outConfig.set( - "GriefPrevention.Claims.Mode." + world.getName(), - this.config_claims_worldModes.get(world).name()); - } - - outConfig.set("GriefPrevention.Claims.PreventGlobalMonsterEggs", this.config_claims_preventGlobalMonsterEggs); - outConfig.set("GriefPrevention.Claims.PreventTheft", this.config_claims_preventTheft); + //claims mode by world + for (World world : this.config_claims_worldModes.keySet()) + { + outConfig.set( + "GriefPrevention.Claims.Mode." + world.getName(), + this.config_claims_worldModes.get(world).name()); + } + + + outConfig.set("GriefPrevention.Claims.PreventGlobalMonsterEggs", this.config_claims_preventGlobalMonsterEggs); + outConfig.set("GriefPrevention.Claims.PreventTheft", this.config_claims_preventTheft); outConfig.set("GriefPrevention.Claims.ProtectCreatures", this.config_claims_protectCreatures); outConfig.set("GriefPrevention.Claims.PreventButtonsSwitches", this.config_claims_preventButtonsSwitches); outConfig.set("GriefPrevention.Claims.LockWoodenDoors", this.config_claims_lockWoodenDoors); @@ -832,7 +823,7 @@ public class GriefPrevention extends JavaPlugin outConfig.set("GriefPrevention.Claims.InvestigationTool", this.config_claims_investigationTool.name()); outConfig.set("GriefPrevention.Claims.ModificationTool", this.config_claims_modificationTool.name()); outConfig.set("GriefPrevention.Claims.Expiration.ChestClaimDays", this.config_claims_chestClaimExpirationDays); - outConfig.set("GriefPrevention.Claims.Expiration.UnusedClaimDays", this.config_claims_unusedClaimExpirationDays); + outConfig.set("GriefPrevention.Claims.Expiration.UnusedClaimDays", this.config_claims_unusedClaimExpirationDays); outConfig.set("GriefPrevention.Claims.Expiration.AllClaims.DaysInactive", this.config_claims_expirationDays); outConfig.set("GriefPrevention.Claims.Expiration.AllClaims.ExceptWhenOwnerHasTotalClaimBlocks", this.config_claims_expirationExemptionTotalBlocks); outConfig.set("GriefPrevention.Claims.Expiration.AllClaims.ExceptWhenOwnerHasBonusClaimBlocks", this.config_claims_expirationExemptionBonusBlocks); @@ -854,15 +845,15 @@ public class GriefPrevention extends JavaPlugin outConfig.set("GriefPrevention.Spam.LoginCooldownSeconds", this.config_spam_loginCooldownSeconds); outConfig.set("GriefPrevention.Spam.LoginLogoutNotificationsPerMinute", this.config_spam_loginLogoutNotificationsPerMinute); outConfig.set("GriefPrevention.Spam.ChatSlashCommands", slashCommandsToMonitor); - outConfig.set("GriefPrevention.Spam.WhisperSlashCommands", whisperCommandsToMonitor); + outConfig.set("GriefPrevention.Spam.WhisperSlashCommands", whisperCommandsToMonitor); outConfig.set("GriefPrevention.Spam.WarningMessage", this.config_spam_warningMessage); - outConfig.set("GriefPrevention.Spam.BanOffenders", this.config_spam_banOffenders); + outConfig.set("GriefPrevention.Spam.BanOffenders", this.config_spam_banOffenders); outConfig.set("GriefPrevention.Spam.BanMessage", this.config_spam_banMessage); outConfig.set("GriefPrevention.Spam.AllowedIpAddresses", this.config_spam_allowedIpAddresses); outConfig.set("GriefPrevention.Spam.DeathMessageCooldownSeconds", this.config_spam_deathMessageCooldownSeconds); outConfig.set("GriefPrevention.Spam.Logout Message Delay In Seconds", this.config_spam_logoutMessageDelaySeconds); - - for(World world : worlds) + + for (World world : worlds) { outConfig.set("GriefPrevention.PvP.RulesEnabledInWorld." + world.getName(), this.pvpRulesApply(world)); } @@ -883,42 +874,42 @@ public class GriefPrevention extends JavaPlugin outConfig.set("GriefPrevention.Economy.ClaimBlocksMaxBonus", this.config_economy_claimBlocksMaxBonus); outConfig.set("GriefPrevention.Economy.ClaimBlocksPurchaseCost", this.config_economy_claimBlocksPurchaseCost); outConfig.set("GriefPrevention.Economy.ClaimBlocksSellValue", this.config_economy_claimBlocksSellValue); - + outConfig.set("GriefPrevention.ProtectItemsDroppedOnDeath.PvPWorlds", this.config_lockDeathDropsInPvpWorlds); outConfig.set("GriefPrevention.ProtectItemsDroppedOnDeath.NonPvPWorlds", this.config_lockDeathDropsInNonPvpWorlds); - + outConfig.set("GriefPrevention.BlockLandClaimExplosions", this.config_blockClaimExplosions); outConfig.set("GriefPrevention.BlockSurfaceCreeperExplosions", this.config_blockSurfaceCreeperExplosions); outConfig.set("GriefPrevention.BlockSurfaceOtherExplosions", this.config_blockSurfaceOtherExplosions); outConfig.set("GriefPrevention.LimitSkyTrees", this.config_blockSkyTrees); outConfig.set("GriefPrevention.LimitTreeGrowth", this.config_limitTreeGrowth); - outConfig.set("GriefPrevention.CheckPistonMovement", this.config_checkPistonMovement); + outConfig.set("GriefPrevention.CheckPistonMovement", this.config_checkPistonMovement); outConfig.set("GriefPrevention.LimitPistonsToLandClaims", this.config_pistonsInClaimsOnly); - + outConfig.set("GriefPrevention.FireSpreads", this.config_fireSpreads); outConfig.set("GriefPrevention.FireDestroys", this.config_fireDestroys); - + outConfig.set("GriefPrevention.AdminsGetWhispers", this.config_whisperNotifications); outConfig.set("GriefPrevention.AdminsGetSignNotifications", this.config_signNotifications); - + outConfig.set("GriefPrevention.SmartBan", this.config_smartBan); outConfig.set("GriefPrevention.Mute New Players Using Banned Words", this.config_trollFilterEnabled); outConfig.set("GriefPrevention.MaxPlayersPerIpAddress", this.config_ipLimit); - + outConfig.set("GriefPrevention.Siege.Worlds", siegeEnabledWorldNames); outConfig.set("GriefPrevention.Siege.BreakableBlocks", breakableBlocksList); outConfig.set("GriefPrevention.Siege.DoorsOpenDelayInSeconds", this.config_siege_doorsOpenSeconds); outConfig.set("GriefPrevention.Siege.CooldownEndInMinutes", this.config_siege_cooldownEndInMinutes); outConfig.set("GriefPrevention.EndermenMoveBlocks", this.config_endermenMoveBlocks); - outConfig.set("GriefPrevention.SilverfishBreakBlocks", this.config_silverfishBreakBlocks); + outConfig.set("GriefPrevention.SilverfishBreakBlocks", this.config_silverfishBreakBlocks); outConfig.set("GriefPrevention.CreaturesTrampleCrops", this.config_creaturesTrampleCrops); outConfig.set("GriefPrevention.RabbitsEatCrops", this.config_rabbitsEatCrops); outConfig.set("GriefPrevention.HardModeZombiesBreakDoors", this.config_zombiesBreakDoors); - + outConfig.set("GriefPrevention.Database.URL", this.databaseUrl); outConfig.set("GriefPrevention.Database.UserName", this.databaseUserName); outConfig.set("GriefPrevention.Database.Password", this.databasePassword); - + outConfig.set("GriefPrevention.UseBanCommand", this.config_ban_useCommand); outConfig.set("GriefPrevention.BanCommandPattern", this.config_ban_commandFormat); @@ -933,47 +924,47 @@ public class GriefPrevention extends JavaPlugin outConfig.set("GriefPrevention.Abridged Logs.Included Entry Types.Administrative Activity", this.config_logs_adminEnabled); outConfig.set("GriefPrevention.Abridged Logs.Included Entry Types.Debug", this.config_logs_debugEnabled); outConfig.set("GriefPrevention.Abridged Logs.Included Entry Types.Muted Chat Messages", this.config_logs_mutedChatEnabled); - + try { outConfig.save(DataStore.configFilePath); } - catch(IOException exception) + catch (IOException exception) { AddLogEntry("Unable to write to the configuration file at \"" + DataStore.configFilePath + "\""); } - + //try to parse the list of commands requiring access trust in land claims this.config_claims_commandsRequiringAccessTrust = new ArrayList(); - String [] commands = accessTrustSlashCommands.split(";"); - for(int i = 0; i < commands.length; i++) + String[] commands = accessTrustSlashCommands.split(";"); + for (int i = 0; i < commands.length; i++) { - if(!commands[i].isEmpty()) + if (!commands[i].isEmpty()) { this.config_claims_commandsRequiringAccessTrust.add(commands[i].trim().toLowerCase()); } } - + //try to parse the list of commands which should be monitored for spam this.config_spam_monitorSlashCommands = new ArrayList(); commands = slashCommandsToMonitor.split(";"); - for(int i = 0; i < commands.length; i++) + for (int i = 0; i < commands.length; i++) { this.config_spam_monitorSlashCommands.add(commands[i].trim().toLowerCase()); } - + //try to parse the list of commands which should be included in eavesdropping - this.config_eavesdrop_whisperCommands = new ArrayList(); + this.config_eavesdrop_whisperCommands = new ArrayList(); commands = whisperCommandsToMonitor.split(";"); - for(int i = 0; i < commands.length; i++) + for (int i = 0; i < commands.length; i++) { this.config_eavesdrop_whisperCommands.add(commands[i].trim().toLowerCase()); - } - + } + //try to parse the list of commands which should be banned during pvp combat this.config_pvp_blockedCommands = new ArrayList(); commands = bannedPvPCommandsList.split(";"); - for(int i = 0; i < commands.length; i++) + for (int i = 0; i < commands.length; i++) { this.config_pvp_blockedCommands.add(commands[i].trim().toLowerCase()); } @@ -981,150 +972,143 @@ public class GriefPrevention extends JavaPlugin private ClaimsMode configStringToClaimsMode(String configSetting) { - if(configSetting.equalsIgnoreCase("Survival")) + if (configSetting.equalsIgnoreCase("Survival")) { return ClaimsMode.Survival; - } - else if(configSetting.equalsIgnoreCase("Creative")) + } else if (configSetting.equalsIgnoreCase("Creative")) { return ClaimsMode.Creative; - } - else if(configSetting.equalsIgnoreCase("Disabled")) + } else if (configSetting.equalsIgnoreCase("Disabled")) { return ClaimsMode.Disabled; - } - else if(configSetting.equalsIgnoreCase("SurvivalRequiringClaims")) + } else if (configSetting.equalsIgnoreCase("SurvivalRequiringClaims")) { return ClaimsMode.SurvivalRequiringClaims; - } - else + } else { return null; } } //handles slash commands - @SuppressWarnings("deprecation") - public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){ - - Player player = null; - if (sender instanceof Player) - { - player = (Player) sender; - } - - //claim - if(cmd.getName().equalsIgnoreCase("claim") && player != null) + @SuppressWarnings("deprecation") + public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) + { + + Player player = null; + if (sender instanceof Player) { - if(!GriefPrevention.instance.claimsEnabledForWorld(player.getWorld())) + player = (Player) sender; + } + + //claim + if (cmd.getName().equalsIgnoreCase("claim") && player != null) + { + if (!GriefPrevention.instance.claimsEnabledForWorld(player.getWorld())) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.ClaimsDisabledWorld); return true; } - + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - + //if he's at the claim count per player limit already and doesn't have permission to bypass, display an error message - if(GriefPrevention.instance.config_claims_maxClaimsPerPlayer > 0 && - !player.hasPermission("griefprevention.overrideclaimcountlimit") && - playerData.getClaims().size() >= GriefPrevention.instance.config_claims_maxClaimsPerPlayer) + if (GriefPrevention.instance.config_claims_maxClaimsPerPlayer > 0 && + !player.hasPermission("griefprevention.overrideclaimcountlimit") && + playerData.getClaims().size() >= GriefPrevention.instance.config_claims_maxClaimsPerPlayer) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.ClaimCreationFailedOverClaimCountLimit); return true; } - + //default is chest claim radius, unless -1 int radius = GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius; - if(radius < 0) radius = (int)Math.ceil(Math.sqrt(GriefPrevention.instance.config_claims_minArea) / 2); - + if (radius < 0) radius = (int) Math.ceil(Math.sqrt(GriefPrevention.instance.config_claims_minArea) / 2); + //if player has any claims, respect claim minimum size setting - if(playerData.getClaims().size() > 0) + if (playerData.getClaims().size() > 0) { //if player has exactly one land claim, this requires the claim modification tool to be in hand (or creative mode player) - if(playerData.getClaims().size() == 1 && player.getGameMode() != GameMode.CREATIVE && player.getItemInHand().getType() != GriefPrevention.instance.config_claims_modificationTool) + if (playerData.getClaims().size() == 1 && player.getGameMode() != GameMode.CREATIVE && player.getItemInHand().getType() != GriefPrevention.instance.config_claims_modificationTool) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.MustHoldModificationToolForThat); return true; } - - radius = (int)Math.ceil(Math.sqrt(GriefPrevention.instance.config_claims_minArea) / 2); + + radius = (int) Math.ceil(Math.sqrt(GriefPrevention.instance.config_claims_minArea) / 2); } - + //allow for specifying the radius - if(args.length > 0) + if (args.length > 0) { - if(playerData.getClaims().size() < 2 && player.getGameMode() != GameMode.CREATIVE && player.getItemInHand().getType() != GriefPrevention.instance.config_claims_modificationTool) + if (playerData.getClaims().size() < 2 && player.getGameMode() != GameMode.CREATIVE && player.getItemInHand().getType() != GriefPrevention.instance.config_claims_modificationTool) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.RadiusRequiresGoldenShovel); return true; } - + int specifiedRadius; try { specifiedRadius = Integer.parseInt(args[0]); } - catch(NumberFormatException e) + catch (NumberFormatException e) { return false; } - - if(specifiedRadius < radius) + + if (specifiedRadius < radius) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.MinimumRadius, String.valueOf(radius)); return true; - } - else + } else { radius = specifiedRadius; } } - - if(radius < 0) radius = 0; - + + if (radius < 0) radius = 0; + Location lc = player.getLocation().add(-radius, 0, -radius); Location gc = player.getLocation().add(radius, 0, radius); - + //player must have sufficient unused claim blocks int area = Math.abs((gc.getBlockX() - lc.getBlockX() + 1) * (gc.getBlockZ() - lc.getBlockZ() + 1)); int remaining = playerData.getRemainingClaimBlocks(); - if(remaining < area) + if (remaining < area) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.CreateClaimInsufficientBlocks, String.valueOf(area - remaining)); GriefPrevention.instance.dataStore.tryAdvertiseAdminAlternatives(player); return true; } - CreateClaimResult result = this.dataStore.createClaim(lc.getWorld(), + CreateClaimResult result = this.dataStore.createClaim(lc.getWorld(), lc.getBlockX(), gc.getBlockX(), lc.getBlockY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance - 1, gc.getWorld().getHighestBlockYAt(gc) - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance - 1, lc.getBlockZ(), gc.getBlockZ(), player.getUniqueId(), null, null, player); - if(!result.succeeded) + if (!result.succeeded) { - if(result.claim != null) + if (result.claim != null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlapShort); - + Visualization visualization = Visualization.FromClaim(result.claim, player.getEyeLocation().getBlockY(), VisualizationType.ErrorClaim, player.getLocation()); Visualization.Apply(player, visualization); - } - else + } else { GriefPrevention.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlapRegion); } - } - else + } else { GriefPrevention.sendMessage(player, TextMode.Success, Messages.CreateClaimSuccess); - + //link to a video demo of land claiming, based on world type - if(GriefPrevention.instance.creativeRulesApply(player.getLocation())) + if (GriefPrevention.instance.creativeRulesApply(player.getLocation())) { - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.CreativeBasicsVideo2, DataStore.CREATIVE_VIDEO_URL); - } - else if(GriefPrevention.instance.claimsEnabledForWorld(player.getLocation().getWorld())) + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.CreativeBasicsVideo2, DataStore.CREATIVE_VIDEO_URL); + } else if (GriefPrevention.instance.claimsEnabledForWorld(player.getLocation().getWorld())) { GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsVideo2, DataStore.SURVIVAL_VIDEO_URL); } @@ -1132,87 +1116,85 @@ public class GriefPrevention extends JavaPlugin Visualization.Apply(player, visualization); playerData.claimResizing = null; playerData.lastShovelLocation = null; - + this.autoExtendClaim(result.claim); } - + return true; } - - //extendclaim - if(cmd.getName().equalsIgnoreCase("extendclaim") && player != null) + + //extendclaim + if (cmd.getName().equalsIgnoreCase("extendclaim") && player != null) { - if(args.length < 1) + if (args.length < 1) { //link to a video demo of land claiming, based on world type - if(GriefPrevention.instance.creativeRulesApply(player.getLocation())) + if (GriefPrevention.instance.creativeRulesApply(player.getLocation())) { - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.CreativeBasicsVideo2, DataStore.CREATIVE_VIDEO_URL); - } - else if(GriefPrevention.instance.claimsEnabledForWorld(player.getLocation().getWorld())) + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.CreativeBasicsVideo2, DataStore.CREATIVE_VIDEO_URL); + } else if (GriefPrevention.instance.claimsEnabledForWorld(player.getLocation().getWorld())) { GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsVideo2, DataStore.SURVIVAL_VIDEO_URL); } return false; } - + int amount; try { amount = Integer.parseInt(args[0]); } - catch(NumberFormatException e) + catch (NumberFormatException e) { //link to a video demo of land claiming, based on world type - if(GriefPrevention.instance.creativeRulesApply(player.getLocation())) + if (GriefPrevention.instance.creativeRulesApply(player.getLocation())) { - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.CreativeBasicsVideo2, DataStore.CREATIVE_VIDEO_URL); - } - else if(GriefPrevention.instance.claimsEnabledForWorld(player.getLocation().getWorld())) + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.CreativeBasicsVideo2, DataStore.CREATIVE_VIDEO_URL); + } else if (GriefPrevention.instance.claimsEnabledForWorld(player.getLocation().getWorld())) { GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsVideo2, DataStore.SURVIVAL_VIDEO_URL); } return false; } - + //requires claim modification tool in hand - if(player.getGameMode() != GameMode.CREATIVE && player.getItemInHand().getType() != GriefPrevention.instance.config_claims_modificationTool) + if (player.getGameMode() != GameMode.CREATIVE && player.getItemInHand().getType() != GriefPrevention.instance.config_claims_modificationTool) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.MustHoldModificationToolForThat); return true; } - + //must be standing in a land claim PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); Claim claim = this.dataStore.getClaimAt(player.getLocation(), true, playerData.lastClaim); - if(claim == null) + if (claim == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.StandInClaimToResize); return true; } - + //must have permission to edit the land claim you're in String errorMessage = claim.allowEdit(player); - if(errorMessage != null) + if (errorMessage != null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.NotYourClaim); return true; } - + //determine new corner coordinates org.bukkit.util.Vector direction = player.getLocation().getDirection(); - if(direction.getY() > .75) + if (direction.getY() > .75) { GriefPrevention.sendMessage(player, TextMode.Info, Messages.ClaimsExtendToSky); return true; } - - if(direction.getY() < -.75) + + if (direction.getY() < -.75) { GriefPrevention.sendMessage(player, TextMode.Info, Messages.ClaimsAutoExtendDownward); return true; } - + Location lc = claim.getLesserBoundaryCorner(); Location gc = claim.getGreaterBoundaryCorner(); int newx1 = lc.getBlockX(); @@ -1221,1032 +1203,1007 @@ public class GriefPrevention extends JavaPlugin int newy2 = gc.getBlockY(); int newz1 = lc.getBlockZ(); int newz2 = gc.getBlockZ(); - + //if changing Z only - if(Math.abs(direction.getX()) < .3) + if (Math.abs(direction.getX()) < .3) { - if(direction.getZ() > 0) + if (direction.getZ() > 0) { newz2 += amount; //north - } - else + } else { newz1 -= amount; //south } } - + //if changing X only - else if(Math.abs(direction.getZ()) < .3) + else if (Math.abs(direction.getZ()) < .3) { - if(direction.getX() > 0) + if (direction.getX() > 0) { newx2 += amount; //east - } - else + } else { newx1 -= amount; //west } } - + //diagonals else { - if(direction.getX() > 0) + if (direction.getX() > 0) { newx2 += amount; - } - else + } else { newx1 -= amount; } - - if(direction.getZ() > 0) + + if (direction.getZ() > 0) { newz2 += amount; - } - else + } else { newz1 -= amount; } } - + //attempt resize playerData.claimResizing = claim; this.dataStore.resizeClaimWithChecks(player, playerData, newx1, newx2, newy1, newy2, newz1, newz2); playerData.claimResizing = null; - + return true; } - - //abandonclaim - if(cmd.getName().equalsIgnoreCase("abandonclaim") && player != null) - { - return this.abandonClaimHandler(player, false); - } - - //abandontoplevelclaim - if(cmd.getName().equalsIgnoreCase("abandontoplevelclaim") && player != null) - { - return this.abandonClaimHandler(player, true); - } - - //ignoreclaims - if(cmd.getName().equalsIgnoreCase("ignoreclaims") && player != null) - { - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - - playerData.ignoreClaims = !playerData.ignoreClaims; - - //toggle ignore claims mode on or off - if(!playerData.ignoreClaims) - { - GriefPrevention.sendMessage(player, TextMode.Success, Messages.RespectingClaims); - } - else - { - GriefPrevention.sendMessage(player, TextMode.Success, Messages.IgnoringClaims); - } - - return true; - } - - //abandonallclaims - else if(cmd.getName().equalsIgnoreCase("abandonallclaims") && player != null) - { - if(args.length != 0) return false; - - //count claims - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - int originalClaimCount = playerData.getClaims().size(); - - //check count - if(originalClaimCount == 0) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.YouHaveNoClaims); - return true; - } - if (this.config_claims_abandonReturnRatio != 1.0D) - { - //adjust claim blocks - for(Claim claim : playerData.getClaims()) - { - playerData.setAccruedClaimBlocks(playerData.getAccruedClaimBlocks() - (int)Math.ceil((claim.getArea() * (1 - this.config_claims_abandonReturnRatio)))); - } - } + //abandonclaim + if (cmd.getName().equalsIgnoreCase("abandonclaim") && player != null) + { + return this.abandonClaimHandler(player, false); + } - - //delete them - this.dataStore.deleteClaimsForPlayer(player.getUniqueId(), false); - - //inform the player - int remainingBlocks = playerData.getRemainingClaimBlocks(); - GriefPrevention.sendMessage(player, TextMode.Success, Messages.SuccessfulAbandon, String.valueOf(remainingBlocks)); - - //revert any current visualization - Visualization.Revert(player); - - return true; - } - - //restore nature - else if(cmd.getName().equalsIgnoreCase("restorenature") && player != null) - { - //change shovel mode - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - playerData.shovelMode = ShovelMode.RestoreNature; - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.RestoreNatureActivate); - return true; - } - - //restore nature aggressive mode - else if(cmd.getName().equalsIgnoreCase("restorenatureaggressive") && player != null) - { - //change shovel mode - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - playerData.shovelMode = ShovelMode.RestoreNatureAggressive; - GriefPrevention.sendMessage(player, TextMode.Warn, Messages.RestoreNatureAggressiveActivate); - return true; - } - - //restore nature fill mode - else if(cmd.getName().equalsIgnoreCase("restorenaturefill") && player != null) - { - //change shovel mode - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - playerData.shovelMode = ShovelMode.RestoreNatureFill; - - //set radius based on arguments - playerData.fillRadius = 2; - if(args.length > 0) - { - try - { - playerData.fillRadius = Integer.parseInt(args[0]); - } - catch(Exception exception){ } - } - - if(playerData.fillRadius < 0) playerData.fillRadius = 2; - - GriefPrevention.sendMessage(player, TextMode.Success, Messages.FillModeActive, String.valueOf(playerData.fillRadius)); - return true; - } - - //trust - else if(cmd.getName().equalsIgnoreCase("trust") && player != null) - { - //requires exactly one parameter, the other player's name - if(args.length != 1) return false; - - //most trust commands use this helper method, it keeps them consistent - this.handleTrustCommand(player, ClaimPermission.Build, args[0]); - - return true; - } - - //transferclaim - else if(cmd.getName().equalsIgnoreCase("transferclaim") && player != null) - { - //which claim is the user in? - Claim claim = this.dataStore.getClaimAt(player.getLocation(), true, null); - if(claim == null) - { - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.TransferClaimMissing); - return true; - } - - //check additional permission for admin claims - if(claim.isAdminClaim() && !player.hasPermission("griefprevention.adminclaims")) + //abandontoplevelclaim + if (cmd.getName().equalsIgnoreCase("abandontoplevelclaim") && player != null) + { + return this.abandonClaimHandler(player, true); + } + + //ignoreclaims + if (cmd.getName().equalsIgnoreCase("ignoreclaims") && player != null) + { + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + + playerData.ignoreClaims = !playerData.ignoreClaims; + + //toggle ignore claims mode on or off + if (!playerData.ignoreClaims) + { + GriefPrevention.sendMessage(player, TextMode.Success, Messages.RespectingClaims); + } else + { + GriefPrevention.sendMessage(player, TextMode.Success, Messages.IgnoringClaims); + } + + return true; + } + + //abandonallclaims + else if (cmd.getName().equalsIgnoreCase("abandonallclaims") && player != null) + { + if (args.length != 0) return false; + + //count claims + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + int originalClaimCount = playerData.getClaims().size(); + + //check count + if (originalClaimCount == 0) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.YouHaveNoClaims); + return true; + } + + if (this.config_claims_abandonReturnRatio != 1.0D) + { + //adjust claim blocks + for (Claim claim : playerData.getClaims()) + { + playerData.setAccruedClaimBlocks(playerData.getAccruedClaimBlocks() - (int) Math.ceil((claim.getArea() * (1 - this.config_claims_abandonReturnRatio)))); + } + } + + + //delete them + this.dataStore.deleteClaimsForPlayer(player.getUniqueId(), false); + + //inform the player + int remainingBlocks = playerData.getRemainingClaimBlocks(); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.SuccessfulAbandon, String.valueOf(remainingBlocks)); + + //revert any current visualization + Visualization.Revert(player); + + return true; + } + + //restore nature + else if (cmd.getName().equalsIgnoreCase("restorenature") && player != null) + { + //change shovel mode + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + playerData.shovelMode = ShovelMode.RestoreNature; + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.RestoreNatureActivate); + return true; + } + + //restore nature aggressive mode + else if (cmd.getName().equalsIgnoreCase("restorenatureaggressive") && player != null) + { + //change shovel mode + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + playerData.shovelMode = ShovelMode.RestoreNatureAggressive; + GriefPrevention.sendMessage(player, TextMode.Warn, Messages.RestoreNatureAggressiveActivate); + return true; + } + + //restore nature fill mode + else if (cmd.getName().equalsIgnoreCase("restorenaturefill") && player != null) + { + //change shovel mode + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + playerData.shovelMode = ShovelMode.RestoreNatureFill; + + //set radius based on arguments + playerData.fillRadius = 2; + if (args.length > 0) + { + try + { + playerData.fillRadius = Integer.parseInt(args[0]); + } + catch (Exception exception) { } + } + + if (playerData.fillRadius < 0) playerData.fillRadius = 2; + + GriefPrevention.sendMessage(player, TextMode.Success, Messages.FillModeActive, String.valueOf(playerData.fillRadius)); + return true; + } + + //trust + else if (cmd.getName().equalsIgnoreCase("trust") && player != null) + { + //requires exactly one parameter, the other player's name + if (args.length != 1) return false; + + //most trust commands use this helper method, it keeps them consistent + this.handleTrustCommand(player, ClaimPermission.Build, args[0]); + + return true; + } + + //transferclaim + else if (cmd.getName().equalsIgnoreCase("transferclaim") && player != null) + { + //which claim is the user in? + Claim claim = this.dataStore.getClaimAt(player.getLocation(), true, null); + if (claim == null) + { + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.TransferClaimMissing); + return true; + } + + //check additional permission for admin claims + if (claim.isAdminClaim() && !player.hasPermission("griefprevention.adminclaims")) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.TransferClaimPermission); return true; } - - UUID newOwnerID = null; //no argument = make an admin claim - String ownerName = "admin"; - - if(args.length > 0) - { - OfflinePlayer targetPlayer = this.resolvePlayerByName(args[0]); - if(targetPlayer == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); - return true; - } - newOwnerID = targetPlayer.getUniqueId(); - ownerName = targetPlayer.getName(); - } - - //change ownerhsip - try - { - this.dataStore.changeClaimOwner(claim, newOwnerID); - } - catch(NoTransferException e) - { - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.TransferTopLevel); - return true; - } - - //confirm - GriefPrevention.sendMessage(player, TextMode.Success, Messages.TransferSuccess); - GriefPrevention.AddLogEntry(player.getName() + " transferred a claim at " + GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner()) + " to " + ownerName + ".", CustomLogEntryTypes.AdminActivity); - - return true; - } - - //trustlist - else if(cmd.getName().equalsIgnoreCase("trustlist") && player != null) - { - Claim claim = this.dataStore.getClaimAt(player.getLocation(), true, null); - - //if no claim here, error message - if(claim == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.TrustListNoClaim); - return true; - } - - //if no permission to manage permissions, error message - String errorMessage = claim.allowGrantPermission(player); - if(errorMessage != null) - { - GriefPrevention.sendMessage(player, TextMode.Err, errorMessage); - return true; - } - - //otherwise build a list of explicit permissions by permission level - //and send that to the player - ArrayList builders = new ArrayList(); - ArrayList containers = new ArrayList(); - ArrayList accessors = new ArrayList(); - ArrayList managers = new ArrayList(); - claim.getPermissions(builders, containers, accessors, managers); - - GriefPrevention.sendMessage(player, TextMode.Info, Messages.TrustListHeader); - - StringBuilder permissions = new StringBuilder(); - permissions.append(ChatColor.GOLD + ">"); - - if(managers.size() > 0) - { - for(int i = 0; i < managers.size(); i++) - permissions.append(this.trustEntryToPlayerName(managers.get(i)) + " "); - } - - player.sendMessage(permissions.toString()); - permissions = new StringBuilder(); - permissions.append(ChatColor.YELLOW + ">"); - - if(builders.size() > 0) - { - for(int i = 0; i < builders.size(); i++) - permissions.append(this.trustEntryToPlayerName(builders.get(i)) + " "); - } - - player.sendMessage(permissions.toString()); - permissions = new StringBuilder(); - permissions.append(ChatColor.GREEN + ">"); - - if(containers.size() > 0) - { - for(int i = 0; i < containers.size(); i++) - permissions.append(this.trustEntryToPlayerName(containers.get(i)) + " "); - } - - player.sendMessage(permissions.toString()); - permissions = new StringBuilder(); - permissions.append(ChatColor.BLUE + ">"); - - if(accessors.size() > 0) - { - for(int i = 0; i < accessors.size(); i++) - permissions.append(this.trustEntryToPlayerName(accessors.get(i)) + " "); - } - - player.sendMessage(permissions.toString()); - - player.sendMessage( - ChatColor.GOLD + this.dataStore.getMessage(Messages.Manage) + " " + - ChatColor.YELLOW + this.dataStore.getMessage(Messages.Build) + " " + - ChatColor.GREEN + this.dataStore.getMessage(Messages.Containers) + " " + - ChatColor.BLUE + this.dataStore.getMessage(Messages.Access)); - - if(claim.getSubclaimRestrictions()) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.HasSubclaimRestriction); - } - return true; - } - - //untrust or untrust [] - else if(cmd.getName().equalsIgnoreCase("untrust") && player != null) - { - //requires exactly one parameter, the other player's name - if(args.length != 1) return false; - - //determine which claim the player is standing in - Claim claim = this.dataStore.getClaimAt(player.getLocation(), true /*ignore height*/, null); - - //bracket any permissions - if(args[0].contains(".") && !args[0].startsWith("[") && !args[0].endsWith("]")) - { - args[0] = "[" + args[0] + "]"; - } - - //determine whether a single player or clearing permissions entirely - boolean clearPermissions = false; - OfflinePlayer otherPlayer = null; - if(args[0].equals("all")) - { - if(claim == null || claim.allowEdit(player) == null) - { - clearPermissions = true; - } - else - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.ClearPermsOwnerOnly); - return true; - } - } - - else - { - //validate player argument or group argument - if(!args[0].startsWith("[") || !args[0].endsWith("]")) - { - otherPlayer = this.resolvePlayerByName(args[0]); - if(!clearPermissions && otherPlayer == null && !args[0].equals("public")) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); - return true; - } - - //correct to proper casing - if(otherPlayer != null) - args[0] = otherPlayer.getName(); - } - } - - //if no claim here, apply changes to all his claims - if(claim == null) - { - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - - String idToDrop = args[0]; - if(otherPlayer != null) - { - idToDrop = otherPlayer.getUniqueId().toString(); - } + UUID newOwnerID = null; //no argument = make an admin claim + String ownerName = "admin"; - //calling event - TrustChangedEvent event = new TrustChangedEvent(player, playerData.getClaims(), null, false, idToDrop); - Bukkit.getPluginManager().callEvent(event); - - if (event.isCancelled()) { - return true; - } - - //dropping permissions - for(int i = 0; i < playerData.getClaims().size(); i++) - { - claim = playerData.getClaims().get(i); - - //if untrusting "all" drop all permissions - if(clearPermissions) - { - claim.clearPermissions(); - } - - //otherwise drop individual permissions - else - { - claim.dropPermission(idToDrop); - claim.managers.remove(idToDrop); - } - - //save changes - this.dataStore.saveClaim(claim); - } - - //beautify for output - if(args[0].equals("public")) - { - args[0] = "the public"; - } - - //confirmation message - if(!clearPermissions) - { - GriefPrevention.sendMessage(player, TextMode.Success, Messages.UntrustIndividualAllClaims, args[0]); - } - else - { - 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, Messages.NoPermissionTrust, claim.getOwnerName()); - return true; - } - else - { - //if clearing all - if(clearPermissions) - { - //requires owner - if(claim.allowEdit(player) != null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.UntrustAllOwnerOnly); - return true; - } - - //calling the event - TrustChangedEvent event = new TrustChangedEvent(player, claim, null, false, args[0]); - Bukkit.getPluginManager().callEvent(event); - - if (event.isCancelled()) { - return true; - } - - claim.clearPermissions(); - GriefPrevention.sendMessage(player, TextMode.Success, Messages.ClearPermissionsOneClaim); - } - - //otherwise individual permission drop - else - { - String idToDrop = args[0]; - if(otherPlayer != null) - { - idToDrop = otherPlayer.getUniqueId().toString(); - } - boolean targetIsManager = claim.managers.contains(idToDrop); - if(targetIsManager && claim.allowEdit(player) != null) //only claim owners can untrust managers - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.ManagersDontUntrustManagers, claim.getOwnerName()); - return true; - } - else - { - //calling the event - TrustChangedEvent event = new TrustChangedEvent(player, claim, null, false, idToDrop); - Bukkit.getPluginManager().callEvent(event); - - if (event.isCancelled()) { - return true; - } - - claim.dropPermission(idToDrop); - claim.managers.remove(idToDrop); - - //beautify for output - if(args[0].equals("public")) - { - args[0] = "the public"; - } - - GriefPrevention.sendMessage(player, TextMode.Success, Messages.UntrustIndividualSingleClaim, args[0]); - } - } - - //save changes - this.dataStore.saveClaim(claim); - } - - return true; - } - - //accesstrust - else if(cmd.getName().equalsIgnoreCase("accesstrust") && player != null) - { - //requires exactly one parameter, the other player's name - if(args.length != 1) return false; - - this.handleTrustCommand(player, ClaimPermission.Access, args[0]); - - return true; - } - - //containertrust - else if(cmd.getName().equalsIgnoreCase("containertrust") && player != null) - { - //requires exactly one parameter, the other player's name - if(args.length != 1) return false; - - this.handleTrustCommand(player, ClaimPermission.Inventory, args[0]); - - return true; - } - - //permissiontrust - else if(cmd.getName().equalsIgnoreCase("permissiontrust") && player != null) - { - //requires exactly one parameter, the other player's name - if(args.length != 1) return false; - - this.handleTrustCommand(player, null, args[0]); //null indicates permissiontrust to the helper method - - return true; - } - - //restrictsubclaim - else if (cmd.getName().equalsIgnoreCase("restrictsubclaim") && player != null) - { - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(player.getLocation(), true, playerData.lastClaim); - if(claim == null || claim.parent == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.StandInSubclaim); - return true; - } - - // If player has /ignoreclaims on, continue - // If admin claim, fail if this user is not an admin - // If not an admin claim, fail if this user is not the owner - if(!playerData.ignoreClaims && (claim.isAdminClaim() ? !player.hasPermission("griefprevention.adminclaims") : !player.getUniqueId().equals(claim.parent.ownerID))) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.OnlyOwnersModifyClaims, claim.getOwnerName()); - return true; - } - - if(claim.getSubclaimRestrictions()) - { - claim.setSubclaimRestrictions(false); - GriefPrevention.sendMessage(player, TextMode.Success, Messages.SubclaimUnrestricted); - } - else - { - claim.setSubclaimRestrictions(true); - GriefPrevention.sendMessage(player, TextMode.Success, Messages.SubclaimRestricted); - } - this.dataStore.saveClaim(claim); - return true; - } - - //buyclaimblocks - else if(cmd.getName().equalsIgnoreCase("buyclaimblocks") && player != null) - { - //if economy is disabled, don't do anything - if(GriefPrevention.economy == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.BuySellNotConfigured); - return true; - } - - if(!player.hasPermission("griefprevention.buysellclaimblocks")) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoPermissionForCommand); - return true; - } - - //if purchase disabled, send error message - if(GriefPrevention.instance.config_economy_claimBlocksPurchaseCost == 0) - { - 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, Messages.BlockPurchaseCost, String.valueOf(GriefPrevention.instance.config_economy_claimBlocksPurchaseCost), String.valueOf(GriefPrevention.economy.getBalance(player.getName()))); - return false; - } - - else - { - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - - //try to parse number of blocks - int blockCount; - try - { - blockCount = Integer.parseInt(args[0]); - } - catch(NumberFormatException numberFormatException) - { - return false; //causes usage to be displayed - } - - if(blockCount <= 0) - { - return false; - } - - //if the player can't afford his purchase, send error message - double balance = economy.getBalance(player.getName()); - double totalCost = blockCount * GriefPrevention.instance.config_economy_claimBlocksPurchaseCost; - if(totalCost > balance) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.InsufficientFunds, String.valueOf(totalCost), String.valueOf(balance)); - } - - //otherwise carry out transaction - else - { - int newBonusClaimBlocks = playerData.getBonusClaimBlocks() + blockCount; - - //if the player is going to reach max bonus limit, send error message - int bonusBlocksLimit = GriefPrevention.instance.config_economy_claimBlocksMaxBonus; - if (bonusBlocksLimit != 0 && newBonusClaimBlocks > bonusBlocksLimit) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.MaxBonusReached, String.valueOf(blockCount), String.valueOf(bonusBlocksLimit)); - return true; - } - - //withdraw cost - economy.withdrawPlayer(player.getName(), totalCost); - - //add blocks - playerData.setBonusClaimBlocks(playerData.getBonusClaimBlocks() + blockCount); - this.dataStore.savePlayerData(player.getUniqueId(), playerData); - - //inform player - GriefPrevention.sendMessage(player, TextMode.Success, Messages.PurchaseConfirmation, String.valueOf(totalCost), String.valueOf(playerData.getRemainingClaimBlocks())); - } - - return true; - } - } - - //sellclaimblocks - else if(cmd.getName().equalsIgnoreCase("sellclaimblocks") && player != null) - { - //if economy is disabled, don't do anything - if(GriefPrevention.economy == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.BuySellNotConfigured); - return true; - } - - if(!player.hasPermission("griefprevention.buysellclaimblocks")) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoPermissionForCommand); - return true; - } - - //if disabled, error message - if(GriefPrevention.instance.config_economy_claimBlocksSellValue == 0) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.OnlyPurchaseBlocks); - return true; - } - - //load player data - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - int availableBlocks = playerData.getRemainingClaimBlocks(); - - //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, Messages.BlockSaleValue, String.valueOf(GriefPrevention.instance.config_economy_claimBlocksSellValue), String.valueOf(availableBlocks)); - return false; - } - - //parse number of blocks - int blockCount; - try - { - blockCount = Integer.parseInt(args[0]); - } - catch(NumberFormatException numberFormatException) - { - return false; //causes usage to be displayed - } - - if(blockCount <= 0) - { - return false; - } - - //if he doesn't have enough blocks, tell him so - if(blockCount > availableBlocks) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.NotEnoughBlocksForSale); - } - - //otherwise carry out the transaction - else - { - //compute value and deposit it - double totalValue = blockCount * GriefPrevention.instance.config_economy_claimBlocksSellValue; - economy.depositPlayer(player.getName(), totalValue); - - //subtract blocks - playerData.setBonusClaimBlocks(playerData.getBonusClaimBlocks() - blockCount); - this.dataStore.savePlayerData(player.getUniqueId(), playerData); - - //inform player - GriefPrevention.sendMessage(player, TextMode.Success, Messages.BlockSaleConfirmation, String.valueOf(totalValue), String.valueOf(playerData.getRemainingClaimBlocks())); - } - - return true; - } - - //adminclaims - else if(cmd.getName().equalsIgnoreCase("adminclaims") && player != null) - { - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - playerData.shovelMode = ShovelMode.Admin; - GriefPrevention.sendMessage(player, TextMode.Success, Messages.AdminClaimsMode); - - return true; - } - - //basicclaims - else if(cmd.getName().equalsIgnoreCase("basicclaims") && player != null) - { - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - playerData.shovelMode = ShovelMode.Basic; - playerData.claimSubdividing = null; - GriefPrevention.sendMessage(player, TextMode.Success, Messages.BasicClaimsMode); - - return true; - } - - //subdivideclaims - else if(cmd.getName().equalsIgnoreCase("subdivideclaims") && player != null) - { - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - playerData.shovelMode = ShovelMode.Subdivide; - playerData.claimSubdividing = null; - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SubdivisionMode); - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SubdivisionVideo2, DataStore.SUBDIVISION_VIDEO_URL); - - return true; - } - - //deleteclaim - else if(cmd.getName().equalsIgnoreCase("deleteclaim") && player != null) - { - //determine which claim the player is standing in - Claim claim = this.dataStore.getClaimAt(player.getLocation(), true /*ignore height*/, null); - - if(claim == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.DeleteClaimMissing); - } - - else - { - //deleting an admin claim additionally requires the adminclaims permission - if(!claim.isAdminClaim() || player.hasPermission("griefprevention.adminclaims")) - { - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - if(claim.children.size() > 0 && !playerData.warnedAboutMajorDeletion) - { - GriefPrevention.sendMessage(player, TextMode.Warn, Messages.DeletionSubdivisionWarning); - playerData.warnedAboutMajorDeletion = true; - } - else - { - claim.removeSurfaceFluids(null); - this.dataStore.deleteClaim(claim, true, true); - - //if in a creative mode world, /restorenature the claim - if(GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner()) || GriefPrevention.instance.config_claims_survivalAutoNatureRestoration) - { - GriefPrevention.instance.restoreClaim(claim, 0); - } - - GriefPrevention.sendMessage(player, TextMode.Success, Messages.DeleteSuccess); - GriefPrevention.AddLogEntry(player.getName() + " deleted " + claim.getOwnerName() + "'s claim at " + GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner()), CustomLogEntryTypes.AdminActivity); - - //revert any current visualization - Visualization.Revert(player); - - playerData.warnedAboutMajorDeletion = false; - } - } - else - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.CantDeleteAdminClaim); - } - } - - return true; - } - - else if(cmd.getName().equalsIgnoreCase("claimexplosions") && player != null) - { - //determine which claim the player is standing in - Claim claim = this.dataStore.getClaimAt(player.getLocation(), true /*ignore height*/, null); - - if(claim == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.DeleteClaimMissing); - } - - else - { - String noBuildReason = claim.allowBuild(player, Material.STONE); - if(noBuildReason != null) - { - GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason); - return true; - } - - if(claim.areExplosivesAllowed) - { - claim.areExplosivesAllowed = false; - GriefPrevention.sendMessage(player, TextMode.Success, Messages.ExplosivesDisabled); - } - else - { - claim.areExplosivesAllowed = true; - GriefPrevention.sendMessage(player, TextMode.Success, Messages.ExplosivesEnabled); - } - } - - return true; - } - - //deleteallclaims - else if(cmd.getName().equalsIgnoreCase("deleteallclaims")) - { - //requires exactly one parameter, the other player's name - if(args.length != 1) return false; - - //try to find that player - OfflinePlayer otherPlayer = this.resolvePlayerByName(args[0]); - if(otherPlayer == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); - return true; - } - - //delete all that player's claims - this.dataStore.deleteClaimsForPlayer(otherPlayer.getUniqueId(), true); - - GriefPrevention.sendMessage(player, TextMode.Success, Messages.DeleteAllSuccess, otherPlayer.getName()); - if(player != null) - { - GriefPrevention.AddLogEntry(player.getName() + " deleted all claims belonging to " + otherPlayer.getName() + ".", CustomLogEntryTypes.AdminActivity); - - //revert any current visualization - Visualization.Revert(player); - } - - return true; - } - - else if(cmd.getName().equalsIgnoreCase("deleteclaimsinworld")) - { - //must be executed at the console - if(player != null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.ConsoleOnlyCommand); - return true; - } - - //requires exactly one parameter, the world name - if(args.length != 1) return false; - - //try to find the specified world - World world = Bukkit.getServer().getWorld(args[0]); - if(world == null) + if (args.length > 0) { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.WorldNotFound); + OfflinePlayer targetPlayer = this.resolvePlayerByName(args[0]); + if (targetPlayer == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); + return true; + } + newOwnerID = targetPlayer.getUniqueId(); + ownerName = targetPlayer.getName(); + } + + //change ownerhsip + try + { + this.dataStore.changeClaimOwner(claim, newOwnerID); + } + catch (NoTransferException e) + { + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.TransferTopLevel); return true; } - - //delete all claims in that world - this.dataStore.deleteClaimsInWorld(world, true); - GriefPrevention.AddLogEntry("Deleted all claims in world: " + world.getName() + ".", CustomLogEntryTypes.AdminActivity); + + //confirm + GriefPrevention.sendMessage(player, TextMode.Success, Messages.TransferSuccess); + GriefPrevention.AddLogEntry(player.getName() + " transferred a claim at " + GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner()) + " to " + ownerName + ".", CustomLogEntryTypes.AdminActivity); + return true; } - - else if(cmd.getName().equalsIgnoreCase("deleteuserclaimsinworld")) + + //trustlist + else if (cmd.getName().equalsIgnoreCase("trustlist") && player != null) + { + Claim claim = this.dataStore.getClaimAt(player.getLocation(), true, null); + + //if no claim here, error message + if (claim == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.TrustListNoClaim); + return true; + } + + //if no permission to manage permissions, error message + String errorMessage = claim.allowGrantPermission(player); + if (errorMessage != null) + { + GriefPrevention.sendMessage(player, TextMode.Err, errorMessage); + return true; + } + + //otherwise build a list of explicit permissions by permission level + //and send that to the player + ArrayList builders = new ArrayList(); + ArrayList containers = new ArrayList(); + ArrayList accessors = new ArrayList(); + ArrayList managers = new ArrayList(); + claim.getPermissions(builders, containers, accessors, managers); + + GriefPrevention.sendMessage(player, TextMode.Info, Messages.TrustListHeader); + + StringBuilder permissions = new StringBuilder(); + permissions.append(ChatColor.GOLD + ">"); + + if (managers.size() > 0) + { + for (int i = 0; i < managers.size(); i++) + permissions.append(this.trustEntryToPlayerName(managers.get(i)) + " "); + } + + player.sendMessage(permissions.toString()); + permissions = new StringBuilder(); + permissions.append(ChatColor.YELLOW + ">"); + + if (builders.size() > 0) + { + for (int i = 0; i < builders.size(); i++) + permissions.append(this.trustEntryToPlayerName(builders.get(i)) + " "); + } + + player.sendMessage(permissions.toString()); + permissions = new StringBuilder(); + permissions.append(ChatColor.GREEN + ">"); + + if (containers.size() > 0) + { + for (int i = 0; i < containers.size(); i++) + permissions.append(this.trustEntryToPlayerName(containers.get(i)) + " "); + } + + player.sendMessage(permissions.toString()); + permissions = new StringBuilder(); + permissions.append(ChatColor.BLUE + ">"); + + if (accessors.size() > 0) + { + for (int i = 0; i < accessors.size(); i++) + permissions.append(this.trustEntryToPlayerName(accessors.get(i)) + " "); + } + + player.sendMessage(permissions.toString()); + + player.sendMessage( + ChatColor.GOLD + this.dataStore.getMessage(Messages.Manage) + " " + + ChatColor.YELLOW + this.dataStore.getMessage(Messages.Build) + " " + + ChatColor.GREEN + this.dataStore.getMessage(Messages.Containers) + " " + + ChatColor.BLUE + this.dataStore.getMessage(Messages.Access)); + + if (claim.getSubclaimRestrictions()) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.HasSubclaimRestriction); + } + + return true; + } + + //untrust or untrust [] + else if (cmd.getName().equalsIgnoreCase("untrust") && player != null) + { + //requires exactly one parameter, the other player's name + if (args.length != 1) return false; + + //determine which claim the player is standing in + Claim claim = this.dataStore.getClaimAt(player.getLocation(), true /*ignore height*/, null); + + //bracket any permissions + if (args[0].contains(".") && !args[0].startsWith("[") && !args[0].endsWith("]")) + { + args[0] = "[" + args[0] + "]"; + } + + //determine whether a single player or clearing permissions entirely + boolean clearPermissions = false; + OfflinePlayer otherPlayer = null; + if (args[0].equals("all")) + { + if (claim == null || claim.allowEdit(player) == null) + { + clearPermissions = true; + } else + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.ClearPermsOwnerOnly); + return true; + } + } else + { + //validate player argument or group argument + if (!args[0].startsWith("[") || !args[0].endsWith("]")) + { + otherPlayer = this.resolvePlayerByName(args[0]); + if (!clearPermissions && otherPlayer == null && !args[0].equals("public")) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); + return true; + } + + //correct to proper casing + if (otherPlayer != null) + args[0] = otherPlayer.getName(); + } + } + + //if no claim here, apply changes to all his claims + if (claim == null) + { + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + + String idToDrop = args[0]; + if (otherPlayer != null) + { + idToDrop = otherPlayer.getUniqueId().toString(); + } + + //calling event + TrustChangedEvent event = new TrustChangedEvent(player, playerData.getClaims(), null, false, idToDrop); + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) + { + return true; + } + + //dropping permissions + for (int i = 0; i < playerData.getClaims().size(); i++) + { + claim = playerData.getClaims().get(i); + + //if untrusting "all" drop all permissions + if (clearPermissions) + { + claim.clearPermissions(); + } + + //otherwise drop individual permissions + else + { + claim.dropPermission(idToDrop); + claim.managers.remove(idToDrop); + } + + //save changes + this.dataStore.saveClaim(claim); + } + + //beautify for output + if (args[0].equals("public")) + { + args[0] = "the public"; + } + + //confirmation message + if (!clearPermissions) + { + GriefPrevention.sendMessage(player, TextMode.Success, Messages.UntrustIndividualAllClaims, args[0]); + } else + { + 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, Messages.NoPermissionTrust, claim.getOwnerName()); + return true; + } else + { + //if clearing all + if (clearPermissions) + { + //requires owner + if (claim.allowEdit(player) != null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.UntrustAllOwnerOnly); + return true; + } + + //calling the event + TrustChangedEvent event = new TrustChangedEvent(player, claim, null, false, args[0]); + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) + { + return true; + } + + claim.clearPermissions(); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.ClearPermissionsOneClaim); + } + + //otherwise individual permission drop + else + { + String idToDrop = args[0]; + if (otherPlayer != null) + { + idToDrop = otherPlayer.getUniqueId().toString(); + } + boolean targetIsManager = claim.managers.contains(idToDrop); + if (targetIsManager && claim.allowEdit(player) != null) //only claim owners can untrust managers + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.ManagersDontUntrustManagers, claim.getOwnerName()); + return true; + } else + { + //calling the event + TrustChangedEvent event = new TrustChangedEvent(player, claim, null, false, idToDrop); + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) + { + return true; + } + + claim.dropPermission(idToDrop); + claim.managers.remove(idToDrop); + + //beautify for output + if (args[0].equals("public")) + { + args[0] = "the public"; + } + + GriefPrevention.sendMessage(player, TextMode.Success, Messages.UntrustIndividualSingleClaim, args[0]); + } + } + + //save changes + this.dataStore.saveClaim(claim); + } + + return true; + } + + //accesstrust + else if (cmd.getName().equalsIgnoreCase("accesstrust") && player != null) + { + //requires exactly one parameter, the other player's name + if (args.length != 1) return false; + + this.handleTrustCommand(player, ClaimPermission.Access, args[0]); + + return true; + } + + //containertrust + else if (cmd.getName().equalsIgnoreCase("containertrust") && player != null) + { + //requires exactly one parameter, the other player's name + if (args.length != 1) return false; + + this.handleTrustCommand(player, ClaimPermission.Inventory, args[0]); + + return true; + } + + //permissiontrust + else if (cmd.getName().equalsIgnoreCase("permissiontrust") && player != null) + { + //requires exactly one parameter, the other player's name + if (args.length != 1) return false; + + this.handleTrustCommand(player, null, args[0]); //null indicates permissiontrust to the helper method + + return true; + } + + //restrictsubclaim + else if (cmd.getName().equalsIgnoreCase("restrictsubclaim") && player != null) + { + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = this.dataStore.getClaimAt(player.getLocation(), true, playerData.lastClaim); + if (claim == null || claim.parent == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.StandInSubclaim); + return true; + } + + // If player has /ignoreclaims on, continue + // If admin claim, fail if this user is not an admin + // If not an admin claim, fail if this user is not the owner + if (!playerData.ignoreClaims && (claim.isAdminClaim() ? !player.hasPermission("griefprevention.adminclaims") : !player.getUniqueId().equals(claim.parent.ownerID))) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.OnlyOwnersModifyClaims, claim.getOwnerName()); + return true; + } + + if (claim.getSubclaimRestrictions()) + { + claim.setSubclaimRestrictions(false); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.SubclaimUnrestricted); + } else + { + claim.setSubclaimRestrictions(true); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.SubclaimRestricted); + } + this.dataStore.saveClaim(claim); + return true; + } + + //buyclaimblocks + else if (cmd.getName().equalsIgnoreCase("buyclaimblocks") && player != null) + { + //if economy is disabled, don't do anything + if (GriefPrevention.economy == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.BuySellNotConfigured); + return true; + } + + if (!player.hasPermission("griefprevention.buysellclaimblocks")) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoPermissionForCommand); + return true; + } + + //if purchase disabled, send error message + if (GriefPrevention.instance.config_economy_claimBlocksPurchaseCost == 0) + { + 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, Messages.BlockPurchaseCost, String.valueOf(GriefPrevention.instance.config_economy_claimBlocksPurchaseCost), String.valueOf(GriefPrevention.economy.getBalance(player.getName()))); + return false; + } else + { + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + + //try to parse number of blocks + int blockCount; + try + { + blockCount = Integer.parseInt(args[0]); + } + catch (NumberFormatException numberFormatException) + { + return false; //causes usage to be displayed + } + + if (blockCount <= 0) + { + return false; + } + + //if the player can't afford his purchase, send error message + double balance = economy.getBalance(player.getName()); + double totalCost = blockCount * GriefPrevention.instance.config_economy_claimBlocksPurchaseCost; + if (totalCost > balance) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.InsufficientFunds, String.valueOf(totalCost), String.valueOf(balance)); + } + + //otherwise carry out transaction + else + { + int newBonusClaimBlocks = playerData.getBonusClaimBlocks() + blockCount; + + //if the player is going to reach max bonus limit, send error message + int bonusBlocksLimit = GriefPrevention.instance.config_economy_claimBlocksMaxBonus; + if (bonusBlocksLimit != 0 && newBonusClaimBlocks > bonusBlocksLimit) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.MaxBonusReached, String.valueOf(blockCount), String.valueOf(bonusBlocksLimit)); + return true; + } + + //withdraw cost + economy.withdrawPlayer(player.getName(), totalCost); + + //add blocks + playerData.setBonusClaimBlocks(playerData.getBonusClaimBlocks() + blockCount); + this.dataStore.savePlayerData(player.getUniqueId(), playerData); + + //inform player + GriefPrevention.sendMessage(player, TextMode.Success, Messages.PurchaseConfirmation, String.valueOf(totalCost), String.valueOf(playerData.getRemainingClaimBlocks())); + } + + return true; + } + } + + //sellclaimblocks + else if (cmd.getName().equalsIgnoreCase("sellclaimblocks") && player != null) + { + //if economy is disabled, don't do anything + if (GriefPrevention.economy == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.BuySellNotConfigured); + return true; + } + + if (!player.hasPermission("griefprevention.buysellclaimblocks")) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoPermissionForCommand); + return true; + } + + //if disabled, error message + if (GriefPrevention.instance.config_economy_claimBlocksSellValue == 0) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.OnlyPurchaseBlocks); + return true; + } + + //load player data + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + int availableBlocks = playerData.getRemainingClaimBlocks(); + + //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, Messages.BlockSaleValue, String.valueOf(GriefPrevention.instance.config_economy_claimBlocksSellValue), String.valueOf(availableBlocks)); + return false; + } + + //parse number of blocks + int blockCount; + try + { + blockCount = Integer.parseInt(args[0]); + } + catch (NumberFormatException numberFormatException) + { + return false; //causes usage to be displayed + } + + if (blockCount <= 0) + { + return false; + } + + //if he doesn't have enough blocks, tell him so + if (blockCount > availableBlocks) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NotEnoughBlocksForSale); + } + + //otherwise carry out the transaction + else + { + //compute value and deposit it + double totalValue = blockCount * GriefPrevention.instance.config_economy_claimBlocksSellValue; + economy.depositPlayer(player.getName(), totalValue); + + //subtract blocks + playerData.setBonusClaimBlocks(playerData.getBonusClaimBlocks() - blockCount); + this.dataStore.savePlayerData(player.getUniqueId(), playerData); + + //inform player + GriefPrevention.sendMessage(player, TextMode.Success, Messages.BlockSaleConfirmation, String.valueOf(totalValue), String.valueOf(playerData.getRemainingClaimBlocks())); + } + + return true; + } + + //adminclaims + else if (cmd.getName().equalsIgnoreCase("adminclaims") && player != null) + { + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + playerData.shovelMode = ShovelMode.Admin; + GriefPrevention.sendMessage(player, TextMode.Success, Messages.AdminClaimsMode); + + return true; + } + + //basicclaims + else if (cmd.getName().equalsIgnoreCase("basicclaims") && player != null) + { + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + playerData.shovelMode = ShovelMode.Basic; + playerData.claimSubdividing = null; + GriefPrevention.sendMessage(player, TextMode.Success, Messages.BasicClaimsMode); + + return true; + } + + //subdivideclaims + else if (cmd.getName().equalsIgnoreCase("subdivideclaims") && player != null) + { + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + playerData.shovelMode = ShovelMode.Subdivide; + playerData.claimSubdividing = null; + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SubdivisionMode); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SubdivisionVideo2, DataStore.SUBDIVISION_VIDEO_URL); + + return true; + } + + //deleteclaim + else if (cmd.getName().equalsIgnoreCase("deleteclaim") && player != null) + { + //determine which claim the player is standing in + Claim claim = this.dataStore.getClaimAt(player.getLocation(), true /*ignore height*/, null); + + if (claim == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.DeleteClaimMissing); + } else + { + //deleting an admin claim additionally requires the adminclaims permission + if (!claim.isAdminClaim() || player.hasPermission("griefprevention.adminclaims")) + { + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + if (claim.children.size() > 0 && !playerData.warnedAboutMajorDeletion) + { + GriefPrevention.sendMessage(player, TextMode.Warn, Messages.DeletionSubdivisionWarning); + playerData.warnedAboutMajorDeletion = true; + } else + { + claim.removeSurfaceFluids(null); + this.dataStore.deleteClaim(claim, true, true); + + //if in a creative mode world, /restorenature the claim + if (GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner()) || GriefPrevention.instance.config_claims_survivalAutoNatureRestoration) + { + GriefPrevention.instance.restoreClaim(claim, 0); + } + + GriefPrevention.sendMessage(player, TextMode.Success, Messages.DeleteSuccess); + GriefPrevention.AddLogEntry(player.getName() + " deleted " + claim.getOwnerName() + "'s claim at " + GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner()), CustomLogEntryTypes.AdminActivity); + + //revert any current visualization + Visualization.Revert(player); + + playerData.warnedAboutMajorDeletion = false; + } + } else + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.CantDeleteAdminClaim); + } + } + + return true; + } else if (cmd.getName().equalsIgnoreCase("claimexplosions") && player != null) + { + //determine which claim the player is standing in + Claim claim = this.dataStore.getClaimAt(player.getLocation(), true /*ignore height*/, null); + + if (claim == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.DeleteClaimMissing); + } else + { + String noBuildReason = claim.allowBuild(player, Material.STONE); + if (noBuildReason != null) + { + GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason); + return true; + } + + if (claim.areExplosivesAllowed) + { + claim.areExplosivesAllowed = false; + GriefPrevention.sendMessage(player, TextMode.Success, Messages.ExplosivesDisabled); + } else + { + claim.areExplosivesAllowed = true; + GriefPrevention.sendMessage(player, TextMode.Success, Messages.ExplosivesEnabled); + } + } + + return true; + } + + //deleteallclaims + else if (cmd.getName().equalsIgnoreCase("deleteallclaims")) + { + //requires exactly one parameter, the other player's name + if (args.length != 1) return false; + + //try to find that player + OfflinePlayer otherPlayer = this.resolvePlayerByName(args[0]); + if (otherPlayer == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); + return true; + } + + //delete all that player's claims + this.dataStore.deleteClaimsForPlayer(otherPlayer.getUniqueId(), true); + + GriefPrevention.sendMessage(player, TextMode.Success, Messages.DeleteAllSuccess, otherPlayer.getName()); + if (player != null) + { + GriefPrevention.AddLogEntry(player.getName() + " deleted all claims belonging to " + otherPlayer.getName() + ".", CustomLogEntryTypes.AdminActivity); + + //revert any current visualization + Visualization.Revert(player); + } + + return true; + } else if (cmd.getName().equalsIgnoreCase("deleteclaimsinworld")) { //must be executed at the console - if(player != null) + if (player != null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.ConsoleOnlyCommand); return true; } - + //requires exactly one parameter, the world name - if(args.length != 1) return false; - + if (args.length != 1) return false; + //try to find the specified world World world = Bukkit.getServer().getWorld(args[0]); - if(world == null) + if (world == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.WorldNotFound); return true; } - + + //delete all claims in that world + this.dataStore.deleteClaimsInWorld(world, true); + GriefPrevention.AddLogEntry("Deleted all claims in world: " + world.getName() + ".", CustomLogEntryTypes.AdminActivity); + return true; + } else if (cmd.getName().equalsIgnoreCase("deleteuserclaimsinworld")) + { + //must be executed at the console + if (player != null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.ConsoleOnlyCommand); + return true; + } + + //requires exactly one parameter, the world name + if (args.length != 1) return false; + + //try to find the specified world + World world = Bukkit.getServer().getWorld(args[0]); + if (world == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.WorldNotFound); + return true; + } + //delete all USER claims in that world this.dataStore.deleteClaimsInWorld(world, false); GriefPrevention.AddLogEntry("Deleted all user claims in world: " + world.getName() + ".", CustomLogEntryTypes.AdminActivity); return true; } - - //claimbook - else if(cmd.getName().equalsIgnoreCase("claimbook")) + + //claimbook + else if (cmd.getName().equalsIgnoreCase("claimbook")) { //requires one parameter - if(args.length != 1) return false; - + if (args.length != 1) return false; + //try to find the specified player Player otherPlayer = this.getServer().getPlayer(args[0]); - if(otherPlayer == null) + if (otherPlayer == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); return true; - } - else + } else { WelcomeTask task = new WelcomeTask(otherPlayer); task.run(); return true; } } - - //claimslist or claimslist - else if(cmd.getName().equalsIgnoreCase("claimslist")) - { - //at most one parameter - if(args.length > 1) return false; - - //player whose claims will be listed - OfflinePlayer otherPlayer; - - //if another player isn't specified, assume current player - if(args.length < 1) - { - if(player != null) - otherPlayer = player; - else - return false; - } - - //otherwise if no permission to delve into another player's claims data - else if(player != null && !player.hasPermission("griefprevention.claimslistother")) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.ClaimsListNoPermission); - return true; - } - - //otherwise try to find the specified player - else - { - otherPlayer = this.resolvePlayerByName(args[0]); - if(otherPlayer == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); - return true; - } - } - - //load the target player's data - PlayerData playerData = this.dataStore.getPlayerData(otherPlayer.getUniqueId()); - Vector claims = playerData.getClaims(); - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.StartBlockMath, - String.valueOf(playerData.getAccruedClaimBlocks()), - String.valueOf((playerData.getBonusClaimBlocks() + this.dataStore.getGroupBonusBlocks(otherPlayer.getUniqueId()))), - String.valueOf((playerData.getAccruedClaimBlocks() + playerData.getBonusClaimBlocks() + this.dataStore.getGroupBonusBlocks(otherPlayer.getUniqueId())))); - if(claims.size() > 0) - { - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.ClaimsListHeader); - for(int i = 0; i < playerData.getClaims().size(); i++) - { - Claim claim = playerData.getClaims().get(i); - GriefPrevention.sendMessage(player, TextMode.Instr, getfriendlyLocationString(claim.getLesserBoundaryCorner()) + this.dataStore.getMessage(Messages.ContinueBlockMath, String.valueOf(claim.getArea()))); - } - - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.EndBlockMath, String.valueOf(playerData.getRemainingClaimBlocks())); - } - - //drop the data we just loaded, if the player isn't online - if(!otherPlayer.isOnline()) - this.dataStore.clearCachedPlayerData(otherPlayer.getUniqueId()); - - return true; - } - - //adminclaimslist - else if(cmd.getName().equalsIgnoreCase("adminclaimslist")) + + //claimslist or claimslist + else if (cmd.getName().equalsIgnoreCase("claimslist")) + { + //at most one parameter + if (args.length > 1) return false; + + //player whose claims will be listed + OfflinePlayer otherPlayer; + + //if another player isn't specified, assume current player + if (args.length < 1) + { + if (player != null) + otherPlayer = player; + else + return false; + } + + //otherwise if no permission to delve into another player's claims data + else if (player != null && !player.hasPermission("griefprevention.claimslistother")) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.ClaimsListNoPermission); + return true; + } + + //otherwise try to find the specified player + else + { + otherPlayer = this.resolvePlayerByName(args[0]); + if (otherPlayer == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); + return true; + } + } + + //load the target player's data + PlayerData playerData = this.dataStore.getPlayerData(otherPlayer.getUniqueId()); + Vector claims = playerData.getClaims(); + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.StartBlockMath, + String.valueOf(playerData.getAccruedClaimBlocks()), + String.valueOf((playerData.getBonusClaimBlocks() + this.dataStore.getGroupBonusBlocks(otherPlayer.getUniqueId()))), + String.valueOf((playerData.getAccruedClaimBlocks() + playerData.getBonusClaimBlocks() + this.dataStore.getGroupBonusBlocks(otherPlayer.getUniqueId())))); + if (claims.size() > 0) + { + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.ClaimsListHeader); + for (int i = 0; i < playerData.getClaims().size(); i++) + { + Claim claim = playerData.getClaims().get(i); + GriefPrevention.sendMessage(player, TextMode.Instr, getfriendlyLocationString(claim.getLesserBoundaryCorner()) + this.dataStore.getMessage(Messages.ContinueBlockMath, String.valueOf(claim.getArea()))); + } + + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.EndBlockMath, String.valueOf(playerData.getRemainingClaimBlocks())); + } + + //drop the data we just loaded, if the player isn't online + if (!otherPlayer.isOnline()) + this.dataStore.clearCachedPlayerData(otherPlayer.getUniqueId()); + + return true; + } + + //adminclaimslist + else if (cmd.getName().equalsIgnoreCase("adminclaimslist")) { //find admin claims Vector claims = new Vector(); - for(Claim claim : this.dataStore.claims) + for (Claim claim : this.dataStore.claims) { - if(claim.ownerID == null) //admin claim + if (claim.ownerID == null) //admin claim { claims.add(claim); } } - if(claims.size() > 0) + if (claims.size() > 0) { GriefPrevention.sendMessage(player, TextMode.Instr, Messages.ClaimsListHeader); - for(int i = 0; i < claims.size(); i++) + for (int i = 0; i < claims.size(); i++) { Claim claim = claims.get(i); GriefPrevention.sendMessage(player, TextMode.Instr, getfriendlyLocationString(claim.getLesserBoundaryCorner())); @@ -2255,140 +2212,141 @@ public class GriefPrevention extends JavaPlugin return true; } - - //unlockItems - else if(cmd.getName().equalsIgnoreCase("unlockdrops") && player != null) - { - PlayerData playerData; - if (player.hasPermission("griefprevention.unlockothersdrops") && args.length == 1) - { - Player otherPlayer = Bukkit.getPlayer(args[0]); - if (otherPlayer == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); - return true; - } + //unlockItems + else if (cmd.getName().equalsIgnoreCase("unlockdrops") && player != null) + { + PlayerData playerData; - playerData = this.dataStore.getPlayerData(otherPlayer.getUniqueId()); - GriefPrevention.sendMessage(player, TextMode.Success, Messages.DropUnlockOthersConfirmation, otherPlayer.getName()); - } - else - { - playerData = this.dataStore.getPlayerData(player.getUniqueId()); - GriefPrevention.sendMessage(player, TextMode.Success, Messages.DropUnlockConfirmation); - } + if (player.hasPermission("griefprevention.unlockothersdrops") && args.length == 1) + { + Player otherPlayer = Bukkit.getPlayer(args[0]); + if (otherPlayer == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); + return true; + } - playerData.dropsAreUnlocked = true; - - return true; - } - - //deletealladminclaims - else if(player != null && cmd.getName().equalsIgnoreCase("deletealladminclaims")) - { - if(!player.hasPermission("griefprevention.deleteclaims")) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoDeletePermission); - return true; - } - - //delete all admin claims - this.dataStore.deleteClaimsForPlayer(null, true); //null for owner id indicates an administrative claim - - GriefPrevention.sendMessage(player, TextMode.Success, Messages.AllAdminDeleted); - if(player != null) - { - GriefPrevention.AddLogEntry(player.getName() + " deleted all administrative claims.", CustomLogEntryTypes.AdminActivity); - - //revert any current visualization - Visualization.Revert(player); - } - - return true; - } - - //adjustbonusclaimblocks or [] amount - else if(cmd.getName().equalsIgnoreCase("adjustbonusclaimblocks")) - { - //requires exactly two parameters, the other player or group's name and the adjustment - if(args.length != 2) return false; - - //parse the adjustment amount - int adjustment; - try - { - adjustment = Integer.parseInt(args[1]); - } - catch(NumberFormatException numberFormatException) - { - 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; - try - { - UUID playerID = UUID.fromString(args[0]); - targetPlayer = this.getServer().getOfflinePlayer(playerID); - - } - catch(IllegalArgumentException e) - { - targetPlayer = this.resolvePlayerByName(args[0]); - } - - if(targetPlayer == null) + playerData = this.dataStore.getPlayerData(otherPlayer.getUniqueId()); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.DropUnlockOthersConfirmation, otherPlayer.getName()); + } else + { + playerData = this.dataStore.getPlayerData(player.getUniqueId()); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.DropUnlockConfirmation); + } + + playerData.dropsAreUnlocked = true; + + return true; + } + + //deletealladminclaims + else if (player != null && cmd.getName().equalsIgnoreCase("deletealladminclaims")) + { + if (!player.hasPermission("griefprevention.deleteclaims")) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoDeletePermission); + return true; + } + + //delete all admin claims + this.dataStore.deleteClaimsForPlayer(null, true); //null for owner id indicates an administrative claim + + GriefPrevention.sendMessage(player, TextMode.Success, Messages.AllAdminDeleted); + if (player != null) + { + GriefPrevention.AddLogEntry(player.getName() + " deleted all administrative claims.", CustomLogEntryTypes.AdminActivity); + + //revert any current visualization + Visualization.Revert(player); + } + + return true; + } + + //adjustbonusclaimblocks or [] amount + else if (cmd.getName().equalsIgnoreCase("adjustbonusclaimblocks")) + { + //requires exactly two parameters, the other player or group's name and the adjustment + if (args.length != 2) return false; + + //parse the adjustment amount + int adjustment; + try + { + adjustment = Integer.parseInt(args[1]); + } + catch (NumberFormatException numberFormatException) + { + 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; + try + { + UUID playerID = UUID.fromString(args[0]); + targetPlayer = this.getServer().getOfflinePlayer(playerID); + + } + catch (IllegalArgumentException e) + { + targetPlayer = this.resolvePlayerByName(args[0]); + } + + if (targetPlayer == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); return true; } - - //give blocks to player - PlayerData playerData = this.dataStore.getPlayerData(targetPlayer.getUniqueId()); - playerData.setBonusClaimBlocks(playerData.getBonusClaimBlocks() + adjustment); - this.dataStore.savePlayerData(targetPlayer.getUniqueId(), playerData); - - GriefPrevention.sendMessage(player, TextMode.Success, Messages.AdjustBlocksSuccess, targetPlayer.getName(), String.valueOf(adjustment), String.valueOf(playerData.getBonusClaimBlocks())); - if(player != null) GriefPrevention.AddLogEntry(player.getName() + " adjusted " + targetPlayer.getName() + "'s bonus claim blocks by " + adjustment + ".", CustomLogEntryTypes.AdminActivity); - - return true; - } - - //adjustbonusclaimblocksall - else if(cmd.getName().equalsIgnoreCase("adjustbonusclaimblocksall")) + + //give blocks to player + PlayerData playerData = this.dataStore.getPlayerData(targetPlayer.getUniqueId()); + playerData.setBonusClaimBlocks(playerData.getBonusClaimBlocks() + adjustment); + this.dataStore.savePlayerData(targetPlayer.getUniqueId(), playerData); + + GriefPrevention.sendMessage(player, TextMode.Success, Messages.AdjustBlocksSuccess, targetPlayer.getName(), String.valueOf(adjustment), String.valueOf(playerData.getBonusClaimBlocks())); + if (player != null) + GriefPrevention.AddLogEntry(player.getName() + " adjusted " + targetPlayer.getName() + "'s bonus claim blocks by " + adjustment + ".", CustomLogEntryTypes.AdminActivity); + + return true; + } + + //adjustbonusclaimblocksall + else if (cmd.getName().equalsIgnoreCase("adjustbonusclaimblocksall")) { //requires exactly one parameter, the amount of adjustment - if(args.length != 1) return false; - + if (args.length != 1) return false; + //parse the adjustment amount - int adjustment; + int adjustment; try { adjustment = Integer.parseInt(args[0]); } - catch(NumberFormatException numberFormatException) + catch (NumberFormatException numberFormatException) { return false; //causes usage to be displayed } - + //for each online player @SuppressWarnings("unchecked") - Collection players = (Collection)this.getServer().getOnlinePlayers(); + Collection players = (Collection) this.getServer().getOnlinePlayers(); StringBuilder builder = new StringBuilder(); - for(Player onlinePlayer : players) + for (Player onlinePlayer : players) { UUID playerID = onlinePlayer.getUniqueId(); PlayerData playerData = this.dataStore.getPlayerData(playerID); @@ -2396,831 +2354,810 @@ public class GriefPrevention extends JavaPlugin this.dataStore.savePlayerData(playerID, playerData); builder.append(onlinePlayer.getName() + " "); } - + GriefPrevention.sendMessage(player, TextMode.Success, Messages.AdjustBlocksAllSuccess, String.valueOf(adjustment)); GriefPrevention.AddLogEntry("Adjusted all " + players.size() + "players' bonus claim blocks by " + adjustment + ". " + builder.toString(), CustomLogEntryTypes.AdminActivity); - + return true; } - - //setaccruedclaimblocks - else if(cmd.getName().equalsIgnoreCase("setaccruedclaimblocks")) + + //setaccruedclaimblocks + else if (cmd.getName().equalsIgnoreCase("setaccruedclaimblocks")) { //requires exactly two parameters, the other player's name and the new amount - if(args.length != 2) return false; - + if (args.length != 2) return false; + //parse the adjustment amount - int newAmount; + int newAmount; try { newAmount = Integer.parseInt(args[1]); } - catch(NumberFormatException numberFormatException) + catch (NumberFormatException numberFormatException) { return false; //causes usage to be displayed } - + //find the specified player OfflinePlayer targetPlayer = this.resolvePlayerByName(args[0]); - if(targetPlayer == null) + if (targetPlayer == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); return true; } - + //set player's blocks PlayerData playerData = this.dataStore.getPlayerData(targetPlayer.getUniqueId()); playerData.setAccruedClaimBlocks(newAmount); this.dataStore.savePlayerData(targetPlayer.getUniqueId(), playerData); - + GriefPrevention.sendMessage(player, TextMode.Success, Messages.SetClaimBlocksSuccess); - if(player != null) GriefPrevention.AddLogEntry(player.getName() + " set " + targetPlayer.getName() + "'s accrued claim blocks to " + newAmount + ".", CustomLogEntryTypes.AdminActivity); - + if (player != null) + GriefPrevention.AddLogEntry(player.getName() + " set " + targetPlayer.getName() + "'s accrued claim blocks to " + newAmount + ".", CustomLogEntryTypes.AdminActivity); + return true; } - - //trapped - else if(cmd.getName().equalsIgnoreCase("trapped") && player != null) - { - //FEATURE: empower players who get "stuck" in an area where they don't have permission to build to save themselves - - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(player.getLocation(), false, playerData.lastClaim); - - //if another /trapped is pending, ignore this slash command - if(playerData.pendingTrapped) - { - return true; - } - - //if the player isn't in a claim or has permission to build, tell him to man up - if(claim == null || claim.allowBuild(player, Material.AIR) == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.NotTrappedHere); - return true; - } - - //rescue destination may be set by GPFlags or other plugin, ask to find out + + //trapped + else if (cmd.getName().equalsIgnoreCase("trapped") && player != null) + { + //FEATURE: empower players who get "stuck" in an area where they don't have permission to build to save themselves + + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = this.dataStore.getClaimAt(player.getLocation(), false, playerData.lastClaim); + + //if another /trapped is pending, ignore this slash command + if (playerData.pendingTrapped) + { + return true; + } + + //if the player isn't in a claim or has permission to build, tell him to man up + if (claim == null || claim.allowBuild(player, Material.AIR) == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NotTrappedHere); + return true; + } + + //rescue destination may be set by GPFlags or other plugin, ask to find out SaveTrappedPlayerEvent event = new SaveTrappedPlayerEvent(claim); Bukkit.getPluginManager().callEvent(event); - - //if the player is in the nether or end, he's screwed (there's no way to programmatically find a safe place for him) - if(player.getWorld().getEnvironment() != Environment.NORMAL && event.getDestination() == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.TrappedWontWorkHere); - return true; - } - - //if the player is in an administrative claim and AllowTrappedInAdminClaims is false, he should contact an admin - if(!GriefPrevention.instance.config_claims_allowTrappedInAdminClaims && claim.isAdminClaim() && event.getDestination() == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.TrappedWontWorkHere); - return true; - } - //send instructions - 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(), event.getDestination()); - this.getServer().getScheduler().scheduleSyncDelayedTask(this, task, 200L); //20L ~ 1 second - - return true; - } - - //siege - else if(cmd.getName().equalsIgnoreCase("siege") && player != null) - { - //error message for when siege mode is disabled - if(!this.siegeEnabledForWorld(player.getWorld())) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.NonSiegeWorld); - return true; - } - - //requires one argument - if(args.length > 1) - { - return false; - } - - //can't start a siege when you're already involved in one - Player attacker = player; - PlayerData attackerData = this.dataStore.getPlayerData(attacker.getUniqueId()); - if(attackerData.siegeData != null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.AlreadySieging); - return true; - } - - //can't start a siege when you're protected from pvp combat - if(attackerData.pvpImmune) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.CantFightWhileImmune); - return true; - } - - //if a player name was specified, use that - Player defender = null; - if(args.length >= 1) - { - defender = this.getServer().getPlayer(args[0]); - if(defender == null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); - return true; - } - } - - //otherwise use the last player this player was in pvp combat with - else if(attackerData.lastPvpPlayer.length() > 0) - { - defender = this.getServer().getPlayer(attackerData.lastPvpPlayer); - if(defender == null) - { - return false; - } - } - - else - { - return false; - } - // First off, you cannot siege yourself, that's just - // silly: - if (attacker.getName().equals( defender.getName() )) { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoSiegeYourself); - return true; - } - - //victim must not have the permission which makes him immune to siege - if(defender.hasPermission("griefprevention.siegeimmune")) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeImmune); + //if the player is in the nether or end, he's screwed (there's no way to programmatically find a safe place for him) + if (player.getWorld().getEnvironment() != Environment.NORMAL && event.getDestination() == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.TrappedWontWorkHere); return true; - } - - //victim must not be under siege already - PlayerData defenderData = this.dataStore.getPlayerData(defender.getUniqueId()); - if(defenderData.siegeData != null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.AlreadyUnderSiegePlayer); - return true; - } - - //victim must not be pvp immune - if(defenderData.pvpImmune) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoSiegeDefenseless); - return true; - } - - Claim defenderClaim = this.dataStore.getClaimAt(defender.getLocation(), false, null); - - //defender must have some level of permission there to be protected - if(defenderClaim == null || defenderClaim.allowAccess(defender) != null) - { - 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, Messages.SiegeTooFarAway); - return true; - } - - //claim can't be under siege already - if(defenderClaim.siegeData != null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.AlreadyUnderSiegeArea); - return true; - } - - //can't siege admin claims - if(defenderClaim.isAdminClaim()) - { - 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, Messages.SiegeOnCooldown); - return true; - } - - //start the siege - dataStore.startSiege(attacker, defender, defenderClaim); + } - //confirmation message for attacker, warning message for defender - GriefPrevention.sendMessage(defender, TextMode.Warn, Messages.SiegeAlert, attacker.getName()); - GriefPrevention.sendMessage(player, TextMode.Success, Messages.SiegeConfirmed, defender.getName()); + //if the player is in an administrative claim and AllowTrappedInAdminClaims is false, he should contact an admin + if (!GriefPrevention.instance.config_claims_allowTrappedInAdminClaims && claim.isAdminClaim() && event.getDestination() == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.TrappedWontWorkHere); + return true; + } + //send instructions + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.RescuePending); - return true; - } - else if(cmd.getName().equalsIgnoreCase("softmute")) - { - //requires one parameter - if(args.length != 1) return false; - - //find the specified player + //create a task to rescue this player in a little while + PlayerRescueTask task = new PlayerRescueTask(player, player.getLocation(), event.getDestination()); + this.getServer().getScheduler().scheduleSyncDelayedTask(this, task, 200L); //20L ~ 1 second + + return true; + } + + //siege + else if (cmd.getName().equalsIgnoreCase("siege") && player != null) + { + //error message for when siege mode is disabled + if (!this.siegeEnabledForWorld(player.getWorld())) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NonSiegeWorld); + return true; + } + + //requires one argument + if (args.length > 1) + { + return false; + } + + //can't start a siege when you're already involved in one + Player attacker = player; + PlayerData attackerData = this.dataStore.getPlayerData(attacker.getUniqueId()); + if (attackerData.siegeData != null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.AlreadySieging); + return true; + } + + //can't start a siege when you're protected from pvp combat + if (attackerData.pvpImmune) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.CantFightWhileImmune); + return true; + } + + //if a player name was specified, use that + Player defender = null; + if (args.length >= 1) + { + defender = this.getServer().getPlayer(args[0]); + if (defender == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); + return true; + } + } + + //otherwise use the last player this player was in pvp combat with + else if (attackerData.lastPvpPlayer.length() > 0) + { + defender = this.getServer().getPlayer(attackerData.lastPvpPlayer); + if (defender == null) + { + return false; + } + } else + { + return false; + } + + // First off, you cannot siege yourself, that's just + // silly: + if (attacker.getName().equals(defender.getName())) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoSiegeYourself); + return true; + } + + //victim must not have the permission which makes him immune to siege + if (defender.hasPermission("griefprevention.siegeimmune")) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeImmune); + return true; + } + + //victim must not be under siege already + PlayerData defenderData = this.dataStore.getPlayerData(defender.getUniqueId()); + if (defenderData.siegeData != null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.AlreadyUnderSiegePlayer); + return true; + } + + //victim must not be pvp immune + if (defenderData.pvpImmune) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoSiegeDefenseless); + return true; + } + + Claim defenderClaim = this.dataStore.getClaimAt(defender.getLocation(), false, null); + + //defender must have some level of permission there to be protected + if (defenderClaim == null || defenderClaim.allowAccess(defender) != null) + { + 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, Messages.SiegeTooFarAway); + return true; + } + + //claim can't be under siege already + if (defenderClaim.siegeData != null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.AlreadyUnderSiegeArea); + return true; + } + + //can't siege admin claims + if (defenderClaim.isAdminClaim()) + { + 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, Messages.SiegeOnCooldown); + return true; + } + + //start the siege + dataStore.startSiege(attacker, defender, defenderClaim); + + //confirmation message for attacker, warning message for defender + GriefPrevention.sendMessage(defender, TextMode.Warn, Messages.SiegeAlert, attacker.getName()); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.SiegeConfirmed, defender.getName()); + + return true; + } else if (cmd.getName().equalsIgnoreCase("softmute")) + { + //requires one parameter + if (args.length != 1) return false; + + //find the specified player OfflinePlayer targetPlayer = this.resolvePlayerByName(args[0]); - if(targetPlayer == null) + if (targetPlayer == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); return true; } - + //toggle mute for player boolean isMuted = this.dataStore.toggleSoftMute(targetPlayer.getUniqueId()); - if(isMuted) + if (isMuted) { GriefPrevention.sendMessage(player, TextMode.Success, Messages.SoftMuted, targetPlayer.getName()); String executorName = "console"; - if(player != null) + if (player != null) { executorName = player.getName(); } - + GriefPrevention.AddLogEntry(executorName + " muted " + targetPlayer.getName() + ".", CustomLogEntryTypes.AdminActivity, true); - } - else + } else { GriefPrevention.sendMessage(player, TextMode.Success, Messages.UnSoftMuted, targetPlayer.getName()); } - + return true; - } - - else if(cmd.getName().equalsIgnoreCase("gpreload")) - { - this.loadConfig(); - if(player != null) - { - GriefPrevention.sendMessage(player, TextMode.Success, "Configuration updated. If you have updated your Grief Prevention JAR, you still need to /reload or reboot your server."); - } - else - { - GriefPrevention.AddLogEntry("Configuration updated. If you have updated your Grief Prevention JAR, you still need to /reload or reboot your server."); - } - - return true; - } - - //givepet - else if(cmd.getName().equalsIgnoreCase("givepet") && player != null) - { - //requires one parameter - if(args.length < 1) return false; - + } else if (cmd.getName().equalsIgnoreCase("gpreload")) + { + this.loadConfig(); + if (player != null) + { + GriefPrevention.sendMessage(player, TextMode.Success, "Configuration updated. If you have updated your Grief Prevention JAR, you still need to /reload or reboot your server."); + } else + { + GriefPrevention.AddLogEntry("Configuration updated. If you have updated your Grief Prevention JAR, you still need to /reload or reboot your server."); + } + + return true; + } + + //givepet + else if (cmd.getName().equalsIgnoreCase("givepet") && player != null) + { + //requires one parameter + if (args.length < 1) return false; + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - + //special case: cancellation - if(args[0].equalsIgnoreCase("cancel")) + if (args[0].equalsIgnoreCase("cancel")) { playerData.petGiveawayRecipient = null; GriefPrevention.sendMessage(player, TextMode.Success, Messages.PetTransferCancellation); return true; } - + //find the specified player OfflinePlayer targetPlayer = this.resolvePlayerByName(args[0]); - if(targetPlayer == null) + if (targetPlayer == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); return true; } - + //remember the player's ID for later pet transfer playerData.petGiveawayRecipient = targetPlayer; - + //send instructions GriefPrevention.sendMessage(player, TextMode.Instr, Messages.ReadyToTransferPet); - + return true; - } - - //gpblockinfo - else if(cmd.getName().equalsIgnoreCase("gpblockinfo") && player != null) - { - ItemStack inHand = player.getItemInHand(); - player.sendMessage("In Hand: " + String.format("%s(dValue:%s)", inHand.getType().name(), inHand.getData().getData())); - - Block inWorld = GriefPrevention.getTargetNonAirBlock(player, 300); - player.sendMessage("In World: " + String.format("%s(dValue:%s)", inWorld.getType().name(), inWorld.getData())); - - return true; - } - - //ignoreplayer - else if(cmd.getName().equalsIgnoreCase("ignoreplayer") && player != null) + } + + //gpblockinfo + else if (cmd.getName().equalsIgnoreCase("gpblockinfo") && player != null) + { + ItemStack inHand = player.getItemInHand(); + player.sendMessage("In Hand: " + String.format("%s(dValue:%s)", inHand.getType().name(), inHand.getData().getData())); + + Block inWorld = GriefPrevention.getTargetNonAirBlock(player, 300); + player.sendMessage("In World: " + String.format("%s(dValue:%s)", inWorld.getType().name(), inWorld.getData())); + + return true; + } + + //ignoreplayer + else if (cmd.getName().equalsIgnoreCase("ignoreplayer") && player != null) { //requires target player name - if(args.length < 1) return false; - + if (args.length < 1) return false; + //validate target player OfflinePlayer targetPlayer = this.resolvePlayerByName(args[0]); - if(targetPlayer == null) + if (targetPlayer == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); return true; } - + this.setIgnoreStatus(player, targetPlayer, IgnoreMode.StandardIgnore); GriefPrevention.sendMessage(player, TextMode.Success, Messages.IgnoreConfirmation); - + return true; } - - //unignoreplayer - else if(cmd.getName().equalsIgnoreCase("unignoreplayer") && player != null) + + //unignoreplayer + else if (cmd.getName().equalsIgnoreCase("unignoreplayer") && player != null) { //requires target player name - if(args.length < 1) return false; - + if (args.length < 1) return false; + //validate target player OfflinePlayer targetPlayer = this.resolvePlayerByName(args[0]); - if(targetPlayer == null) + if (targetPlayer == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); return true; } - + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); Boolean ignoreStatus = playerData.ignoredPlayers.get(targetPlayer.getUniqueId()); - if(ignoreStatus == null || ignoreStatus == true) + if (ignoreStatus == null || ignoreStatus == true) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.NotIgnoringPlayer); return true; } - + this.setIgnoreStatus(player, targetPlayer, IgnoreMode.None); GriefPrevention.sendMessage(player, TextMode.Success, Messages.UnIgnoreConfirmation); - + return true; } - - //ignoredplayerlist - else if(cmd.getName().equalsIgnoreCase("ignoredplayerlist") && player != null) + + //ignoredplayerlist + else if (cmd.getName().equalsIgnoreCase("ignoredplayerlist") && player != null) { PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); StringBuilder builder = new StringBuilder(); - for(Entry entry : playerData.ignoredPlayers.entrySet()) + for (Entry entry : playerData.ignoredPlayers.entrySet()) { - if(entry.getValue() != null) + if (entry.getValue() != null) { //if not an admin ignore, add it to the list - if(!entry.getValue()) + if (!entry.getValue()) { builder.append(GriefPrevention.lookupPlayerName(entry.getKey())); builder.append(" "); } } } - + String list = builder.toString().trim(); - if(list.isEmpty()) + if (list.isEmpty()) { GriefPrevention.sendMessage(player, TextMode.Info, Messages.NotIgnoringAnyone); - } - else + } else { GriefPrevention.sendMessage(player, TextMode.Info, list); } - + return true; } - - //separateplayers - else if(cmd.getName().equalsIgnoreCase("separate")) + + //separateplayers + else if (cmd.getName().equalsIgnoreCase("separate")) { //requires two player names - if(args.length < 2) return false; - + if (args.length < 2) return false; + //validate target players OfflinePlayer targetPlayer = this.resolvePlayerByName(args[0]); - if(targetPlayer == null) + if (targetPlayer == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); return true; } - + OfflinePlayer targetPlayer2 = this.resolvePlayerByName(args[1]); - if(targetPlayer2 == null) + if (targetPlayer2 == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); return true; } - + this.setIgnoreStatus(targetPlayer, targetPlayer2, IgnoreMode.AdminIgnore); GriefPrevention.sendMessage(player, TextMode.Success, Messages.SeparateConfirmation); - + return true; } - - //unseparateplayers - else if(cmd.getName().equalsIgnoreCase("unseparate")) + + //unseparateplayers + else if (cmd.getName().equalsIgnoreCase("unseparate")) { //requires two player names - if(args.length < 2) return false; - + if (args.length < 2) return false; + //validate target players OfflinePlayer targetPlayer = this.resolvePlayerByName(args[0]); - if(targetPlayer == null) + if (targetPlayer == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); return true; } - + OfflinePlayer targetPlayer2 = this.resolvePlayerByName(args[1]); - if(targetPlayer2 == null) + if (targetPlayer2 == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); return true; } - + this.setIgnoreStatus(targetPlayer, targetPlayer2, IgnoreMode.None); this.setIgnoreStatus(targetPlayer2, targetPlayer, IgnoreMode.None); GriefPrevention.sendMessage(player, TextMode.Success, Messages.UnSeparateConfirmation); - + return true; } - return false; - } - - void setIgnoreStatus(OfflinePlayer ignorer, OfflinePlayer ignoree, IgnoreMode mode) - { - PlayerData playerData = this.dataStore.getPlayerData(ignorer.getUniqueId()); - if(mode == IgnoreMode.None) + return false; + } + + void setIgnoreStatus(OfflinePlayer ignorer, OfflinePlayer ignoree, IgnoreMode mode) + { + PlayerData playerData = this.dataStore.getPlayerData(ignorer.getUniqueId()); + if (mode == IgnoreMode.None) { playerData.ignoredPlayers.remove(ignoree.getUniqueId()); - } - else + } else { playerData.ignoredPlayers.put(ignoree.getUniqueId(), mode == IgnoreMode.StandardIgnore ? false : true); } - + playerData.ignoreListChanged = true; - if(!ignorer.isOnline()) + if (!ignorer.isOnline()) { this.dataStore.savePlayerData(ignorer.getUniqueId(), playerData); this.dataStore.clearCachedPlayerData(ignorer.getUniqueId()); } - } - - public enum IgnoreMode {None, StandardIgnore, AdminIgnore} - - private String trustEntryToPlayerName(String entry) - { - if(entry.startsWith("[") || entry.equals("public")) + } + + public enum IgnoreMode + {None, StandardIgnore, AdminIgnore} + + private String trustEntryToPlayerName(String entry) + { + if (entry.startsWith("[") || entry.equals("public")) { return entry; - } - else + } else { return GriefPrevention.lookupPlayerName(entry); } } - public static String getfriendlyLocationString(Location location) - { - return location.getWorld().getName() + ": x" + location.getBlockX() + ", z" + location.getBlockZ(); - } + public static String getfriendlyLocationString(Location location) + { + return location.getWorld().getName() + ": x" + location.getBlockX() + ", z" + location.getBlockZ(); + } - private boolean abandonClaimHandler(Player player, boolean deleteTopLevelClaim) - { - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - - //which claim is being abandoned? - Claim claim = this.dataStore.getClaimAt(player.getLocation(), true /*ignore height*/, null); - if(claim == null) - { - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.AbandonClaimMissing); - } - - //verify ownership - else if(claim.allowEdit(player) != null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.NotYourClaim); - } - - //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, Messages.DeleteTopLevelClaim); - return true; - } - - else - { - //delete it - claim.removeSurfaceFluids(null); - this.dataStore.deleteClaim(claim, true, false); - - //if in a creative mode world, restore the claim area - if(GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner())) - { - GriefPrevention.AddLogEntry(player.getName() + " abandoned a claim @ " + GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner())); - GriefPrevention.sendMessage(player, TextMode.Warn, Messages.UnclaimCleanupWarning); - GriefPrevention.instance.restoreClaim(claim, 20L * 60 * 2); - } + private boolean abandonClaimHandler(Player player, boolean deleteTopLevelClaim) + { + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - //adjust claim blocks when abandoning a top level claim - if(this.config_claims_abandonReturnRatio != 1.0D && claim.parent == null && claim.ownerID.equals(playerData.playerID)) - { - playerData.setAccruedClaimBlocks(playerData.getAccruedClaimBlocks() - (int)Math.ceil((claim.getArea() * (1 - this.config_claims_abandonReturnRatio)))); - } - - //tell the player how many claim blocks he has left - int remainingBlocks = playerData.getRemainingClaimBlocks(); - GriefPrevention.sendMessage(player, TextMode.Success, Messages.AbandonSuccess, String.valueOf(remainingBlocks)); - - //revert any current visualization - Visualization.Revert(player); - - playerData.warnedAboutMajorDeletion = false; - } - - return true; - - } + //which claim is being abandoned? + Claim claim = this.dataStore.getClaimAt(player.getLocation(), true /*ignore height*/, null); + if (claim == null) + { + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.AbandonClaimMissing); + } - //helper method keeps the trust commands consistent and eliminates duplicate code - private void handleTrustCommand(Player player, ClaimPermission permissionLevel, String recipientName) - { - //determine which claim the player is standing in - Claim claim = this.dataStore.getClaimAt(player.getLocation(), true /*ignore height*/, null); - - //validate player or group argument - String permission = null; - OfflinePlayer otherPlayer = null; - UUID recipientID = null; - if(recipientName.startsWith("[") && recipientName.endsWith("]")) - { - permission = recipientName.substring(1, recipientName.length() - 1); - if(permission == null || permission.isEmpty()) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.InvalidPermissionID); - return; - } - } - - else if(recipientName.contains(".")) - { - permission = recipientName; - } - - else - { - otherPlayer = this.resolvePlayerByName(recipientName); - if(otherPlayer == null && !recipientName.equals("public") && !recipientName.equals("all")) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); - return; - } - - if(otherPlayer != null) - { - recipientName = otherPlayer.getName(); - recipientID = otherPlayer.getUniqueId(); - } - else - { - recipientName = "public"; - } - } - - //determine which claims should be modified - ArrayList targetClaims = new ArrayList(); - if(claim == null) - { - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - for(int i = 0; i < playerData.getClaims().size(); i++) - { - targetClaims.add(playerData.getClaims().get(i)); - } - } - else - { - //check permission here - if(claim.allowGrantPermission(player) != null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoPermissionTrust, claim.getOwnerName()); - return; - } - - //see if the player has the level of permission he's trying to grant - String errorMessage = null; - - //permission level null indicates granting permission trust - if(permissionLevel == null) - { - errorMessage = claim.allowEdit(player); - if(errorMessage != null) - { - errorMessage = "Only " + claim.getOwnerName() + " can grant /PermissionTrust here."; - } - } - - //otherwise just use the ClaimPermission enum values - else - { - switch(permissionLevel) - { - case Access: - errorMessage = claim.allowAccess(player); - break; - case Inventory: - errorMessage = claim.allowContainers(player); - break; - default: - errorMessage = claim.allowBuild(player, Material.AIR); - } - } - - //error message for trying to grant a permission the player doesn't have - if(errorMessage != null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.CantGrantThatPermission); - return; - } - - targetClaims.add(claim); - } - - //if we didn't determine which claims to modify, tell the player to be specific - if(targetClaims.size() == 0) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.GrantPermissionNoClaim); - return; - } - - String identifierToAdd = recipientName; - if(permission != null) - { - identifierToAdd = "[" + permission + "]"; - } - else if(recipientID != null) - { - identifierToAdd = recipientID.toString(); - } - - //calling the event - TrustChangedEvent event = new TrustChangedEvent(player, targetClaims, permissionLevel, true, identifierToAdd); - Bukkit.getPluginManager().callEvent(event); - - if (event.isCancelled()) { - return; - } - - //apply changes - for(int i = 0; i < targetClaims.size(); i++) - { - Claim currentClaim = targetClaims.get(i); - - if(permissionLevel == null) - { - if(!currentClaim.managers.contains(identifierToAdd)) - { - currentClaim.managers.add(identifierToAdd); - } - } - else - { - currentClaim.setPermission(identifierToAdd, permissionLevel); - } - this.dataStore.saveClaim(currentClaim); - } - - //notify player - if(recipientName.equals("public")) recipientName = this.dataStore.getMessage(Messages.CollectivePublic); - String permissionDescription; - if(permissionLevel == null) - { - permissionDescription = this.dataStore.getMessage(Messages.PermissionsPermission); - } - else if(permissionLevel == ClaimPermission.Build) - { - permissionDescription = this.dataStore.getMessage(Messages.BuildPermission); - } - else if(permissionLevel == ClaimPermission.Access) - { - permissionDescription = this.dataStore.getMessage(Messages.AccessPermission); - } - else //ClaimPermission.Inventory - { - permissionDescription = this.dataStore.getMessage(Messages.ContainersPermission); - } - - String location; - if(claim == null) - { - location = this.dataStore.getMessage(Messages.LocationAllClaims); - } - else - { - location = this.dataStore.getMessage(Messages.LocationCurrentClaim); - } - - GriefPrevention.sendMessage(player, TextMode.Success, Messages.GrantPermissionConfirmation, recipientName, permissionDescription, location); - } + //verify ownership + else if (claim.allowEdit(player) != null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NotYourClaim); + } - //helper method to resolve a player by name - ConcurrentHashMap playerNameToIDMap = new ConcurrentHashMap(); + //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, Messages.DeleteTopLevelClaim); + return true; + } else + { + //delete it + claim.removeSurfaceFluids(null); + this.dataStore.deleteClaim(claim, true, false); + + //if in a creative mode world, restore the claim area + if (GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner())) + { + GriefPrevention.AddLogEntry(player.getName() + " abandoned a claim @ " + GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner())); + GriefPrevention.sendMessage(player, TextMode.Warn, Messages.UnclaimCleanupWarning); + GriefPrevention.instance.restoreClaim(claim, 20L * 60 * 2); + } + + //adjust claim blocks when abandoning a top level claim + if (this.config_claims_abandonReturnRatio != 1.0D && claim.parent == null && claim.ownerID.equals(playerData.playerID)) + { + playerData.setAccruedClaimBlocks(playerData.getAccruedClaimBlocks() - (int) Math.ceil((claim.getArea() * (1 - this.config_claims_abandonReturnRatio)))); + } + + //tell the player how many claim blocks he has left + int remainingBlocks = playerData.getRemainingClaimBlocks(); + GriefPrevention.sendMessage(player, TextMode.Success, Messages.AbandonSuccess, String.valueOf(remainingBlocks)); + + //revert any current visualization + Visualization.Revert(player); + + playerData.warnedAboutMajorDeletion = false; + } + + return true; + + } + + //helper method keeps the trust commands consistent and eliminates duplicate code + private void handleTrustCommand(Player player, ClaimPermission permissionLevel, String recipientName) + { + //determine which claim the player is standing in + Claim claim = this.dataStore.getClaimAt(player.getLocation(), true /*ignore height*/, null); + + //validate player or group argument + String permission = null; + OfflinePlayer otherPlayer = null; + UUID recipientID = null; + if (recipientName.startsWith("[") && recipientName.endsWith("]")) + { + permission = recipientName.substring(1, recipientName.length() - 1); + if (permission == null || permission.isEmpty()) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.InvalidPermissionID); + return; + } + } else if (recipientName.contains(".")) + { + permission = recipientName; + } else + { + otherPlayer = this.resolvePlayerByName(recipientName); + if (otherPlayer == null && !recipientName.equals("public") && !recipientName.equals("all")) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound2); + return; + } + + if (otherPlayer != null) + { + recipientName = otherPlayer.getName(); + recipientID = otherPlayer.getUniqueId(); + } else + { + recipientName = "public"; + } + } + + //determine which claims should be modified + ArrayList targetClaims = new ArrayList(); + if (claim == null) + { + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + for (int i = 0; i < playerData.getClaims().size(); i++) + { + targetClaims.add(playerData.getClaims().get(i)); + } + } else + { + //check permission here + if (claim.allowGrantPermission(player) != null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoPermissionTrust, claim.getOwnerName()); + return; + } + + //see if the player has the level of permission he's trying to grant + String errorMessage = null; + + //permission level null indicates granting permission trust + if (permissionLevel == null) + { + errorMessage = claim.allowEdit(player); + if (errorMessage != null) + { + errorMessage = "Only " + claim.getOwnerName() + " can grant /PermissionTrust here."; + } + } + + //otherwise just use the ClaimPermission enum values + else + { + switch (permissionLevel) + { + case Access: + errorMessage = claim.allowAccess(player); + break; + case Inventory: + errorMessage = claim.allowContainers(player); + break; + default: + errorMessage = claim.allowBuild(player, Material.AIR); + } + } + + //error message for trying to grant a permission the player doesn't have + if (errorMessage != null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.CantGrantThatPermission); + return; + } + + targetClaims.add(claim); + } + + //if we didn't determine which claims to modify, tell the player to be specific + if (targetClaims.size() == 0) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.GrantPermissionNoClaim); + return; + } + + String identifierToAdd = recipientName; + if (permission != null) + { + identifierToAdd = "[" + permission + "]"; + } else if (recipientID != null) + { + identifierToAdd = recipientID.toString(); + } + + //calling the event + TrustChangedEvent event = new TrustChangedEvent(player, targetClaims, permissionLevel, true, identifierToAdd); + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) + { + return; + } + + //apply changes + for (int i = 0; i < targetClaims.size(); i++) + { + Claim currentClaim = targetClaims.get(i); + + if (permissionLevel == null) + { + if (!currentClaim.managers.contains(identifierToAdd)) + { + currentClaim.managers.add(identifierToAdd); + } + } else + { + currentClaim.setPermission(identifierToAdd, permissionLevel); + } + this.dataStore.saveClaim(currentClaim); + } + + //notify player + if (recipientName.equals("public")) recipientName = this.dataStore.getMessage(Messages.CollectivePublic); + String permissionDescription; + if (permissionLevel == null) + { + permissionDescription = this.dataStore.getMessage(Messages.PermissionsPermission); + } else if (permissionLevel == ClaimPermission.Build) + { + permissionDescription = this.dataStore.getMessage(Messages.BuildPermission); + } else if (permissionLevel == ClaimPermission.Access) + { + permissionDescription = this.dataStore.getMessage(Messages.AccessPermission); + } else //ClaimPermission.Inventory + { + permissionDescription = this.dataStore.getMessage(Messages.ContainersPermission); + } + + String location; + if (claim == null) + { + location = this.dataStore.getMessage(Messages.LocationAllClaims); + } else + { + location = this.dataStore.getMessage(Messages.LocationCurrentClaim); + } + + GriefPrevention.sendMessage(player, TextMode.Success, Messages.GrantPermissionConfirmation, recipientName, permissionDescription, location); + } + + //helper method to resolve a player by name + ConcurrentHashMap playerNameToIDMap = new ConcurrentHashMap(); //thread to build the above cache - private class CacheOfflinePlayerNamesThread extends Thread + private class CacheOfflinePlayerNamesThread extends Thread { - private OfflinePlayer [] offlinePlayers; + private OfflinePlayer[] offlinePlayers; private ConcurrentHashMap playerNameToIDMap; - - CacheOfflinePlayerNamesThread(OfflinePlayer [] offlinePlayers, ConcurrentHashMap playerNameToIDMap) + + CacheOfflinePlayerNamesThread(OfflinePlayer[] offlinePlayers, ConcurrentHashMap playerNameToIDMap) { this.offlinePlayers = offlinePlayers; this.playerNameToIDMap = playerNameToIDMap; } - + public void run() { long now = System.currentTimeMillis(); final long millisecondsPerDay = 1000 * 60 * 60 * 24; - for(OfflinePlayer player : offlinePlayers) + for (OfflinePlayer player : offlinePlayers) { try { UUID playerID = player.getUniqueId(); - if(playerID == null) continue; + if (playerID == null) continue; long lastSeen = player.getLastPlayed(); - + //if the player has been seen in the last 90 days, cache his name/UUID pair long diff = now - lastSeen; long daysDiff = diff / millisecondsPerDay; - if(daysDiff <= config_advanced_offlineplayer_cache_days) + if (daysDiff <= config_advanced_offlineplayer_cache_days) { String playerName = player.getName(); - if(playerName == null) continue; + if (playerName == null) continue; this.playerNameToIDMap.put(playerName, playerID); this.playerNameToIDMap.put(playerName.toLowerCase(), playerID); } } - catch(Exception e) + catch (Exception e) { e.printStackTrace(); } } } } - - @SuppressWarnings("deprecation") - public OfflinePlayer resolvePlayerByName(String name) - { - //try online players first - Player targetPlayer = this.getServer().getPlayerExact(name); - if(targetPlayer != null) return targetPlayer; - + + @SuppressWarnings("deprecation") + public OfflinePlayer resolvePlayerByName(String name) + { + //try online players first + Player targetPlayer = this.getServer().getPlayerExact(name); + if (targetPlayer != null) return targetPlayer; + UUID bestMatchID = null; - + //try exact match first bestMatchID = this.playerNameToIDMap.get(name); - + //if failed, try ignore case - if(bestMatchID == null) + if (bestMatchID == null) { bestMatchID = this.playerNameToIDMap.get(name.toLowerCase()); } - if(bestMatchID == null) + if (bestMatchID == null) { return null; } - return this.getServer().getOfflinePlayer(bestMatchID); - } + return this.getServer().getOfflinePlayer(bestMatchID); + } - //helper method to resolve a player name from the player's UUID - static String lookupPlayerName(UUID playerID) + //helper method to resolve a player name from the player's UUID + static String lookupPlayerName(UUID playerID) { //parameter validation - if(playerID == null) return "somebody"; - + if (playerID == null) return "somebody"; + //check the cache OfflinePlayer player = GriefPrevention.instance.getServer().getOfflinePlayer(playerID); - if(player.hasPlayedBefore() || player.isOnline()) + if (player.hasPlayedBefore() || player.isOnline()) { return player.getName(); - } - else + } else { return "someone(" + playerID.toString() + ")"; } } - + //cache for player name lookups, to save searches of all offline players static void cacheUUIDNamePair(UUID playerID, String playerName) { @@ -3237,398 +3174,393 @@ public class GriefPrevention extends JavaPlugin { id = UUID.fromString(playerID); } - catch(IllegalArgumentException ex) + catch (IllegalArgumentException ex) { GriefPrevention.AddLogEntry("Error: Tried to look up a local player name for invalid UUID: " + playerID); return "someone"; } - + return lookupPlayerName(id); } - - public void onDisable() - { - //save data for any online players - @SuppressWarnings("unchecked") - Collection players = (Collection)this.getServer().getOnlinePlayers(); - for(Player player : players) - { - UUID playerID = player.getUniqueId(); - PlayerData playerData = this.dataStore.getPlayerData(playerID); - this.dataStore.savePlayerDataSync(playerID, playerData); - } - - this.dataStore.close(); - - //dump any remaining unwritten log entries - this.customLogger.WriteEntries(); - - AddLogEntry("GriefPrevention disabled."); - } - - //called when a player spawns, applies protection for that player if necessary - public void checkPvpProtectionNeeded(Player player) - { - //if anti spawn camping feature is not enabled, do nothing - if(!this.config_pvp_protectFreshSpawns) return; - - //if pvp is disabled, do nothing - if(!pvpRulesApply(player.getWorld())) return; - - //if player is in creative mode, do nothing - if(player.getGameMode() == GameMode.CREATIVE) return; - - //if the player has the damage any player permission enabled, do nothing - if(player.hasPermission("griefprevention.nopvpimmunity")) return; - - //check inventory for well, anything - if(GriefPrevention.isInventoryEmpty(player)) - { - //if empty, apply immunity - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - playerData.pvpImmune = true; - - //inform the player after he finishes respawning - GriefPrevention.sendMessage(player, TextMode.Success, Messages.PvPImmunityStart, 5L); - - //start a task to re-check this player's inventory every minute until his immunity is gone - PvPImmunityValidationTask task = new PvPImmunityValidationTask(player); - this.getServer().getScheduler().scheduleSyncDelayedTask(this, task, 1200L); - } - } - - static boolean isInventoryEmpty(Player player) - { - PlayerInventory inventory = player.getInventory(); - ItemStack [] armorStacks = inventory.getArmorContents(); - + + public void onDisable() + { + //save data for any online players + @SuppressWarnings("unchecked") + Collection players = (Collection) this.getServer().getOnlinePlayers(); + for (Player player : players) + { + UUID playerID = player.getUniqueId(); + PlayerData playerData = this.dataStore.getPlayerData(playerID); + this.dataStore.savePlayerDataSync(playerID, playerData); + } + + this.dataStore.close(); + + //dump any remaining unwritten log entries + this.customLogger.WriteEntries(); + + AddLogEntry("GriefPrevention disabled."); + } + + //called when a player spawns, applies protection for that player if necessary + public void checkPvpProtectionNeeded(Player player) + { + //if anti spawn camping feature is not enabled, do nothing + if (!this.config_pvp_protectFreshSpawns) return; + + //if pvp is disabled, do nothing + if (!pvpRulesApply(player.getWorld())) return; + + //if player is in creative mode, do nothing + if (player.getGameMode() == GameMode.CREATIVE) return; + + //if the player has the damage any player permission enabled, do nothing + if (player.hasPermission("griefprevention.nopvpimmunity")) return; + + //check inventory for well, anything + if (GriefPrevention.isInventoryEmpty(player)) + { + //if empty, apply immunity + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + playerData.pvpImmune = true; + + //inform the player after he finishes respawning + GriefPrevention.sendMessage(player, TextMode.Success, Messages.PvPImmunityStart, 5L); + + //start a task to re-check this player's inventory every minute until his immunity is gone + PvPImmunityValidationTask task = new PvPImmunityValidationTask(player); + this.getServer().getScheduler().scheduleSyncDelayedTask(this, task, 1200L); + } + } + + static boolean isInventoryEmpty(Player player) + { + PlayerInventory inventory = player.getInventory(); + ItemStack[] armorStacks = inventory.getArmorContents(); + //check armor slots, stop if any items are found - for(int i = 0; i < armorStacks.length; i++) + for (int i = 0; i < armorStacks.length; i++) { - if(!(armorStacks[i] == null || armorStacks[i].getType() == Material.AIR)) return false; + if (!(armorStacks[i] == null || armorStacks[i].getType() == Material.AIR)) return false; } - + //check other slots, stop if any items are found - ItemStack [] generalStacks = inventory.getContents(); - for(int i = 0; i < generalStacks.length; i++) + ItemStack[] generalStacks = inventory.getContents(); + for (int i = 0; i < generalStacks.length; i++) { - if(!(generalStacks[i] == null || generalStacks[i].getType() == Material.AIR)) return false; + if (!(generalStacks[i] == null || generalStacks[i].getType() == Material.AIR)) return false; } - - return true; + + return true; } //checks whether players siege in a world - public boolean siegeEnabledForWorld(World world) - { - return this.config_siege_enabledWorlds.contains(world); - } + public boolean siegeEnabledForWorld(World world) + { + return this.config_siege_enabledWorlds.contains(world); + } - //moves a player from the claim he's in to a nearby wilderness location - public Location ejectPlayer(Player player) - { - //look for a suitable location - Location candidateLocation = player.getLocation(); - while(true) - { - Claim claim = null; - claim = GriefPrevention.instance.dataStore.getClaimAt(candidateLocation, false, null); - - //if there's a claim here, keep looking - if(claim != null) - { - candidateLocation = new Location(claim.lesserBoundaryCorner.getWorld(), claim.lesserBoundaryCorner.getBlockX() - 1, claim.lesserBoundaryCorner.getBlockY(), claim.lesserBoundaryCorner.getBlockZ() - 1); - continue; - } - - //otherwise find a safe place to teleport the player - else - { - //find a safe height, a couple of blocks above the surface - GuaranteeChunkLoaded(candidateLocation); - Block highestBlock = candidateLocation.getWorld().getHighestBlockAt(candidateLocation.getBlockX(), candidateLocation.getBlockZ()); - Location destination = new Location(highestBlock.getWorld(), highestBlock.getX(), highestBlock.getY() + 2, highestBlock.getZ()); - player.teleport(destination); - return destination; - } - } - } - - //ensures a piece of the managed world is loaded into server memory - //(generates the chunk if necessary) - private static void GuaranteeChunkLoaded(Location location) - { - Chunk chunk = location.getChunk(); - while(!chunk.isLoaded() || !chunk.load(true)); - } - - //sends a color-coded message to a player - public static void sendMessage(Player player, ChatColor color, Messages messageID, String... args) - { - sendMessage(player, color, messageID, 0, args); - } - - //sends a color-coded message to a player - public static void sendMessage(Player player, ChatColor color, Messages messageID, long delayInTicks, String... args) - { - String message = GriefPrevention.instance.dataStore.getMessage(messageID, args); - sendMessage(player, color, message, delayInTicks); - } - - //sends a color-coded message to a player - public static void sendMessage(Player player, ChatColor color, String message) - { - if(message == null || message.length() == 0) return; - - if(player == null) - { - GriefPrevention.AddLogEntry(color + message); - } - else - { - player.sendMessage(color + message); - } - } - - public static void sendMessage(Player player, ChatColor color, String message, long delayInTicks) - { - SendPlayerMessageTask task = new SendPlayerMessageTask(player, color, message); + //moves a player from the claim he's in to a nearby wilderness location + public Location ejectPlayer(Player player) + { + //look for a suitable location + Location candidateLocation = player.getLocation(); + while (true) + { + Claim claim = null; + claim = GriefPrevention.instance.dataStore.getClaimAt(candidateLocation, false, null); - //Only schedule if there should be a delay. Otherwise, send the message right now, else the message will appear out of order. - if(delayInTicks > 0) - { - GriefPrevention.instance.getServer().getScheduler().runTaskLater(GriefPrevention.instance, task, delayInTicks); - } - else - { - task.run(); - } - } - - //checks whether players can create claims in a world + //if there's a claim here, keep looking + if (claim != null) + { + candidateLocation = new Location(claim.lesserBoundaryCorner.getWorld(), claim.lesserBoundaryCorner.getBlockX() - 1, claim.lesserBoundaryCorner.getBlockY(), claim.lesserBoundaryCorner.getBlockZ() - 1); + continue; + } + + //otherwise find a safe place to teleport the player + else + { + //find a safe height, a couple of blocks above the surface + GuaranteeChunkLoaded(candidateLocation); + Block highestBlock = candidateLocation.getWorld().getHighestBlockAt(candidateLocation.getBlockX(), candidateLocation.getBlockZ()); + Location destination = new Location(highestBlock.getWorld(), highestBlock.getX(), highestBlock.getY() + 2, highestBlock.getZ()); + player.teleport(destination); + return destination; + } + } + } + + //ensures a piece of the managed world is loaded into server memory + //(generates the chunk if necessary) + private static void GuaranteeChunkLoaded(Location location) + { + Chunk chunk = location.getChunk(); + while (!chunk.isLoaded() || !chunk.load(true)) ; + } + + //sends a color-coded message to a player + public static void sendMessage(Player player, ChatColor color, Messages messageID, String... args) + { + sendMessage(player, color, messageID, 0, args); + } + + //sends a color-coded message to a player + public static void sendMessage(Player player, ChatColor color, Messages messageID, long delayInTicks, String... args) + { + String message = GriefPrevention.instance.dataStore.getMessage(messageID, args); + sendMessage(player, color, message, delayInTicks); + } + + //sends a color-coded message to a player + public static void sendMessage(Player player, ChatColor color, String message) + { + if (message == null || message.length() == 0) return; + + if (player == null) + { + GriefPrevention.AddLogEntry(color + message); + } else + { + player.sendMessage(color + message); + } + } + + public static void sendMessage(Player player, ChatColor color, String message, long delayInTicks) + { + SendPlayerMessageTask task = new SendPlayerMessageTask(player, color, message); + + //Only schedule if there should be a delay. Otherwise, send the message right now, else the message will appear out of order. + if (delayInTicks > 0) + { + GriefPrevention.instance.getServer().getScheduler().runTaskLater(GriefPrevention.instance, task, delayInTicks); + } else + { + task.run(); + } + } + + //checks whether players can create claims in a world public boolean claimsEnabledForWorld(World world) { ClaimsMode mode = this.config_claims_worldModes.get(world); return mode != null && mode != ClaimsMode.Disabled; } - - //determines whether creative anti-grief rules apply at a location - boolean creativeRulesApply(Location location) - { - if(!this.config_creativeWorldsExist) return false; - return this.config_claims_worldModes.get((location.getWorld())) == ClaimsMode.Creative; - } - - public String allowBuild(Player player, Location location) - { - return this.allowBuild(player, location, location.getBlock().getType()); - } - - public String allowBuild(Player player, Location location, Material material) - { - if(!GriefPrevention.instance.claimsEnabledForWorld(location.getWorld())) return null; - - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(location, false, playerData.lastClaim); - - //exception: administrators in ignore claims mode - if(playerData.ignoreClaims) return null; - - //wilderness rules - if(claim == null) - { - //no building in the wilderness in creative mode - if(this.creativeRulesApply(location) || this.config_claims_worldModes.get(location.getWorld()) == ClaimsMode.SurvivalRequiringClaims) - { - //exception: when chest claims are enabled, players who have zero land claims and are placing a chest - if(material != Material.CHEST || playerData.getClaims().size() > 0 || GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius == -1) - { - String reason = this.dataStore.getMessage(Messages.NoBuildOutsideClaims); - if(player.hasPermission("griefprevention.ignoreclaims")) - reason += " " + this.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); - reason += " " + this.dataStore.getMessage(Messages.CreativeBasicsVideo2, DataStore.CREATIVE_VIDEO_URL); - return reason; - } - else - { - return null; - } - } - - //but it's fine in survival mode - else - { - return null; - } - } - - //if not in the wilderness, then apply claim rules (permissions, etc) - else - { - //cache the claim for later reference - playerData.lastClaim = claim; - return claim.allowBuild(player, material); - } - } - - public String allowBreak(Player player, Block block, Location location) - { - return this.allowBreak(player, block, location, null); - } - - public String allowBreak(Player player, Block block, Location location, BlockBreakEvent breakEvent) + //determines whether creative anti-grief rules apply at a location + boolean creativeRulesApply(Location location) { - if(!GriefPrevention.instance.claimsEnabledForWorld(location.getWorld())) return null; - + if (!this.config_creativeWorldsExist) return false; + + return this.config_claims_worldModes.get((location.getWorld())) == ClaimsMode.Creative; + } + + public String allowBuild(Player player, Location location) + { + return this.allowBuild(player, location, location.getBlock().getType()); + } + + public String allowBuild(Player player, Location location, Material material) + { + if (!GriefPrevention.instance.claimsEnabledForWorld(location.getWorld())) return null; + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); Claim claim = this.dataStore.getClaimAt(location, false, playerData.lastClaim); - + //exception: administrators in ignore claims mode - if(playerData.ignoreClaims) return null; - + if (playerData.ignoreClaims) return null; + //wilderness rules - if(claim == null) + if (claim == null) { //no building in the wilderness in creative mode - if(this.creativeRulesApply(location) || this.config_claims_worldModes.get(location.getWorld()) == ClaimsMode.SurvivalRequiringClaims) + if (this.creativeRulesApply(location) || this.config_claims_worldModes.get(location.getWorld()) == ClaimsMode.SurvivalRequiringClaims) { - String reason = this.dataStore.getMessage(Messages.NoBuildOutsideClaims); - if(player.hasPermission("griefprevention.ignoreclaims")) - reason += " " + this.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); - reason += " " + this.dataStore.getMessage(Messages.CreativeBasicsVideo2, DataStore.CREATIVE_VIDEO_URL); - return reason; + //exception: when chest claims are enabled, players who have zero land claims and are placing a chest + if (material != Material.CHEST || playerData.getClaims().size() > 0 || GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius == -1) + { + String reason = this.dataStore.getMessage(Messages.NoBuildOutsideClaims); + if (player.hasPermission("griefprevention.ignoreclaims")) + reason += " " + this.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); + reason += " " + this.dataStore.getMessage(Messages.CreativeBasicsVideo2, DataStore.CREATIVE_VIDEO_URL); + return reason; + } else + { + return null; + } } - + //but it's fine in survival mode else { return null; } } + + //if not in the wilderness, then apply claim rules (permissions, etc) else { //cache the claim for later reference playerData.lastClaim = claim; - + return claim.allowBuild(player, material); + } + } + + public String allowBreak(Player player, Block block, Location location) + { + return this.allowBreak(player, block, location, null); + } + + public String allowBreak(Player player, Block block, Location location, BlockBreakEvent breakEvent) + { + if (!GriefPrevention.instance.claimsEnabledForWorld(location.getWorld())) return null; + + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = this.dataStore.getClaimAt(location, false, playerData.lastClaim); + + //exception: administrators in ignore claims mode + if (playerData.ignoreClaims) return null; + + //wilderness rules + if (claim == null) + { + //no building in the wilderness in creative mode + if (this.creativeRulesApply(location) || this.config_claims_worldModes.get(location.getWorld()) == ClaimsMode.SurvivalRequiringClaims) + { + String reason = this.dataStore.getMessage(Messages.NoBuildOutsideClaims); + if (player.hasPermission("griefprevention.ignoreclaims")) + reason += " " + this.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); + reason += " " + this.dataStore.getMessage(Messages.CreativeBasicsVideo2, DataStore.CREATIVE_VIDEO_URL); + return reason; + } + + //but it's fine in survival mode + else + { + return null; + } + } else + { + //cache the claim for later reference + playerData.lastClaim = claim; + //if not in the wilderness, then apply claim rules (permissions, etc) String cancel = claim.allowBreak(player, block.getType()); - if(cancel != null && breakEvent != null) + if (cancel != null && breakEvent != null) { PreventBlockBreakEvent preventionEvent = new PreventBlockBreakEvent(breakEvent); Bukkit.getPluginManager().callEvent(preventionEvent); - if(preventionEvent.isCancelled()) + if (preventionEvent.isCancelled()) { cancel = null; } } - + return cancel; } } - //restores nature in multiple chunks, as described by a claim instance - //this restores all chunks which have ANY number of claim blocks from this claim in them - //if the claim is still active (in the data store), then the claimed blocks will not be changed (only the area bordering the claim) - public void restoreClaim(Claim claim, long delayInTicks) - { - //admin claims aren't automatically cleaned up when deleted or abandoned - if(claim.isAdminClaim()) return; - - //it's too expensive to do this for huge claims - if(claim.getArea() > 10000) return; - - ArrayList chunks = claim.getChunks(); - for(Chunk chunk : chunks) + //restores nature in multiple chunks, as described by a claim instance + //this restores all chunks which have ANY number of claim blocks from this claim in them + //if the claim is still active (in the data store), then the claimed blocks will not be changed (only the area bordering the claim) + public void restoreClaim(Claim claim, long delayInTicks) + { + //admin claims aren't automatically cleaned up when deleted or abandoned + if (claim.isAdminClaim()) return; + + //it's too expensive to do this for huge claims + if (claim.getArea() > 10000) return; + + ArrayList chunks = claim.getChunks(); + for (Chunk chunk : chunks) { - this.restoreChunk(chunk, this.getSeaLevel(chunk.getWorld()) - 15, false, delayInTicks, null); + this.restoreChunk(chunk, this.getSeaLevel(chunk.getWorld()) - 15, false, delayInTicks, null); } - } - - @SuppressWarnings("deprecation") + } + + @SuppressWarnings("deprecation") public void restoreChunk(Chunk chunk, int miny, boolean aggressiveMode, long delayInTicks, Player playerReceivingVisualization) - { - //build a snapshot of this chunk, including 1 block boundary outside of the chunk all the way around - int maxHeight = chunk.getWorld().getMaxHeight(); - BlockSnapshot[][][] snapshots = new BlockSnapshot[18][maxHeight][18]; - Block startBlock = chunk.getBlock(0, 0, 0); - Location startLocation = new Location(chunk.getWorld(), startBlock.getX() - 1, 0, startBlock.getZ() - 1); - for(int x = 0; x < snapshots.length; x++) - { - for(int z = 0; z < snapshots[0][0].length; z++) - { - for(int y = 0; y < snapshots[0].length; y++) - { - Block block = chunk.getWorld().getBlockAt(startLocation.getBlockX() + x, startLocation.getBlockY() + y, startLocation.getBlockZ() + z); - snapshots[x][y][z] = new BlockSnapshot(block.getLocation(), block.getType(), block.getBlockData()); - } - } - } - - //create task to process those data in another thread - Location lesserBoundaryCorner = chunk.getBlock(0, 0, 0).getLocation(); - Location greaterBoundaryCorner = chunk.getBlock(15, 0, 15).getLocation(); - - //create task - //when done processing, this task will create a main thread task to actually update the world with processing results - RestoreNatureProcessingTask task = new RestoreNatureProcessingTask(snapshots, miny, chunk.getWorld().getEnvironment(), lesserBoundaryCorner.getBlock().getBiome(), lesserBoundaryCorner, greaterBoundaryCorner, this.getSeaLevel(chunk.getWorld()), aggressiveMode, GriefPrevention.instance.creativeRulesApply(lesserBoundaryCorner), playerReceivingVisualization); - GriefPrevention.instance.getServer().getScheduler().runTaskLaterAsynchronously(GriefPrevention.instance, task, delayInTicks); - } - - private void parseMaterialListFromConfig(List stringsToParse, MaterialCollection materialCollection) - { - materialCollection.clear(); - - //for each string in the list - for(int i = 0; i < stringsToParse.size(); i++) - { - //try to parse the string value into a material info - MaterialInfo materialInfo = MaterialInfo.fromString(stringsToParse.get(i)); - - //null value returned indicates an error parsing the string from the config file - if(materialInfo == null) - { - //show error in log - GriefPrevention.AddLogEntry("ERROR: Unable to read a material entry from the config file. Please update your config.yml."); - - //update string, which will go out to config file to help user find the error entry - if(!stringsToParse.get(i).contains("can't")) - { - stringsToParse.set(i, stringsToParse.get(i) + " <-- can't understand this entry, see BukkitDev documentation"); - } - } - - //otherwise store the valid entry in config data - else - { - materialCollection.Add(materialInfo); - } - } - } - - public int getSeaLevel(World world) - { - Integer overrideValue = this.config_seaLevelOverride.get(world.getName()); - if(overrideValue == null || overrideValue == -1) - { - return world.getSeaLevel(); - } - else - { - return overrideValue; - } - } - - private static Block getTargetNonAirBlock(Player player, int maxDistance) throws IllegalStateException + { + //build a snapshot of this chunk, including 1 block boundary outside of the chunk all the way around + int maxHeight = chunk.getWorld().getMaxHeight(); + BlockSnapshot[][][] snapshots = new BlockSnapshot[18][maxHeight][18]; + Block startBlock = chunk.getBlock(0, 0, 0); + Location startLocation = new Location(chunk.getWorld(), startBlock.getX() - 1, 0, startBlock.getZ() - 1); + for (int x = 0; x < snapshots.length; x++) + { + for (int z = 0; z < snapshots[0][0].length; z++) + { + for (int y = 0; y < snapshots[0].length; y++) + { + Block block = chunk.getWorld().getBlockAt(startLocation.getBlockX() + x, startLocation.getBlockY() + y, startLocation.getBlockZ() + z); + snapshots[x][y][z] = new BlockSnapshot(block.getLocation(), block.getType(), block.getBlockData()); + } + } + } + + //create task to process those data in another thread + Location lesserBoundaryCorner = chunk.getBlock(0, 0, 0).getLocation(); + Location greaterBoundaryCorner = chunk.getBlock(15, 0, 15).getLocation(); + + //create task + //when done processing, this task will create a main thread task to actually update the world with processing results + RestoreNatureProcessingTask task = new RestoreNatureProcessingTask(snapshots, miny, chunk.getWorld().getEnvironment(), lesserBoundaryCorner.getBlock().getBiome(), lesserBoundaryCorner, greaterBoundaryCorner, this.getSeaLevel(chunk.getWorld()), aggressiveMode, GriefPrevention.instance.creativeRulesApply(lesserBoundaryCorner), playerReceivingVisualization); + GriefPrevention.instance.getServer().getScheduler().runTaskLaterAsynchronously(GriefPrevention.instance, task, delayInTicks); + } + + private void parseMaterialListFromConfig(List stringsToParse, MaterialCollection materialCollection) + { + materialCollection.clear(); + + //for each string in the list + for (int i = 0; i < stringsToParse.size(); i++) + { + //try to parse the string value into a material info + MaterialInfo materialInfo = MaterialInfo.fromString(stringsToParse.get(i)); + + //null value returned indicates an error parsing the string from the config file + if (materialInfo == null) + { + //show error in log + GriefPrevention.AddLogEntry("ERROR: Unable to read a material entry from the config file. Please update your config.yml."); + + //update string, which will go out to config file to help user find the error entry + if (!stringsToParse.get(i).contains("can't")) + { + stringsToParse.set(i, stringsToParse.get(i) + " <-- can't understand this entry, see BukkitDev documentation"); + } + } + + //otherwise store the valid entry in config data + else + { + materialCollection.Add(materialInfo); + } + } + } + + public int getSeaLevel(World world) + { + Integer overrideValue = this.config_seaLevelOverride.get(world.getName()); + if (overrideValue == null || overrideValue == -1) + { + return world.getSeaLevel(); + } else + { + return overrideValue; + } + } + + private static Block getTargetNonAirBlock(Player player, int maxDistance) throws IllegalStateException { BlockIterator iterator = new BlockIterator(player.getLocation(), player.getEyeHeight(), maxDistance); Block result = player.getLocation().getBlock().getRelative(BlockFace.UP); while (iterator.hasNext()) { result = iterator.next(); - if(result.getType() != Material.AIR) return result; + if (result.getType() != Material.AIR) return result; } - + return result; } @@ -3637,20 +3569,20 @@ public class GriefPrevention extends JavaPlugin message = message.replace("\r\n", ""); Pattern ipAddressPattern = Pattern.compile("([0-9]{1,3}\\.){3}[0-9]{1,3}"); Matcher matcher = ipAddressPattern.matcher(message); - + //if it looks like an IP address - if(matcher.find()) + if (matcher.find()) { //and it's not in the list of allowed IP addresses - if(!GriefPrevention.instance.config_spam_allowedIpAddresses.contains(matcher.group())) + if (!GriefPrevention.instance.config_spam_allowedIpAddresses.contains(matcher.group())) { return true; } } - + return false; } - + void autoExtendClaim(Claim newClaim) { //auto-extend it downward to cover anything already built underground @@ -3658,57 +3590,56 @@ public class GriefPrevention extends JavaPlugin Location greaterCorner = newClaim.getGreaterBoundaryCorner(); World world = lesserCorner.getWorld(); ArrayList snapshots = new ArrayList(); - for(int chunkx = lesserCorner.getBlockX() / 16; chunkx <= greaterCorner.getBlockX() / 16; chunkx++) + for (int chunkx = lesserCorner.getBlockX() / 16; chunkx <= greaterCorner.getBlockX() / 16; chunkx++) { - for(int chunkz = lesserCorner.getBlockZ() / 16; chunkz <= greaterCorner.getBlockZ() / 16; chunkz++) + for (int chunkz = lesserCorner.getBlockZ() / 16; chunkz <= greaterCorner.getBlockZ() / 16; chunkz++) { - if(world.isChunkLoaded(chunkx, chunkz)) + if (world.isChunkLoaded(chunkx, chunkz)) { snapshots.add(world.getChunkAt(chunkx, chunkz).getChunkSnapshot(true, true, false)); } } } - + Bukkit.getScheduler().runTaskAsynchronously(GriefPrevention.instance, new AutoExtendClaimTask(newClaim, snapshots, world.getEnvironment())); } public boolean pvpRulesApply(World world) { Boolean configSetting = this.config_pvp_specifiedWorlds.get(world); - if(configSetting != null) return configSetting; + if (configSetting != null) return configSetting; return world.getPVP(); } public static boolean isNewToServer(Player player) { - if( player.getStatistic(Statistic.PICKUP, Material.OAK_LOG) > 0 || + if (player.getStatistic(Statistic.PICKUP, Material.OAK_LOG) > 0 || player.getStatistic(Statistic.PICKUP, Material.SPRUCE_LOG) > 0 || player.getStatistic(Statistic.PICKUP, Material.BIRCH_LOG) > 0 || player.getStatistic(Statistic.PICKUP, Material.JUNGLE_LOG) > 0 || player.getStatistic(Statistic.PICKUP, Material.ACACIA_LOG) > 0 || player.getStatistic(Statistic.PICKUP, Material.DARK_OAK_LOG) > 0) return false; - + PlayerData playerData = instance.dataStore.getPlayerData(player.getUniqueId()); - if(playerData.getClaims().size() > 0) return false; - + if (playerData.getClaims().size() > 0) return false; + return true; } static void banPlayer(Player player, String reason, String source) { - if(GriefPrevention.instance.config_ban_useCommand) + if (GriefPrevention.instance.config_ban_useCommand) { Bukkit.getServer().dispatchCommand( - Bukkit.getConsoleSender(), - GriefPrevention.instance.config_ban_commandFormat.replace("%name%", player.getName()).replace("%reason%", reason)); - } - else + Bukkit.getConsoleSender(), + GriefPrevention.instance.config_ban_commandFormat.replace("%name%", player.getName()).replace("%reason%", reason)); + } else { BanList bans = Bukkit.getServer().getBanList(Type.NAME); bans.addBan(player.getName(), reason, null, source); - + //kick - if(player.isOnline()) + if (player.isOnline()) { player.kickPlayer(reason); } @@ -3717,17 +3648,17 @@ public class GriefPrevention extends JavaPlugin public ItemStack getItemInHand(Player player, EquipmentSlot hand) { - if(hand == EquipmentSlot.OFF_HAND) return player.getInventory().getItemInOffHand(); + if (hand == EquipmentSlot.OFF_HAND) return player.getInventory().getItemInOffHand(); return player.getInventory().getItemInMainHand(); } public boolean claimIsPvPSafeZone(Claim claim) { if (claim.siegeData != null) - return false; + return false; return claim.isAdminClaim() && claim.parent == null && GriefPrevention.instance.config_pvp_noCombatInAdminLandClaims || claim.isAdminClaim() && claim.parent != null && GriefPrevention.instance.config_pvp_noCombatInAdminSubdivisions || - !claim.isAdminClaim() && GriefPrevention.instance.config_pvp_noCombatInPlayerLandClaims; + !claim.isAdminClaim() && GriefPrevention.instance.config_pvp_noCombatInPlayerLandClaims; } /* @@ -3786,17 +3717,18 @@ public class GriefPrevention extends JavaPlugin } */ - //Track scheduled "rescues" so we can cancel them if the player happens to teleport elsewhere so we can cancel it. - ConcurrentHashMap portalReturnTaskMap = new ConcurrentHashMap(); - public void startRescueTask(Player player, Location location) - { - //Schedule task to reset player's portal cooldown after 30 seconds (Maximum timeout time for client, in case their network is slow and taking forever to load chunks) - BukkitTask task = new CheckForPortalTrapTask(player, this, location).runTaskLater(GriefPrevention.instance, 600L); + //Track scheduled "rescues" so we can cancel them if the player happens to teleport elsewhere so we can cancel it. + ConcurrentHashMap portalReturnTaskMap = new ConcurrentHashMap(); - //Cancel existing rescue task - if (portalReturnTaskMap.containsKey(player.getUniqueId())) - portalReturnTaskMap.put(player.getUniqueId(), task).cancel(); - else - portalReturnTaskMap.put(player.getUniqueId(), task); - } + public void startRescueTask(Player player, Location location) + { + //Schedule task to reset player's portal cooldown after 30 seconds (Maximum timeout time for client, in case their network is slow and taking forever to load chunks) + BukkitTask task = new CheckForPortalTrapTask(player, this, location).runTaskLater(GriefPrevention.instance, 600L); + + //Cancel existing rescue task + if (portalReturnTaskMap.containsKey(player.getUniqueId())) + portalReturnTaskMap.put(player.getUniqueId(), task).cancel(); + else + portalReturnTaskMap.put(player.getUniqueId(), task); + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/IgnoreLoaderThread.java b/src/main/java/me/ryanhamshire/GriefPrevention/IgnoreLoaderThread.java index 8b939ca..2d215e3 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/IgnoreLoaderThread.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/IgnoreLoaderThread.java @@ -1,51 +1,51 @@ package me.ryanhamshire.GriefPrevention; +import com.google.common.io.Files; + import java.io.File; import java.nio.charset.Charset; import java.util.List; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import com.google.common.io.Files; - //loads ignore data from file into a hash map class IgnoreLoaderThread extends Thread { private UUID playerToLoad; private ConcurrentHashMap destinationMap; - + IgnoreLoaderThread(UUID playerToLoad, ConcurrentHashMap destinationMap) { this.playerToLoad = playerToLoad; this.destinationMap = destinationMap; this.setPriority(MIN_PRIORITY); } - + @Override public void run() { File ignoreFile = new File(DataStore.playerDataFolderPath + File.separator + this.playerToLoad + ".ignore"); - + //if the file doesn't exist, there's nothing to do here - if(!ignoreFile.exists()) return; - + if (!ignoreFile.exists()) return; + boolean needRetry = false; int retriesRemaining = 5; Exception latestException = null; do { try - { + { needRetry = false; - + //read the file content and immediately close it List lines = Files.readLines(ignoreFile, Charset.forName("UTF-8")); - + //each line is one ignore. asterisks indicate administrative ignores - for(String line : lines) + for (String line : lines) { boolean adminIgnore = false; - if(line.startsWith("*")) + if (line.startsWith("*")) { adminIgnore = true; line = line.substring(1); @@ -55,28 +55,28 @@ class IgnoreLoaderThread extends Thread UUID ignoredUUID = UUID.fromString(line); this.destinationMap.put(ignoredUUID, adminIgnore); } - catch(IllegalArgumentException e){} //if a bad UUID, ignore the line + catch (IllegalArgumentException e) {} //if a bad UUID, ignore the line } } - + //if there's any problem with the file's content, retry up to 5 times with 5 milliseconds between - catch(Exception e) + catch (Exception e) { latestException = e; needRetry = true; retriesRemaining--; } - + try { - if(needRetry) Thread.sleep(5); + if (needRetry) Thread.sleep(5); } - catch(InterruptedException exception) {} - - }while(needRetry && retriesRemaining >= 0); - + catch (InterruptedException exception) {} + + } while (needRetry && retriesRemaining >= 0); + //if last attempt failed, log information about the problem - if(needRetry) + if (needRetry) { GriefPrevention.AddLogEntry("Retry attempts exhausted. Unable to load ignore data for player \"" + playerToLoad.toString() + "\": " + latestException.toString()); latestException.printStackTrace(); diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/IpBanInfo.java b/src/main/java/me/ryanhamshire/GriefPrevention/IpBanInfo.java index 76f1ce3..2d147f1 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/IpBanInfo.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/IpBanInfo.java @@ -22,14 +22,14 @@ import java.net.InetAddress; public class IpBanInfo { - InetAddress address; - long expirationTimestamp; - String bannedAccountName; - - IpBanInfo(InetAddress address, long expirationTimestamp, String bannedAccountName) - { - this.address = address; - this.expirationTimestamp = expirationTimestamp; - this.bannedAccountName = bannedAccountName; - } + InetAddress address; + long expirationTimestamp; + String bannedAccountName; + + IpBanInfo(InetAddress address, long expirationTimestamp, String bannedAccountName) + { + this.address = address; + this.expirationTimestamp = expirationTimestamp; + this.bannedAccountName = bannedAccountName; + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/MaterialCollection.java b/src/main/java/me/ryanhamshire/GriefPrevention/MaterialCollection.java index a1bb447..795c643 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/MaterialCollection.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/MaterialCollection.java @@ -24,31 +24,31 @@ import java.util.Set; //ordered list of material info objects, for fast searching public class MaterialCollection { - Set materials = new HashSet(); - - void Add(MaterialInfo material) - { - this.materials.add(material); - } - - boolean Contains(MaterialInfo material) - { - return this.materials.contains(material); - } - - @Override - public String toString() - { - return materials.toString(); - } - - public int size() - { - return this.materials.size(); - } + Set materials = new HashSet(); - public void clear() - { - this.materials.clear(); - } + void Add(MaterialInfo material) + { + this.materials.add(material); + } + + boolean Contains(MaterialInfo material) + { + return this.materials.contains(material); + } + + @Override + public String toString() + { + return materials.toString(); + } + + public int size() + { + return this.materials.size(); + } + + public void clear() + { + this.materials.clear(); + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/MaterialInfo.java b/src/main/java/me/ryanhamshire/GriefPrevention/MaterialInfo.java index 094b09d..e90f55f 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/MaterialInfo.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/MaterialInfo.java @@ -24,73 +24,72 @@ import org.bukkit.Material; public class MaterialInfo { - Material typeID; - byte data; - boolean allDataValues; - String description; - - public MaterialInfo(Material typeID, byte data, String description) - { - this.typeID = typeID; - this.data = data; - this.allDataValues = false; - this.description = description; - } - - public MaterialInfo(Material typeID, String description) - { - this.typeID = typeID; - this.data = 0; - this.allDataValues = true; - this.description = description; - } - - private MaterialInfo(Material typeID, byte data, boolean allDataValues, String description) - { - this.typeID = typeID; - this.data = data; - this.allDataValues = allDataValues; - this.description = description; - } - - @Override - public String toString() - { - String returnValue = String.valueOf(this.typeID) + ":" + (this.allDataValues?"*":String.valueOf(this.data)); - if(this.description != null) returnValue += ":" + this.description; - - return returnValue; - } - - public static MaterialInfo fromString(String string) - { - if(string == null || string.isEmpty()) return null; - - String [] parts = string.split(":"); - if(parts.length < 3) return null; - - try - { - Material typeID = Material.matchMaterial(parts[0]); - - byte data; - boolean allDataValues; - if(parts[1].equals("*")) - { - allDataValues = true; - data = 0; - } - else - { - allDataValues = false; - data = Byte.parseByte(parts[1]); - } - - return new MaterialInfo(typeID, data, allDataValues, parts[2]); - } - catch(NumberFormatException exception) - { - return null; - } - } + Material typeID; + byte data; + boolean allDataValues; + String description; + + public MaterialInfo(Material typeID, byte data, String description) + { + this.typeID = typeID; + this.data = data; + this.allDataValues = false; + this.description = description; + } + + public MaterialInfo(Material typeID, String description) + { + this.typeID = typeID; + this.data = 0; + this.allDataValues = true; + this.description = description; + } + + private MaterialInfo(Material typeID, byte data, boolean allDataValues, String description) + { + this.typeID = typeID; + this.data = data; + this.allDataValues = allDataValues; + this.description = description; + } + + @Override + public String toString() + { + String returnValue = String.valueOf(this.typeID) + ":" + (this.allDataValues ? "*" : String.valueOf(this.data)); + if (this.description != null) returnValue += ":" + this.description; + + return returnValue; + } + + public static MaterialInfo fromString(String string) + { + if (string == null || string.isEmpty()) return null; + + String[] parts = string.split(":"); + if (parts.length < 3) return null; + + try + { + Material typeID = Material.matchMaterial(parts[0]); + + byte data; + boolean allDataValues; + if (parts[1].equals("*")) + { + allDataValues = true; + data = 0; + } else + { + allDataValues = false; + data = Byte.parseByte(parts[1]); + } + + return new MaterialInfo(typeID, data, allDataValues, parts[2]); + } + catch (NumberFormatException exception) + { + return null; + } + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/PendingItemProtection.java b/src/main/java/me/ryanhamshire/GriefPrevention/PendingItemProtection.java index e19ecac..3d66d16 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/PendingItemProtection.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/PendingItemProtection.java @@ -1,16 +1,17 @@ package me.ryanhamshire.GriefPrevention; -import java.util.UUID; import org.bukkit.Location; import org.bukkit.inventory.ItemStack; +import java.util.UUID; + class PendingItemProtection { public Location location; public UUID owner; long expirationTimestamp; ItemStack itemStack; - + public PendingItemProtection(Location location, UUID owner, long expirationTimestamp, ItemStack itemStack) { this.location = location; diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/PlayerData.java b/src/main/java/me/ryanhamshire/GriefPrevention/PlayerData.java index e87827a..d8fcb68 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/PlayerData.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/PlayerData.java @@ -17,184 +17,176 @@ */ package me.ryanhamshire.GriefPrevention; + +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; + import java.net.InetAddress; import java.util.Calendar; -import java.util.Date; import java.util.UUID; import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; -import me.ryanhamshire.GriefPrevention.Claim; -import me.ryanhamshire.GriefPrevention.GriefPrevention; -import me.ryanhamshire.GriefPrevention.ShovelMode; -import me.ryanhamshire.GriefPrevention.SiegeData; -import me.ryanhamshire.GriefPrevention.Visualization; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; - //holds all of GriefPrevention's player-tied data -public class PlayerData +public class PlayerData { - //the player's ID - public UUID playerID; - - //the player's claims - private Vector claims = null; - - //how many claim blocks the player has earned via play time - private Integer accruedClaimBlocks = null; - - //temporary holding area to avoid opening data files too early - private int newlyAccruedClaimBlocks = 0; - - //where this player was the last time we checked on him for earning claim blocks - public Location lastAfkCheckLocation = null; - - //how many claim blocks the player has been gifted by admins, or purchased via economy integration - private Integer bonusClaimBlocks = null; - - //what "mode" the shovel is in determines what it will do when it's used - public ShovelMode shovelMode = ShovelMode.Basic; - - //radius for restore nature fill mode - int fillRadius = 0; - - //last place the player used the shovel, useful in creating and resizing claims, - //because the player must use the shovel twice in those instances - public Location lastShovelLocation = null; - - //the claim this player is currently resizing - public Claim claimResizing = null; - - //the claim this player is currently subdividing - public Claim claimSubdividing = null; - - //whether or not the player has a pending /trapped rescue - public boolean pendingTrapped = false; - - //whether this player was recently warned about building outside land claims - boolean warnedAboutBuildingOutsideClaims = false; - - //timestamp when last siege ended (where this player was the defender) - long lastSiegeEndTimeStamp = 0; - - //whether the player was kicked (set and used during logout) - boolean wasKicked = false; - - //visualization - public Visualization currentVisualization = null; - - //anti-camping pvp protection - public boolean pvpImmune = false; - public long lastSpawn = 0; - - //ignore claims mode - public boolean ignoreClaims = false; - - //the last claim this player was in, that we know of - public Claim lastClaim = null; - - //siege - public SiegeData siegeData = null; - - //pvp - public long lastPvpTimestamp = 0; - public String lastPvpPlayer = ""; - - //safety confirmation for deleting multi-subdivision claims - public boolean warnedAboutMajorDeletion = false; + //the player's ID + public UUID playerID; - public InetAddress ipAddress; + //the player's claims + private Vector claims = null; + + //how many claim blocks the player has earned via play time + private Integer accruedClaimBlocks = null; + + //temporary holding area to avoid opening data files too early + private int newlyAccruedClaimBlocks = 0; + + //where this player was the last time we checked on him for earning claim blocks + public Location lastAfkCheckLocation = null; + + //how many claim blocks the player has been gifted by admins, or purchased via economy integration + private Integer bonusClaimBlocks = null; + + //what "mode" the shovel is in determines what it will do when it's used + public ShovelMode shovelMode = ShovelMode.Basic; + + //radius for restore nature fill mode + int fillRadius = 0; + + //last place the player used the shovel, useful in creating and resizing claims, + //because the player must use the shovel twice in those instances + public Location lastShovelLocation = null; + + //the claim this player is currently resizing + public Claim claimResizing = null; + + //the claim this player is currently subdividing + public Claim claimSubdividing = null; + + //whether or not the player has a pending /trapped rescue + public boolean pendingTrapped = false; + + //whether this player was recently warned about building outside land claims + boolean warnedAboutBuildingOutsideClaims = false; + + //timestamp when last siege ended (where this player was the defender) + long lastSiegeEndTimeStamp = 0; + + //whether the player was kicked (set and used during logout) + boolean wasKicked = false; + + //visualization + public Visualization currentVisualization = null; + + //anti-camping pvp protection + public boolean pvpImmune = false; + public long lastSpawn = 0; + + //ignore claims mode + public boolean ignoreClaims = false; + + //the last claim this player was in, that we know of + public Claim lastClaim = null; + + //siege + public SiegeData siegeData = null; + + //pvp + public long lastPvpTimestamp = 0; + public String lastPvpPlayer = ""; + + //safety confirmation for deleting multi-subdivision claims + public boolean warnedAboutMajorDeletion = false; + + public InetAddress ipAddress; //for addons to set per-player claim limits. Any negative value will use config's value private int AccruedClaimBlocksLimit = -1; //whether or not this player has received a message about unlocking death drops since his last death - boolean receivedDropUnlockAdvertisement = false; + boolean receivedDropUnlockAdvertisement = false; //whether or not this player's dropped items (on death) are unlocked for other players to pick up - boolean dropsAreUnlocked = false; + boolean dropsAreUnlocked = false; //message to send to player after he respawns - String messageOnRespawn = null; + String messageOnRespawn = null; //player which a pet will be given to when it's right-clicked - OfflinePlayer petGiveawayRecipient = null; - - //timestamp for last "you're building outside your land claims" message - Long buildWarningTimestamp = null; - - //spot where a player can't talk, used to mute new players until they've moved a little - //this is an anti-bot strategy. - Location noChatLocation = null; - - //ignore list - //true means invisible (admin-forced ignore), false means player-created ignore - public ConcurrentHashMap ignoredPlayers = new ConcurrentHashMap(); - public boolean ignoreListChanged = false; + OfflinePlayer petGiveawayRecipient = null; + + //timestamp for last "you're building outside your land claims" message + Long buildWarningTimestamp = null; + + //spot where a player can't talk, used to mute new players until they've moved a little + //this is an anti-bot strategy. + Location noChatLocation = null; + + //ignore list + //true means invisible (admin-forced ignore), false means player-created ignore + public ConcurrentHashMap ignoredPlayers = new ConcurrentHashMap(); + public boolean ignoreListChanged = false; //profanity warning, once per play session - boolean profanityWarned = false; + boolean profanityWarned = false; - //whether or not this player is "in" pvp combat - public boolean inPvpCombat() - { - if(this.lastPvpTimestamp == 0) return false; - - long now = Calendar.getInstance().getTimeInMillis(); - - long elapsed = now - this.lastPvpTimestamp; - - if(elapsed > GriefPrevention.instance.config_pvp_combatTimeoutSeconds * 1000) //X seconds - { - this.lastPvpTimestamp = 0; - return false; - } - - return true; - } - - //the number of claim blocks a player has available for claiming land - public int getRemainingClaimBlocks() - { - int remainingBlocks = this.getAccruedClaimBlocks() + this.getBonusClaimBlocks() + GriefPrevention.instance.dataStore.getGroupBonusBlocks(this.playerID); - for(int i = 0; i < this.getClaims().size(); i++) - { - Claim claim = this.getClaims().get(i); - remainingBlocks -= claim.getArea(); - } - - return remainingBlocks; - } - - //don't load data from secondary storage until it's needed - public synchronized int getAccruedClaimBlocks() - { - if(this.accruedClaimBlocks == null) this.loadDataFromSecondaryStorage(); - - //update claim blocks with any he has accrued during his current play session - if(this.newlyAccruedClaimBlocks > 0) - { - int accruedLimit = this.getAccruedClaimBlocksLimit(); - - //if over the limit before adding blocks, leave it as-is, because the limit may have changed AFTER he accrued the blocks - if(this.accruedClaimBlocks < accruedLimit) - { - //move any in the holding area - int newTotal = this.accruedClaimBlocks + this.newlyAccruedClaimBlocks; - - //respect limits - this.accruedClaimBlocks = Math.min(newTotal, accruedLimit); - } - - this.newlyAccruedClaimBlocks = 0; - return this.accruedClaimBlocks; - } - - return accruedClaimBlocks; + //whether or not this player is "in" pvp combat + public boolean inPvpCombat() + { + if (this.lastPvpTimestamp == 0) return false; + + long now = Calendar.getInstance().getTimeInMillis(); + + long elapsed = now - this.lastPvpTimestamp; + + if (elapsed > GriefPrevention.instance.config_pvp_combatTimeoutSeconds * 1000) //X seconds + { + this.lastPvpTimestamp = 0; + return false; + } + + return true; + } + + //the number of claim blocks a player has available for claiming land + public int getRemainingClaimBlocks() + { + int remainingBlocks = this.getAccruedClaimBlocks() + this.getBonusClaimBlocks() + GriefPrevention.instance.dataStore.getGroupBonusBlocks(this.playerID); + for (int i = 0; i < this.getClaims().size(); i++) + { + Claim claim = this.getClaims().get(i); + remainingBlocks -= claim.getArea(); + } + + return remainingBlocks; + } + + //don't load data from secondary storage until it's needed + public synchronized int getAccruedClaimBlocks() + { + if (this.accruedClaimBlocks == null) this.loadDataFromSecondaryStorage(); + + //update claim blocks with any he has accrued during his current play session + if (this.newlyAccruedClaimBlocks > 0) + { + int accruedLimit = this.getAccruedClaimBlocksLimit(); + + //if over the limit before adding blocks, leave it as-is, because the limit may have changed AFTER he accrued the blocks + if (this.accruedClaimBlocks < accruedLimit) + { + //move any in the holding area + int newTotal = this.accruedClaimBlocks + this.newlyAccruedClaimBlocks; + + //respect limits + this.accruedClaimBlocks = Math.min(newTotal, accruedLimit); + } + + this.newlyAccruedClaimBlocks = 0; + return this.accruedClaimBlocks; + } + + return accruedClaimBlocks; } public void setAccruedClaimBlocks(Integer accruedClaimBlocks) @@ -205,7 +197,7 @@ public class PlayerData public int getBonusClaimBlocks() { - if(this.bonusClaimBlocks == null) this.loadDataFromSecondaryStorage(); + if (this.bonusClaimBlocks == null) this.loadDataFromSecondaryStorage(); return bonusClaimBlocks; } @@ -213,89 +205,87 @@ public class PlayerData { this.bonusClaimBlocks = bonusClaimBlocks; } - + private void loadDataFromSecondaryStorage() { //reach out to secondary storage to get any data there PlayerData storageData = GriefPrevention.instance.dataStore.getPlayerDataFromStorage(this.playerID); - - if(this.accruedClaimBlocks == null) + + if (this.accruedClaimBlocks == null) { - if(storageData.accruedClaimBlocks != null) + if (storageData.accruedClaimBlocks != null) { this.accruedClaimBlocks = storageData.accruedClaimBlocks; //ensure at least minimum accrued are accrued (in case of settings changes to increase initial amount) - if(GriefPrevention.instance.config_advanced_fixNegativeClaimblockAmounts && (this.accruedClaimBlocks < GriefPrevention.instance.config_claims_initialBlocks)) + if (GriefPrevention.instance.config_advanced_fixNegativeClaimblockAmounts && (this.accruedClaimBlocks < GriefPrevention.instance.config_claims_initialBlocks)) { this.accruedClaimBlocks = GriefPrevention.instance.config_claims_initialBlocks; } - - } - else + + } else { this.accruedClaimBlocks = GriefPrevention.instance.config_claims_initialBlocks; } } - - if(this.bonusClaimBlocks == null) + + if (this.bonusClaimBlocks == null) { - if(storageData.bonusClaimBlocks != null) + if (storageData.bonusClaimBlocks != null) { this.bonusClaimBlocks = storageData.bonusClaimBlocks; - } - else + } else { this.bonusClaimBlocks = 0; } } } - + public Vector getClaims() { - if(this.claims == null) + if (this.claims == null) { this.claims = new Vector(); - + //find all the claims belonging to this player and note them for future reference DataStore dataStore = GriefPrevention.instance.dataStore; int totalClaimsArea = 0; - for(int i = 0; i < dataStore.claims.size(); i++) + for (int i = 0; i < dataStore.claims.size(); i++) { Claim claim = dataStore.claims.get(i); - if(!claim.inDataStore) + if (!claim.inDataStore) { dataStore.claims.remove(i--); continue; } - if(playerID.equals(claim.ownerID)) + if (playerID.equals(claim.ownerID)) { this.claims.add(claim); totalClaimsArea += claim.getArea(); } } - + //ensure player has claim blocks for his claims, and at least the minimum accrued this.loadDataFromSecondaryStorage(); - + //if total claimed area is more than total blocks available int totalBlocks = this.accruedClaimBlocks + this.getBonusClaimBlocks() + GriefPrevention.instance.dataStore.getGroupBonusBlocks(this.playerID); - if(GriefPrevention.instance.config_advanced_fixNegativeClaimblockAmounts && totalBlocks < totalClaimsArea) + if (GriefPrevention.instance.config_advanced_fixNegativeClaimblockAmounts && totalBlocks < totalClaimsArea) { OfflinePlayer player = GriefPrevention.instance.getServer().getOfflinePlayer(this.playerID); GriefPrevention.AddLogEntry(player.getName() + " has more claimed land than blocks available. Adding blocks to fix.", CustomLogEntryTypes.Debug, true); GriefPrevention.AddLogEntry(player.getName() + " Accrued blocks: " + this.getAccruedClaimBlocks() + " Bonus blocks: " + this.getBonusClaimBlocks(), CustomLogEntryTypes.Debug, true); GriefPrevention.AddLogEntry("Total blocks: " + totalBlocks + " Total claimed area: " + totalClaimsArea, CustomLogEntryTypes.Debug, true); - for(Claim claim : this.claims) + for (Claim claim : this.claims) { - if(!claim.inDataStore) continue; + if (!claim.inDataStore) continue; GriefPrevention.AddLogEntry( GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner()) + " // " - + GriefPrevention.getfriendlyLocationString(claim.getGreaterBoundaryCorner()) + " = " - + claim.getArea() + + GriefPrevention.getfriendlyLocationString(claim.getGreaterBoundaryCorner()) + " = " + + claim.getArea() , CustomLogEntryTypes.Debug, true); } - + //try to fix it by adding to accrued blocks this.accruedClaimBlocks = totalClaimsArea; //Set accrued blocks to equal total claims int accruedLimit = this.getAccruedClaimBlocksLimit(); @@ -307,7 +297,7 @@ public class PlayerData GriefPrevention.AddLogEntry("New total blocks: " + totalBlocks, CustomLogEntryTypes.Debug, true); //if that didn't fix it, then make up the difference with bonus blocks - if(totalBlocks < totalClaimsArea) + if (totalBlocks < totalClaimsArea) { int bonusBlocksToAdd = totalClaimsArea - totalBlocks; this.bonusClaimBlocks += bonusBlocksToAdd; @@ -320,18 +310,18 @@ public class PlayerData GriefPrevention.AddLogEntry("Remaining claim blocks to use: " + this.getRemainingClaimBlocks() + " (should be 0)", CustomLogEntryTypes.Debug, true); } } - - for(int i = 0; i < this.claims.size(); i++) + + for (int i = 0; i < this.claims.size(); i++) { - if(!claims.get(i).inDataStore) + if (!claims.get(i).inDataStore) { claims.remove(i--); } } - + return claims; } - + //Limit can be changed by addons public int getAccruedClaimBlocksLimit() { diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java b/src/main/java/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java index 5fd025f..7e56983 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java @@ -17,6 +17,7 @@ */ package me.ryanhamshire.GriefPrevention; + import me.ryanhamshire.GriefPrevention.events.VisualizationEvent; import org.bukkit.BanList; import org.bukkit.Bukkit; @@ -38,19 +39,19 @@ import org.bukkit.command.Command; import org.bukkit.entity.AbstractHorse; import org.bukkit.entity.Animals; import org.bukkit.entity.Creature; +import org.bukkit.entity.Donkey; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Fish; import org.bukkit.entity.Hanging; import org.bukkit.entity.Item; +import org.bukkit.entity.Llama; +import org.bukkit.entity.Mule; import org.bukkit.entity.Player; import org.bukkit.entity.Tameable; import org.bukkit.entity.Vehicle; import org.bukkit.entity.minecart.PoweredMinecart; import org.bukkit.entity.minecart.StorageMinecart; -import org.bukkit.entity.Llama; -import org.bukkit.entity.Donkey; -import org.bukkit.entity.Mule; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -102,104 +103,103 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; -class PlayerEventHandler implements Listener +class PlayerEventHandler implements Listener { - private DataStore dataStore; - private GriefPrevention instance; - - //list of temporarily banned ip's - private ArrayList tempBannedIps = new ArrayList(); - - //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(); - - //regex pattern for the "how do i claim land?" scanner - private Pattern howToClaimPattern = null; - - //matcher for banned words - private WordFinder bannedWordFinder; - - //spam tracker - SpamDetector spamDetector = new SpamDetector(); + private DataStore dataStore; + private GriefPrevention instance; - //typical constructor, yawn - PlayerEventHandler(DataStore dataStore, GriefPrevention plugin) - { - this.dataStore = dataStore; - this.instance = plugin; - bannedWordFinder = new WordFinder(instance.dataStore.loadBannedWords()); - } - - //when a player chats, monitor for spam - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - synchronized void onPlayerChat (AsyncPlayerChatEvent event) - { - Player player = event.getPlayer(); - if(!player.isOnline()) - { - event.setCancelled(true); - return; - } - - String message = event.getMessage(); - - boolean muted = this.handlePlayerChat(player, message, event); - Set recipients = event.getRecipients(); - - //muted messages go out to only the sender - if(muted) - { - recipients.clear(); - recipients.add(player); - } - - //soft muted messages go out to all soft muted players - else if(this.dataStore.isSoftMuted(player.getUniqueId())) - { - String notificationMessage = "(Muted " + player.getName() + "): " + message; - Set recipientsToKeep = new HashSet(); - for(Player recipient : recipients) - { - if(this.dataStore.isSoftMuted(recipient.getUniqueId())) - { - recipientsToKeep.add(recipient); - } - else if(recipient.hasPermission("griefprevention.eavesdrop")) - { - recipient.sendMessage(ChatColor.GRAY + notificationMessage); - } - } - recipients.clear(); - recipients.addAll(recipientsToKeep); - - instance.AddLogEntry(notificationMessage, CustomLogEntryTypes.MutedChat, false); - } - - //troll and excessive profanity filter - else if(!player.hasPermission("griefprevention.spam") && this.bannedWordFinder.hasMatch(message)) + //list of temporarily banned ip's + private ArrayList tempBannedIps = new ArrayList(); + + //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(); + + //regex pattern for the "how do i claim land?" scanner + private Pattern howToClaimPattern = null; + + //matcher for banned words + private WordFinder bannedWordFinder; + + //spam tracker + SpamDetector spamDetector = new SpamDetector(); + + //typical constructor, yawn + PlayerEventHandler(DataStore dataStore, GriefPrevention plugin) + { + this.dataStore = dataStore; + this.instance = plugin; + bannedWordFinder = new WordFinder(instance.dataStore.loadBannedWords()); + } + + //when a player chats, monitor for spam + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + synchronized void onPlayerChat(AsyncPlayerChatEvent event) + { + Player player = event.getPlayer(); + if (!player.isOnline()) { - //allow admins to see the soft-muted text - String notificationMessage = "(Muted " + player.getName() + "): " + message; - for(Player recipient : recipients) + event.setCancelled(true); + return; + } + + String message = event.getMessage(); + + boolean muted = this.handlePlayerChat(player, message, event); + Set recipients = event.getRecipients(); + + //muted messages go out to only the sender + if (muted) + { + recipients.clear(); + recipients.add(player); + } + + //soft muted messages go out to all soft muted players + else if (this.dataStore.isSoftMuted(player.getUniqueId())) + { + String notificationMessage = "(Muted " + player.getName() + "): " + message; + Set recipientsToKeep = new HashSet(); + for (Player recipient : recipients) { - if(recipient.hasPermission("griefprevention.eavesdrop")) + if (this.dataStore.isSoftMuted(recipient.getUniqueId())) + { + recipientsToKeep.add(recipient); + } else if (recipient.hasPermission("griefprevention.eavesdrop")) { recipient.sendMessage(ChatColor.GRAY + notificationMessage); } } - - //limit recipients to sender - recipients.clear(); + recipients.clear(); + recipients.addAll(recipientsToKeep); + + instance.AddLogEntry(notificationMessage, CustomLogEntryTypes.MutedChat, false); + } + + //troll and excessive profanity filter + else if (!player.hasPermission("griefprevention.spam") && this.bannedWordFinder.hasMatch(message)) + { + //allow admins to see the soft-muted text + String notificationMessage = "(Muted " + player.getName() + "): " + message; + for (Player recipient : recipients) + { + if (recipient.hasPermission("griefprevention.eavesdrop")) + { + recipient.sendMessage(ChatColor.GRAY + notificationMessage); + } + } + + //limit recipients to sender + recipients.clear(); recipients.add(player); - - //if player not new warn for the first infraction per play session. - if(!instance.isNewToServer(player)) + + //if player not new warn for the first infraction per play session. + if (!instance.isNewToServer(player)) { PlayerData playerData = instance.dataStore.getPlayerData(player.getUniqueId()); - if(!playerData.profanityWarned) + if (!playerData.profanityWarned) { playerData.profanityWarned = true; instance.sendMessage(player, TextMode.Err, Messages.NoProfanity); @@ -207,170 +207,166 @@ class PlayerEventHandler implements Listener return; } } - + //otherwise assume chat troll and mute all chat from this sender until an admin says otherwise - else if(instance.config_trollFilterEnabled) + else if (instance.config_trollFilterEnabled) { - instance.AddLogEntry("Auto-muted new player " + player.getName() + " for profanity shortly after join. Use /SoftMute to undo.", CustomLogEntryTypes.AdminActivity); + instance.AddLogEntry("Auto-muted new player " + player.getName() + " for profanity shortly after join. Use /SoftMute to undo.", CustomLogEntryTypes.AdminActivity); instance.AddLogEntry(notificationMessage, CustomLogEntryTypes.MutedChat, false); instance.dataStore.toggleSoftMute(player.getUniqueId()); } } - - //remaining messages - else - { - //enter in abridged chat logs - makeSocialLogEntry(player.getName(), message); - - //based on ignore lists, remove some of the audience - if(!player.hasPermission("griefprevention.notignorable")) - { - Set recipientsToRemove = new HashSet(); - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - for(Player recipient : recipients) - { - if(!recipient.hasPermission("griefprevention.notignorable")) - { - if(playerData.ignoredPlayers.containsKey(recipient.getUniqueId())) - { - recipientsToRemove.add(recipient); - } - else - { - PlayerData targetPlayerData = this.dataStore.getPlayerData(recipient.getUniqueId()); - if(targetPlayerData.ignoredPlayers.containsKey(player.getUniqueId())) - { - recipientsToRemove.add(recipient); - } - } - } - } - - recipients.removeAll(recipientsToRemove); - } - } - } - - //returns true if the message should be muted, true if it should be sent - private boolean handlePlayerChat(Player player, String message, PlayerEvent event) - { - //FEATURE: automatically educate players about claiming land - //watching for message format how*claim*, and will send a link to the basics video - if(this.howToClaimPattern == null) - { - this.howToClaimPattern = Pattern.compile(this.dataStore.getMessage(Messages.HowToClaimRegex), Pattern.CASE_INSENSITIVE); - } - if(this.howToClaimPattern.matcher(message).matches()) - { - if(instance.creativeRulesApply(player.getLocation())) - { - instance.sendMessage(player, TextMode.Info, Messages.CreativeBasicsVideo2, 10L, DataStore.CREATIVE_VIDEO_URL); - } - else - { - instance.sendMessage(player, TextMode.Info, Messages.SurvivalBasicsVideo2, 10L, DataStore.SURVIVAL_VIDEO_URL); - } - } - - //FEATURE: automatically educate players about the /trapped command - //check for "trapped" or "stuck" to educate players about the /trapped command - String trappedwords = this.dataStore.getMessage( - Messages.TrappedChatKeyword - ); - if (!trappedwords.isEmpty()) { - String[] checkWords = trappedwords.split(";"); - - for (String checkWord : checkWords) { - if (!message.contains("/trapped") - && message.contains(checkWord)) - { - instance.sendMessage( - player, - TextMode.Info, - Messages.TrappedInstructions, - 10L - ); - break; - } - } - } - - //FEATURE: monitor for chat and command spam - - if(!instance.config_spam_enabled) return false; - - //if the player has permission to spam, don't bother even examining the message - if(player.hasPermission("griefprevention.spam")) return false; - - //examine recent messages to detect spam - SpamAnalysisResult result = this.spamDetector.AnalyzeMessage(player.getUniqueId(), message, System.currentTimeMillis()); - - //apply any needed changes to message (like lowercasing all-caps) - if(event instanceof AsyncPlayerChatEvent) + //remaining messages + else { - ((AsyncPlayerChatEvent)event).setMessage(result.finalMessage); + //enter in abridged chat logs + makeSocialLogEntry(player.getName(), message); + + //based on ignore lists, remove some of the audience + if (!player.hasPermission("griefprevention.notignorable")) + { + Set recipientsToRemove = new HashSet(); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + for (Player recipient : recipients) + { + if (!recipient.hasPermission("griefprevention.notignorable")) + { + if (playerData.ignoredPlayers.containsKey(recipient.getUniqueId())) + { + recipientsToRemove.add(recipient); + } else + { + PlayerData targetPlayerData = this.dataStore.getPlayerData(recipient.getUniqueId()); + if (targetPlayerData.ignoredPlayers.containsKey(player.getUniqueId())) + { + recipientsToRemove.add(recipient); + } + } + } + } + + recipients.removeAll(recipientsToRemove); + } } - - //don't allow new players to chat after logging in until they move - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - if(playerData.noChatLocation != null) + } + + //returns true if the message should be muted, true if it should be sent + private boolean handlePlayerChat(Player player, String message, PlayerEvent event) + { + //FEATURE: automatically educate players about claiming land + //watching for message format how*claim*, and will send a link to the basics video + if (this.howToClaimPattern == null) + { + this.howToClaimPattern = Pattern.compile(this.dataStore.getMessage(Messages.HowToClaimRegex), Pattern.CASE_INSENSITIVE); + } + + if (this.howToClaimPattern.matcher(message).matches()) + { + if (instance.creativeRulesApply(player.getLocation())) + { + instance.sendMessage(player, TextMode.Info, Messages.CreativeBasicsVideo2, 10L, DataStore.CREATIVE_VIDEO_URL); + } else + { + instance.sendMessage(player, TextMode.Info, Messages.SurvivalBasicsVideo2, 10L, DataStore.SURVIVAL_VIDEO_URL); + } + } + + //FEATURE: automatically educate players about the /trapped command + //check for "trapped" or "stuck" to educate players about the /trapped command + String trappedwords = this.dataStore.getMessage( + Messages.TrappedChatKeyword + ); + if (!trappedwords.isEmpty()) + { + String[] checkWords = trappedwords.split(";"); + + for (String checkWord : checkWords) + { + if (!message.contains("/trapped") + && message.contains(checkWord)) + { + instance.sendMessage( + player, + TextMode.Info, + Messages.TrappedInstructions, + 10L + ); + break; + } + } + } + + //FEATURE: monitor for chat and command spam + + if (!instance.config_spam_enabled) return false; + + //if the player has permission to spam, don't bother even examining the message + if (player.hasPermission("griefprevention.spam")) return false; + + //examine recent messages to detect spam + SpamAnalysisResult result = this.spamDetector.AnalyzeMessage(player.getUniqueId(), message, System.currentTimeMillis()); + + //apply any needed changes to message (like lowercasing all-caps) + if (event instanceof AsyncPlayerChatEvent) + { + ((AsyncPlayerChatEvent) event).setMessage(result.finalMessage); + } + + //don't allow new players to chat after logging in until they move + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + if (playerData.noChatLocation != null) { Location currentLocation = player.getLocation(); - if(currentLocation.getBlockX() == playerData.noChatLocation.getBlockX() && - currentLocation.getBlockZ() == playerData.noChatLocation.getBlockZ()) + if (currentLocation.getBlockX() == playerData.noChatLocation.getBlockX() && + currentLocation.getBlockZ() == playerData.noChatLocation.getBlockZ()) { instance.sendMessage(player, TextMode.Err, Messages.NoChatUntilMove, 10L); result.muteReason = "pre-movement chat"; - } - else + } else { playerData.noChatLocation = null; } } - + //filter IP addresses - if(result.muteReason == null) + if (result.muteReason == null) { - if(instance.containsBlockedIP(message)) + if (instance.containsBlockedIP(message)) { //block message result.muteReason = "IP address"; } } - + //take action based on spam detector results - if(result.shouldBanChatter) + if (result.shouldBanChatter) { - if(instance.config_spam_banOffenders) + if (instance.config_spam_banOffenders) { //log entry instance.AddLogEntry("Banning " + player.getName() + " for spam.", CustomLogEntryTypes.AdminActivity); - + //kick and ban - PlayerKickBanTask task = new PlayerKickBanTask(player, instance.config_spam_banMessage, "GriefPrevention Anti-Spam",true); + PlayerKickBanTask task = new PlayerKickBanTask(player, instance.config_spam_banMessage, "GriefPrevention Anti-Spam", true); instance.getServer().getScheduler().scheduleSyncDelayedTask(instance, task, 1L); - } - else + } else { //log entry instance.AddLogEntry("Kicking " + player.getName() + " for spam.", CustomLogEntryTypes.AdminActivity); - + //just kick PlayerKickBanTask task = new PlayerKickBanTask(player, "", "GriefPrevention Anti-Spam", false); - instance.getServer().getScheduler().scheduleSyncDelayedTask(instance, task, 1L); + instance.getServer().getScheduler().scheduleSyncDelayedTask(instance, task, 1L); } - } - - else if(result.shouldWarnChatter) + } else if (result.shouldWarnChatter) { //warn and log instance.sendMessage(player, TextMode.Warn, instance.config_spam_warningMessage, 10L); instance.AddLogEntry("Warned " + player.getName() + " about spam penalties.", CustomLogEntryTypes.Debug, true); } - - if(result.muteReason != null) + + if (result.muteReason != null) { //mute and log instance.AddLogEntry("Muted " + result.muteReason + "."); @@ -378,235 +374,236 @@ class PlayerEventHandler implements Listener return true; } - + return false; - } - - //when a player uses a slash command... - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - synchronized void onPlayerCommandPreprocess (PlayerCommandPreprocessEvent event) - { - String message = event.getMessage(); - String [] args = message.split(" "); - - String command = args[0].toLowerCase(); - - CommandCategory category = this.getCommandCategory(command); - - Player player = event.getPlayer(); - PlayerData playerData = null; - - //if a whisper - if(category == CommandCategory.Whisper && args.length > 1) - { - //determine target player, might be NULL + } + + //when a player uses a slash command... + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + synchronized void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) + { + String message = event.getMessage(); + String[] args = message.split(" "); + + String command = args[0].toLowerCase(); + + CommandCategory category = this.getCommandCategory(command); + + Player player = event.getPlayer(); + PlayerData playerData = null; + + //if a whisper + if (category == CommandCategory.Whisper && args.length > 1) + { + //determine target player, might be NULL @SuppressWarnings("deprecation") Player targetPlayer = instance.getServer().getPlayer(args[1]); - + //softmute feature - if(this.dataStore.isSoftMuted(player.getUniqueId()) && targetPlayer != null && !this.dataStore.isSoftMuted(targetPlayer.getUniqueId())) + if (this.dataStore.isSoftMuted(player.getUniqueId()) && targetPlayer != null && !this.dataStore.isSoftMuted(targetPlayer.getUniqueId())) { event.setCancelled(true); return; } - + //if eavesdrop enabled and sender doesn't have the eavesdrop immunity permission, eavesdrop - if(instance.config_whisperNotifications && !player.hasPermission("griefprevention.eavesdropimmune")) - { + if (instance.config_whisperNotifications && !player.hasPermission("griefprevention.eavesdropimmune")) + { //except for when the recipient has eavesdrop immunity - if(targetPlayer == null || !targetPlayer.hasPermission("griefprevention.eavesdropimmune")) + if (targetPlayer == null || !targetPlayer.hasPermission("griefprevention.eavesdropimmune")) { StringBuilder logMessageBuilder = new StringBuilder(); - logMessageBuilder.append("[[").append(event.getPlayer().getName()).append("]] "); - - for(int i = 1; i < args.length; i++) - { - logMessageBuilder.append(args[i]).append(" "); - } - - String logMessage = logMessageBuilder.toString(); - - @SuppressWarnings("unchecked") - Collection players = (Collection)instance.getServer().getOnlinePlayers(); - for(Player onlinePlayer : players) - { - if(onlinePlayer.hasPermission("griefprevention.eavesdrop") && !onlinePlayer.equals(targetPlayer) && !onlinePlayer.equals(player)) - { - onlinePlayer.sendMessage(ChatColor.GRAY + logMessage); - } - } + logMessageBuilder.append("[[").append(event.getPlayer().getName()).append("]] "); + + for (int i = 1; i < args.length; i++) + { + logMessageBuilder.append(args[i]).append(" "); + } + + String logMessage = logMessageBuilder.toString(); + + @SuppressWarnings("unchecked") + Collection players = (Collection) instance.getServer().getOnlinePlayers(); + for (Player onlinePlayer : players) + { + if (onlinePlayer.hasPermission("griefprevention.eavesdrop") && !onlinePlayer.equals(targetPlayer) && !onlinePlayer.equals(player)) + { + onlinePlayer.sendMessage(ChatColor.GRAY + logMessage); + } + } } - } - - //ignore feature - if(targetPlayer != null && targetPlayer.isOnline()) + } + + //ignore feature + if (targetPlayer != null && targetPlayer.isOnline()) { //if either is ignoring the other, cancel this command playerData = this.dataStore.getPlayerData(player.getUniqueId()); - if(playerData.ignoredPlayers.containsKey(targetPlayer.getUniqueId()) && !targetPlayer.hasPermission("griefprevention.notignorable")) + if (playerData.ignoredPlayers.containsKey(targetPlayer.getUniqueId()) && !targetPlayer.hasPermission("griefprevention.notignorable")) { event.setCancelled(true); instance.sendMessage(player, TextMode.Err, Messages.IsIgnoringYou); return; } - + PlayerData targetPlayerData = this.dataStore.getPlayerData(targetPlayer.getUniqueId()); - if(targetPlayerData.ignoredPlayers.containsKey(player.getUniqueId()) && !player.hasPermission("griefprevention.notignorable")) + if (targetPlayerData.ignoredPlayers.containsKey(player.getUniqueId()) && !player.hasPermission("griefprevention.notignorable")) { event.setCancelled(true); instance.sendMessage(player, TextMode.Err, Messages.IsIgnoringYou); return; } } - } - - //if in pvp, block any pvp-banned slash commands - if(playerData == null) playerData = this.dataStore.getPlayerData(event.getPlayer().getUniqueId()); + } - if((playerData.inPvpCombat() || playerData.siegeData != null) && instance.config_pvp_blockedCommands.contains(command)) - { - event.setCancelled(true); - instance.sendMessage(event.getPlayer(), TextMode.Err, Messages.CommandBannedInPvP); - return; - } - - //soft mute for chat slash commands - if(category == CommandCategory.Chat && this.dataStore.isSoftMuted(player.getUniqueId())) + //if in pvp, block any pvp-banned slash commands + if (playerData == null) playerData = this.dataStore.getPlayerData(event.getPlayer().getUniqueId()); + + if ((playerData.inPvpCombat() || playerData.siegeData != null) && instance.config_pvp_blockedCommands.contains(command)) + { + event.setCancelled(true); + instance.sendMessage(event.getPlayer(), TextMode.Err, Messages.CommandBannedInPvP); + return; + } + + //soft mute for chat slash commands + if (category == CommandCategory.Chat && this.dataStore.isSoftMuted(player.getUniqueId())) { event.setCancelled(true); return; } - - //if the slash command used is in the list of monitored commands, treat it like a chat message (see above) - boolean isMonitoredCommand = (category == CommandCategory.Chat || category == CommandCategory.Whisper); - if(isMonitoredCommand) - { - //if anti spam enabled, check for spam - if(instance.config_spam_enabled) - { - event.setCancelled(this.handlePlayerChat(event.getPlayer(), event.getMessage(), event)); - } - - if(!player.hasPermission("griefprevention.spam") && this.bannedWordFinder.hasMatch(message)) - { - event.setCancelled(true); - } - - //unless cancelled, log in abridged logs - if(!event.isCancelled()) - { - StringBuilder builder = new StringBuilder(); - for(String arg : args) - { - builder.append(arg + " "); - } - - makeSocialLogEntry(event.getPlayer().getName(), builder.toString()); - } - } - - //if requires access trust, check for permission - isMonitoredCommand = false; - String lowerCaseMessage = message.toLowerCase(); - for(String monitoredCommand : instance.config_claims_commandsRequiringAccessTrust) + + //if the slash command used is in the list of monitored commands, treat it like a chat message (see above) + boolean isMonitoredCommand = (category == CommandCategory.Chat || category == CommandCategory.Whisper); + if (isMonitoredCommand) { - if(lowerCaseMessage.startsWith(monitoredCommand)) + //if anti spam enabled, check for spam + if (instance.config_spam_enabled) + { + event.setCancelled(this.handlePlayerChat(event.getPlayer(), event.getMessage(), event)); + } + + if (!player.hasPermission("griefprevention.spam") && this.bannedWordFinder.hasMatch(message)) + { + event.setCancelled(true); + } + + //unless cancelled, log in abridged logs + if (!event.isCancelled()) + { + StringBuilder builder = new StringBuilder(); + for (String arg : args) + { + builder.append(arg + " "); + } + + makeSocialLogEntry(event.getPlayer().getName(), builder.toString()); + } + } + + //if requires access trust, check for permission + isMonitoredCommand = false; + String lowerCaseMessage = message.toLowerCase(); + for (String monitoredCommand : instance.config_claims_commandsRequiringAccessTrust) + { + if (lowerCaseMessage.startsWith(monitoredCommand)) { isMonitoredCommand = true; break; } } - - if(isMonitoredCommand) + + if (isMonitoredCommand) { Claim claim = this.dataStore.getClaimAt(player.getLocation(), false, playerData.lastClaim); - if(claim != null) + if (claim != null) { playerData.lastClaim = claim; - String reason = claim.allowAccess(player); - if(reason != null) + String reason = claim.allowAccess(player); + if (reason != null) { instance.sendMessage(player, TextMode.Err, reason); event.setCancelled(true); } } } - } - - private ConcurrentHashMap commandCategoryMap = new ConcurrentHashMap(); - private CommandCategory getCommandCategory(String commandName) - { - if(commandName.startsWith("/")) commandName = commandName.substring(1); - - //if we've seen this command or alias before, return the category determined previously - CommandCategory category = this.commandCategoryMap.get(commandName); - if(category != null) return category; - - //otherwise build a list of all the aliases of this command across all installed plugins - HashSet aliases = new HashSet(); - aliases.add(commandName); - aliases.add("minecraft:" + commandName); - for(Plugin plugin : Bukkit.getServer().getPluginManager().getPlugins()) + } + + private ConcurrentHashMap commandCategoryMap = new ConcurrentHashMap(); + + private CommandCategory getCommandCategory(String commandName) + { + if (commandName.startsWith("/")) commandName = commandName.substring(1); + + //if we've seen this command or alias before, return the category determined previously + CommandCategory category = this.commandCategoryMap.get(commandName); + if (category != null) return category; + + //otherwise build a list of all the aliases of this command across all installed plugins + HashSet aliases = new HashSet(); + aliases.add(commandName); + aliases.add("minecraft:" + commandName); + for (Plugin plugin : Bukkit.getServer().getPluginManager().getPlugins()) { if (!(plugin instanceof JavaPlugin)) continue; - JavaPlugin javaPlugin = (JavaPlugin)plugin; + JavaPlugin javaPlugin = (JavaPlugin) plugin; Command command = javaPlugin.getCommand(commandName); - if(command != null) + if (command != null) { aliases.add(command.getName().toLowerCase()); aliases.add(plugin.getName().toLowerCase() + ":" + command.getName().toLowerCase()); - for(String alias : command.getAliases()) + for (String alias : command.getAliases()) { aliases.add(alias.toLowerCase()); aliases.add(plugin.getName().toLowerCase() + ":" + alias.toLowerCase()); } } } - - //also consider vanilla commands - Command command = Bukkit.getServer().getPluginCommand(commandName); - if(command != null) + + //also consider vanilla commands + Command command = Bukkit.getServer().getPluginCommand(commandName); + if (command != null) { aliases.add(command.getName().toLowerCase()); aliases.add("minecraft:" + command.getName().toLowerCase()); - for(String alias : command.getAliases()) + for (String alias : command.getAliases()) { aliases.add(alias.toLowerCase()); aliases.add("minecraft:" + alias.toLowerCase()); } } - - //if any of those aliases are in the chat list or whisper list, then we know the category for that command - category = CommandCategory.None; - for(String alias : aliases) - { - if(instance.config_eavesdrop_whisperCommands.contains("/" + alias)) - { - category = CommandCategory.Whisper; - } - else if(instance.config_spam_monitorSlashCommands.contains("/" + alias)) - { - category = CommandCategory.Chat; - } - - //remember the categories for later - this.commandCategoryMap.put(alias.toLowerCase(), category); - } - - return category; + + //if any of those aliases are in the chat list or whisper list, then we know the category for that command + category = CommandCategory.None; + for (String alias : aliases) + { + if (instance.config_eavesdrop_whisperCommands.contains("/" + alias)) + { + category = CommandCategory.Whisper; + } else if (instance.config_spam_monitorSlashCommands.contains("/" + alias)) + { + category = CommandCategory.Chat; + } + + //remember the categories for later + this.commandCategoryMap.put(alias.toLowerCase(), category); + } + + return category; } static int longestNameLength = 10; - static void makeSocialLogEntry(String name, String message) - { + + static void makeSocialLogEntry(String name, String message) + { StringBuilder entryBuilder = new StringBuilder(name); - for(int i = name.length(); i < longestNameLength; i++) + for (int i = name.length(); i < longestNameLength; i++) { entryBuilder.append(' '); } entryBuilder.append(": " + message); - + longestNameLength = Math.max(longestNameLength, name.length()); //TODO: cleanup static GriefPrevention.AddLogEntry(entryBuilder.toString(), CustomLogEntryTypes.SocialActivity, true); @@ -614,232 +611,232 @@ class PlayerEventHandler implements Listener private ConcurrentHashMap lastLoginThisServerSessionMap = new ConcurrentHashMap(); - //when a player attempts to join the server... - @EventHandler(priority = EventPriority.HIGHEST) - void onPlayerLogin (PlayerLoginEvent event) - { - Player player = event.getPlayer(); - - //all this is anti-spam code - if(instance.config_spam_enabled) - { - //FEATURE: login cooldown to prevent login/logout spam with custom clients - long now = Calendar.getInstance().getTimeInMillis(); - - //if allowed to join and login cooldown enabled - if(instance.config_spam_loginCooldownSeconds > 0 && event.getResult() == Result.ALLOWED && !player.hasPermission("griefprevention.spam")) - { - //determine how long since last login and cooldown remaining - Date lastLoginThisSession = lastLoginThisServerSessionMap.get(player.getUniqueId()); - if(lastLoginThisSession != null) - { - long millisecondsSinceLastLogin = now - lastLoginThisSession.getTime(); - long secondsSinceLastLogin = millisecondsSinceLastLogin / 1000; - long cooldownRemaining = instance.config_spam_loginCooldownSeconds - secondsSinceLastLogin; + //when a player attempts to join the server... + @EventHandler(priority = EventPriority.HIGHEST) + void onPlayerLogin(PlayerLoginEvent event) + { + Player player = event.getPlayer(); - //if cooldown remaining - if(cooldownRemaining > 0) - { - //DAS BOOT! - event.setResult(Result.KICK_OTHER); - event.setKickMessage("You must wait " + cooldownRemaining + " seconds before logging-in again."); - event.disallow(event.getResult(), event.getKickMessage()); - return; - } - } - } - - //if logging-in account is banned, remember IP address for later - if(instance.config_smartBan && event.getResult() == Result.KICK_BANNED) - { - this.tempBannedIps.add(new IpBanInfo(event.getAddress(), now + this.MILLISECONDS_IN_DAY, player.getName())); - } - } - - //remember the player's ip address - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - playerData.ipAddress = event.getAddress(); - } - - //when a player successfully joins the server... - @SuppressWarnings("deprecation") + //all this is anti-spam code + if (instance.config_spam_enabled) + { + //FEATURE: login cooldown to prevent login/logout spam with custom clients + long now = Calendar.getInstance().getTimeInMillis(); + + //if allowed to join and login cooldown enabled + if (instance.config_spam_loginCooldownSeconds > 0 && event.getResult() == Result.ALLOWED && !player.hasPermission("griefprevention.spam")) + { + //determine how long since last login and cooldown remaining + Date lastLoginThisSession = lastLoginThisServerSessionMap.get(player.getUniqueId()); + if (lastLoginThisSession != null) + { + long millisecondsSinceLastLogin = now - lastLoginThisSession.getTime(); + long secondsSinceLastLogin = millisecondsSinceLastLogin / 1000; + long cooldownRemaining = instance.config_spam_loginCooldownSeconds - secondsSinceLastLogin; + + //if cooldown remaining + if (cooldownRemaining > 0) + { + //DAS BOOT! + event.setResult(Result.KICK_OTHER); + event.setKickMessage("You must wait " + cooldownRemaining + " seconds before logging-in again."); + event.disallow(event.getResult(), event.getKickMessage()); + return; + } + } + } + + //if logging-in account is banned, remember IP address for later + if (instance.config_smartBan && event.getResult() == Result.KICK_BANNED) + { + this.tempBannedIps.add(new IpBanInfo(event.getAddress(), now + this.MILLISECONDS_IN_DAY, player.getName())); + } + } + + //remember the player's ip address + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + playerData.ipAddress = event.getAddress(); + } + + //when a player successfully joins the server... + @SuppressWarnings("deprecation") @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) - void onPlayerJoin(PlayerJoinEvent event) - { - Player player = event.getPlayer(); - UUID playerID = player.getUniqueId(); - - //note login time - Date nowDate = new Date(); + void onPlayerJoin(PlayerJoinEvent event) + { + Player player = event.getPlayer(); + UUID playerID = player.getUniqueId(); + + //note login time + Date nowDate = new Date(); long now = nowDate.getTime(); - PlayerData playerData = this.dataStore.getPlayerData(playerID); - playerData.lastSpawn = now; - this.lastLoginThisServerSessionMap.put(playerID, nowDate); - - //if newish, prevent chat until he's moved a bit to prove he's not a bot - if(instance.isNewToServer(player)) - { - playerData.noChatLocation = player.getLocation(); - } - - //if player has never played on the server before... - if(!player.hasPlayedBefore()) - { - //may need pvp protection - instance.checkPvpProtectionNeeded(player); - - //if in survival claims mode, send a message about the claim basics video (except for admins - assumed experts) - if(instance.config_claims_worldModes.get(player.getWorld()) == ClaimsMode.Survival && !player.hasPermission("griefprevention.adminclaims") && this.dataStore.claims.size() > 10) - { - WelcomeTask task = new WelcomeTask(player); - Bukkit.getScheduler().scheduleSyncDelayedTask(instance, task, instance.config_claims_manualDeliveryDelaySeconds * 20L); - } - } - - //silence notifications when they're coming too fast - if(event.getJoinMessage() != null && this.shouldSilenceNotification()) - { - event.setJoinMessage(null); - } - - //FEATURE: auto-ban accounts who use an IP address which was very recently used by another banned account - if(instance.config_smartBan && !player.hasPlayedBefore()) - { - //search temporarily banned IP addresses for this one - for(int i = 0; i < this.tempBannedIps.size(); i++) - { - IpBanInfo info = this.tempBannedIps.get(i); - String address = info.address.toString(); - - //eliminate any expired entries - if(now > info.expirationTimestamp) - { - this.tempBannedIps.remove(i--); - } - - //if we find a match - else if(address.equals(playerData.ipAddress.toString())) - { - //if the account associated with the IP ban has been pardoned, remove all ip bans for that ip and we're done - OfflinePlayer bannedPlayer = instance.getServer().getOfflinePlayer(info.bannedAccountName); - if(!bannedPlayer.isBanned()) - { - for(int j = 0; j < this.tempBannedIps.size(); j++) - { - IpBanInfo info2 = this.tempBannedIps.get(j); - if(info2.address.toString().equals(address)) - { - OfflinePlayer bannedAccount = instance.getServer().getOfflinePlayer(info2.bannedAccountName); - instance.getServer().getBanList(BanList.Type.NAME).pardon(bannedAccount.getName()); - this.tempBannedIps.remove(j--); - } - } - - break; - } - - //otherwise if that account is still banned, ban this account, too - else - { - instance.AddLogEntry("Auto-banned new player " + player.getName() + " because that account is using an IP address very recently used by banned player " + info.bannedAccountName + " (" + info.address.toString() + ").", CustomLogEntryTypes.AdminActivity); - - //notify any online ops - @SuppressWarnings("unchecked") - Collection players = (Collection)instance.getServer().getOnlinePlayers(); - for(Player otherPlayer : players) - { - if(otherPlayer.isOp()) - { - instance.sendMessage(otherPlayer, TextMode.Success, Messages.AutoBanNotify, player.getName(), info.bannedAccountName); - } - } - - //ban player - PlayerKickBanTask task = new PlayerKickBanTask(player, "", "GriefPrevention Smart Ban - Shared Login:" + info.bannedAccountName, true); - instance.getServer().getScheduler().scheduleSyncDelayedTask(instance, task, 10L); - - //silence join message - event.setJoinMessage(""); - - break; - } - } - } - } - - //in case player has changed his name, on successful login, update UUID > Name mapping - instance.cacheUUIDNamePair(player.getUniqueId(), player.getName()); - - //ensure we're not over the limit for this IP address + PlayerData playerData = this.dataStore.getPlayerData(playerID); + playerData.lastSpawn = now; + this.lastLoginThisServerSessionMap.put(playerID, nowDate); + + //if newish, prevent chat until he's moved a bit to prove he's not a bot + if (instance.isNewToServer(player)) + { + playerData.noChatLocation = player.getLocation(); + } + + //if player has never played on the server before... + if (!player.hasPlayedBefore()) + { + //may need pvp protection + instance.checkPvpProtectionNeeded(player); + + //if in survival claims mode, send a message about the claim basics video (except for admins - assumed experts) + if (instance.config_claims_worldModes.get(player.getWorld()) == ClaimsMode.Survival && !player.hasPermission("griefprevention.adminclaims") && this.dataStore.claims.size() > 10) + { + WelcomeTask task = new WelcomeTask(player); + Bukkit.getScheduler().scheduleSyncDelayedTask(instance, task, instance.config_claims_manualDeliveryDelaySeconds * 20L); + } + } + + //silence notifications when they're coming too fast + if (event.getJoinMessage() != null && this.shouldSilenceNotification()) + { + event.setJoinMessage(null); + } + + //FEATURE: auto-ban accounts who use an IP address which was very recently used by another banned account + if (instance.config_smartBan && !player.hasPlayedBefore()) + { + //search temporarily banned IP addresses for this one + for (int i = 0; i < this.tempBannedIps.size(); i++) + { + IpBanInfo info = this.tempBannedIps.get(i); + String address = info.address.toString(); + + //eliminate any expired entries + if (now > info.expirationTimestamp) + { + this.tempBannedIps.remove(i--); + } + + //if we find a match + else if (address.equals(playerData.ipAddress.toString())) + { + //if the account associated with the IP ban has been pardoned, remove all ip bans for that ip and we're done + OfflinePlayer bannedPlayer = instance.getServer().getOfflinePlayer(info.bannedAccountName); + if (!bannedPlayer.isBanned()) + { + for (int j = 0; j < this.tempBannedIps.size(); j++) + { + IpBanInfo info2 = this.tempBannedIps.get(j); + if (info2.address.toString().equals(address)) + { + OfflinePlayer bannedAccount = instance.getServer().getOfflinePlayer(info2.bannedAccountName); + instance.getServer().getBanList(BanList.Type.NAME).pardon(bannedAccount.getName()); + this.tempBannedIps.remove(j--); + } + } + + break; + } + + //otherwise if that account is still banned, ban this account, too + else + { + instance.AddLogEntry("Auto-banned new player " + player.getName() + " because that account is using an IP address very recently used by banned player " + info.bannedAccountName + " (" + info.address.toString() + ").", CustomLogEntryTypes.AdminActivity); + + //notify any online ops + @SuppressWarnings("unchecked") + Collection players = (Collection) instance.getServer().getOnlinePlayers(); + for (Player otherPlayer : players) + { + if (otherPlayer.isOp()) + { + instance.sendMessage(otherPlayer, TextMode.Success, Messages.AutoBanNotify, player.getName(), info.bannedAccountName); + } + } + + //ban player + PlayerKickBanTask task = new PlayerKickBanTask(player, "", "GriefPrevention Smart Ban - Shared Login:" + info.bannedAccountName, true); + instance.getServer().getScheduler().scheduleSyncDelayedTask(instance, task, 10L); + + //silence join message + event.setJoinMessage(""); + + break; + } + } + } + } + + //in case player has changed his name, on successful login, update UUID > Name mapping + instance.cacheUUIDNamePair(player.getUniqueId(), player.getName()); + + //ensure we're not over the limit for this IP address InetAddress ipAddress = playerData.ipAddress; - if(ipAddress != null) + if (ipAddress != null) { int ipLimit = instance.config_ipLimit; - if(ipLimit > 0 && instance.isNewToServer(player)) + if (ipLimit > 0 && instance.isNewToServer(player)) { int ipCount = 0; - + @SuppressWarnings("unchecked") - Collection players = (Collection)instance.getServer().getOnlinePlayers(); - for(Player onlinePlayer : players) + Collection players = (Collection) instance.getServer().getOnlinePlayers(); + for (Player onlinePlayer : players) { - if(onlinePlayer.getUniqueId().equals(player.getUniqueId())) continue; - + if (onlinePlayer.getUniqueId().equals(player.getUniqueId())) continue; + PlayerData otherData = instance.dataStore.getPlayerData(onlinePlayer.getUniqueId()); - if(ipAddress.equals(otherData.ipAddress) && instance.isNewToServer(onlinePlayer)) + if (ipAddress.equals(otherData.ipAddress) && instance.isNewToServer(onlinePlayer)) { ipCount++; } } - - if(ipCount >= ipLimit) + + if (ipCount >= ipLimit) { //kick player PlayerKickBanTask task = new PlayerKickBanTask(player, instance.dataStore.getMessage(Messages.TooMuchIpOverlap), "GriefPrevention IP-sharing limit.", false); instance.getServer().getScheduler().scheduleSyncDelayedTask(instance, task, 100L); - + //silence join message event.setJoinMessage(null); return; } } } - + //create a thread to load ignore information new IgnoreLoaderThread(playerID, playerData.ignoredPlayers).start(); - - //is he stuck in a portal frame? - if (player.hasMetadata("GP_PORTALRESCUE")) - { - //If so, let him know and rescue him in 10 seconds. If he is in fact not trapped, hopefully chunks will have loaded by this time so he can walk out. - instance.sendMessage(player, TextMode.Info, Messages.NetherPortalTrapDetectionMessage, 20L); - new BukkitRunnable() - { - @Override - public void run() - { - if (player.getPortalCooldown() > 8 && player.hasMetadata("GP_PORTALRESCUE")) - { - instance.AddLogEntry("Rescued " + player.getName() + " from a nether portal.\nTeleported from " + player.getLocation().toString() + " to " + ((Location)player.getMetadata("GP_PORTALRESCUE").get(0).value()).toString(), CustomLogEntryTypes.Debug); - player.teleport((Location)player.getMetadata("GP_PORTALRESCUE").get(0).value()); - player.removeMetadata("GP_PORTALRESCUE", instance); - } - } - }.runTaskLater(instance, 200L); - } - //Otherwise just reset cooldown, just in case they happened to logout again... - else - player.setPortalCooldown(0); - + //is he stuck in a portal frame? + if (player.hasMetadata("GP_PORTALRESCUE")) + { + //If so, let him know and rescue him in 10 seconds. If he is in fact not trapped, hopefully chunks will have loaded by this time so he can walk out. + instance.sendMessage(player, TextMode.Info, Messages.NetherPortalTrapDetectionMessage, 20L); + new BukkitRunnable() + { + @Override + public void run() + { + if (player.getPortalCooldown() > 8 && player.hasMetadata("GP_PORTALRESCUE")) + { + instance.AddLogEntry("Rescued " + player.getName() + " from a nether portal.\nTeleported from " + player.getLocation().toString() + " to " + ((Location) player.getMetadata("GP_PORTALRESCUE").get(0).value()).toString(), CustomLogEntryTypes.Debug); + player.teleport((Location) player.getMetadata("GP_PORTALRESCUE").get(0).value()); + player.removeMetadata("GP_PORTALRESCUE", instance); + } + } + }.runTaskLater(instance, 200L); + } + //Otherwise just reset cooldown, just in case they happened to logout again... + else + player.setPortalCooldown(0); + + //if we're holding a logout message for this player, don't send that or this event's join message - if(instance.config_spam_logoutMessageDelaySeconds > 0) + if (instance.config_spam_logoutMessageDelaySeconds > 0) { String joinMessage = event.getJoinMessage(); - if(joinMessage != null && !joinMessage.isEmpty()) + if (joinMessage != null && !joinMessage.isEmpty()) { Integer taskID = this.heldLogoutMessages.get(player.getUniqueId()); - if(taskID != null && Bukkit.getScheduler().isQueued(taskID)) + if (taskID != null && Bukkit.getScheduler().isQueued(taskID)) { Bukkit.getScheduler().cancelTask(taskID); player.sendMessage(event.getJoinMessage()); @@ -847,132 +844,134 @@ class PlayerEventHandler implements Listener } } } - } + } - //when a player spawns, conditionally apply temporary pvp protection + //when a player spawns, conditionally apply temporary pvp protection @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - void onPlayerRespawn (PlayerRespawnEvent event) + void onPlayerRespawn(PlayerRespawnEvent event) { Player player = event.getPlayer(); PlayerData playerData = instance.dataStore.getPlayerData(player.getUniqueId()); playerData.lastSpawn = Calendar.getInstance().getTimeInMillis(); playerData.lastPvpTimestamp = 0; //no longer in pvp combat - + //also send him any messaged from grief prevention he would have received while dead - if(playerData.messageOnRespawn != null) + if (playerData.messageOnRespawn != null) { instance.sendMessage(player, ChatColor.RESET /*color is alrady embedded in message in this case*/, playerData.messageOnRespawn, 40L); playerData.messageOnRespawn = null; } - + instance.checkPvpProtectionNeeded(player); } - - //when a player dies... - private HashMap deathTimestamps = new HashMap(); + + //when a player dies... + private HashMap deathTimestamps = new HashMap(); + @EventHandler(priority = EventPriority.HIGHEST) - void onPlayerDeath(PlayerDeathEvent event) - { - //FEATURE: prevent death message spam by implementing a "cooldown period" for death messages - Player player = event.getEntity(); - Long lastDeathTime = this.deathTimestamps.get(player.getUniqueId()); - long now = Calendar.getInstance().getTimeInMillis(); - if(lastDeathTime != null && now - lastDeathTime < instance.config_spam_deathMessageCooldownSeconds * 1000) - { - player.sendMessage(event.getDeathMessage()); //let the player assume his death message was broadcasted to everyone - event.setDeathMessage(""); - } - - this.deathTimestamps.put(player.getUniqueId(), now); - - //these are related to locking dropped items on death to prevent theft - PlayerData playerData = instance.dataStore.getPlayerData(player.getUniqueId()); - playerData.dropsAreUnlocked = false; - playerData.receivedDropUnlockAdvertisement = false; - } - - //when a player gets kicked... - @EventHandler(priority = EventPriority.HIGHEST) - void onPlayerKicked(PlayerKickEvent event) + void onPlayerDeath(PlayerDeathEvent event) { - Player player = event.getPlayer(); - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - playerData.wasKicked = true; + //FEATURE: prevent death message spam by implementing a "cooldown period" for death messages + Player player = event.getEntity(); + Long lastDeathTime = this.deathTimestamps.get(player.getUniqueId()); + long now = Calendar.getInstance().getTimeInMillis(); + if (lastDeathTime != null && now - lastDeathTime < instance.config_spam_deathMessageCooldownSeconds * 1000) + { + player.sendMessage(event.getDeathMessage()); //let the player assume his death message was broadcasted to everyone + event.setDeathMessage(""); + } + + this.deathTimestamps.put(player.getUniqueId(), now); + + //these are related to locking dropped items on death to prevent theft + PlayerData playerData = instance.dataStore.getPlayerData(player.getUniqueId()); + playerData.dropsAreUnlocked = false; + playerData.receivedDropUnlockAdvertisement = false; } - - //when a player quits... - private HashMap heldLogoutMessages = new HashMap(); - @EventHandler(priority = EventPriority.HIGHEST) - void onPlayerQuit(PlayerQuitEvent event) - { - Player player = event.getPlayer(); - UUID playerID = player.getUniqueId(); - PlayerData playerData = this.dataStore.getPlayerData(playerID); - boolean isBanned; - //If player is not trapped in a portal and has a pending rescue task, remove the associated metadata - //Why 9? No idea why, but this is decremented by 1 when the player disconnects. - if (player.getPortalCooldown() < 9) - { - player.removeMetadata("GP_PORTALRESCUE", instance); - } + //when a player gets kicked... + @EventHandler(priority = EventPriority.HIGHEST) + void onPlayerKicked(PlayerKickEvent event) + { + Player player = event.getPlayer(); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + playerData.wasKicked = true; + } - if(playerData.wasKicked) - { - isBanned = player.isBanned(); - } - else - { - isBanned = false; - } - - //if banned, add IP to the temporary IP ban list - if(isBanned && playerData.ipAddress != null) - { - 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); - } - - //silence notifications when the player is banned - if(isBanned) - { - event.setQuitMessage(null); - } - - //make sure his data is all saved - he might have accrued some claim blocks while playing that were not saved immediately - else - { - this.dataStore.savePlayerData(player.getUniqueId(), playerData); - } - - //FEATURE: players in pvp combat when they log out will die - if(instance.config_pvp_punishLogout && playerData.inPvpCombat()) + //when a player quits... + private HashMap heldLogoutMessages = new HashMap(); + + @EventHandler(priority = EventPriority.HIGHEST) + void onPlayerQuit(PlayerQuitEvent event) + { + Player player = event.getPlayer(); + UUID playerID = player.getUniqueId(); + PlayerData playerData = this.dataStore.getPlayerData(playerID); + boolean isBanned; + + //If player is not trapped in a portal and has a pending rescue task, remove the associated metadata + //Why 9? No idea why, but this is decremented by 1 when the player disconnects. + if (player.getPortalCooldown() < 9) + { + player.removeMetadata("GP_PORTALRESCUE", instance); + } + + if (playerData.wasKicked) + { + isBanned = player.isBanned(); + } else + { + isBanned = false; + } + + //if banned, add IP to the temporary IP ban list + if (isBanned && playerData.ipAddress != null) + { + 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); + } + + //silence notifications when the player is banned + if (isBanned) + { + event.setQuitMessage(null); + } + + //make sure his data is all saved - he might have accrued some claim blocks while playing that were not saved immediately + else + { + this.dataStore.savePlayerData(player.getUniqueId(), playerData); + } + + //FEATURE: players in pvp combat when they log out will die + if (instance.config_pvp_punishLogout && playerData.inPvpCombat()) { player.setHealth(0); } - + //FEATURE: during a siege, any player who logs out dies and forfeits the siege - + //if player was involved in a siege, he forfeits - if(playerData.siegeData != null) + if (playerData.siegeData != null) { - if(player.getHealth() > 0) player.setHealth(0); //might already be zero from above, this avoids a double death message + if (player.getHealth() > 0) + player.setHealth(0); //might already be zero from above, this avoids a double death message } - + //drop data about this player this.dataStore.clearCachedPlayerData(playerID); - + //send quit message later, but only if the player stays offline - if(instance.config_spam_logoutMessageDelaySeconds > 0) + if (instance.config_spam_logoutMessageDelaySeconds > 0) { String quitMessage = event.getQuitMessage(); - if(quitMessage != null && !quitMessage.isEmpty()) + if (quitMessage != null && !quitMessage.isEmpty()) { BroadcastMessageTask task = new BroadcastMessageTask(quitMessage); int taskID = Bukkit.getScheduler().scheduleSyncDelayedTask(instance, task, 20L * instance.config_spam_logoutMessageDelaySeconds); @@ -980,1141 +979,1141 @@ class PlayerEventHandler implements Listener event.setQuitMessage(""); } } - } - - //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() - { - if (instance.config_spam_loginLogoutNotificationsPerMinute <= 0) - { - return false; // not silencing login/logout notifications - } + } - final long ONE_MINUTE = 60000; - Long now = Calendar.getInstance().getTimeInMillis(); - - //eliminate any expired entries (longer than a minute ago) - for(int i = 0; i < this.recentLoginLogoutNotifications.size(); i++) - { - 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() > instance.config_spam_loginLogoutNotificationsPerMinute; - } + //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() + { + if (instance.config_spam_loginLogoutNotificationsPerMinute <= 0) + { + return false; // not silencing login/logout notifications + } - //when a player drops an item - @EventHandler(priority = EventPriority.LOWEST) - public void onPlayerDropItem(PlayerDropItemEvent event) - { - Player player = event.getPlayer(); - - //in creative worlds, dropping items is blocked - if(instance.creativeRulesApply(player.getLocation())) - { - event.setCancelled(true); - return; - } - - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - - //FEATURE: players under siege or in PvP combat, can't throw items on the ground to hide - //them or give them away to other players before they are defeated - - //if in combat, don't let him drop it - if(!instance.config_pvp_allowCombatItemDrop && playerData.inPvpCombat()) - { - instance.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) - { - instance.sendMessage(player, TextMode.Err, Messages.SiegeNoDrop); - event.setCancelled(true); - } - } - - //when a player teleports via a portal - @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) - void onPlayerPortal(PlayerPortalEvent event) - { - //if the player isn't going anywhere, take no action - if(event.getTo() == null || event.getTo().getWorld() == null) return; - - Player player = event.getPlayer(); - if(event.getCause() == TeleportCause.NETHER_PORTAL) + final long ONE_MINUTE = 60000; + Long now = Calendar.getInstance().getTimeInMillis(); + + //eliminate any expired entries (longer than a minute ago) + for (int i = 0; i < this.recentLoginLogoutNotifications.size(); i++) + { + 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() > instance.config_spam_loginLogoutNotificationsPerMinute; + } + + //when a player drops an item + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerDropItem(PlayerDropItemEvent event) + { + Player player = event.getPlayer(); + + //in creative worlds, dropping items is blocked + if (instance.creativeRulesApply(player.getLocation())) + { + event.setCancelled(true); + return; + } + + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + + //FEATURE: players under siege or in PvP combat, can't throw items on the ground to hide + //them or give them away to other players before they are defeated + + //if in combat, don't let him drop it + if (!instance.config_pvp_allowCombatItemDrop && playerData.inPvpCombat()) + { + instance.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) + { + instance.sendMessage(player, TextMode.Err, Messages.SiegeNoDrop); + event.setCancelled(true); + } + } + + //when a player teleports via a portal + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) + void onPlayerPortal(PlayerPortalEvent event) + { + //if the player isn't going anywhere, take no action + if (event.getTo() == null || event.getTo().getWorld() == null) return; + + Player player = event.getPlayer(); + if (event.getCause() == TeleportCause.NETHER_PORTAL) { //FEATURE: when players get trapped in a nether portal, send them back through to the other side - instance.startRescueTask(player, player.getLocation()); + instance.startRescueTask(player, player.getLocation()); - //don't track in worlds where claims are not enabled - if(!instance.claimsEnabledForWorld(event.getTo().getWorld())) return; + //don't track in worlds where claims are not enabled + if (!instance.claimsEnabledForWorld(event.getTo().getWorld())) return; } - } - - //when a player teleports - @EventHandler(priority = EventPriority.LOWEST) - public void onPlayerTeleport(PlayerTeleportEvent event) - { - Player player = event.getPlayer(); - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - - //FEATURE: prevent players from using ender pearls to gain access to secured claims - TeleportCause cause = event.getCause(); - if(cause == TeleportCause.CHORUS_FRUIT || (cause == TeleportCause.ENDER_PEARL && instance.config_claims_enderPearlsRequireAccessTrust)) - { - Claim toClaim = this.dataStore.getClaimAt(event.getTo(), false, playerData.lastClaim); - if(toClaim != null) - { - playerData.lastClaim = toClaim; - String noAccessReason = toClaim.allowAccess(player); - if(noAccessReason != null) - { - instance.sendMessage(player, TextMode.Err, noAccessReason); - event.setCancelled(true); - if(cause == TeleportCause.ENDER_PEARL) - player.getInventory().addItem(new ItemStack(Material.ENDER_PEARL)); - } - } - } - - //FEATURE: prevent teleport abuse to win sieges - - //these rules only apply to siege worlds only - if(!instance.config_siege_enabledWorlds.contains(player.getWorld())) return; - - //these rules do not apply to admins - if(player.hasPermission("griefprevention.siegeteleport")) return; + } - //Ignore vanilla teleports (usually corrective teleports? See issue #210) - if(event.getCause() == TeleportCause.UNKNOWN) return; - - Location source = event.getFrom(); - Claim sourceClaim = this.dataStore.getClaimAt(source, false, playerData.lastClaim); - if(sourceClaim != null && sourceClaim.siegeData != null) - { - instance.sendMessage(player, TextMode.Err, Messages.SiegeNoTeleport); - event.setCancelled(true); - return; - } - - Location destination = event.getTo(); - Claim destinationClaim = this.dataStore.getClaimAt(destination, false, null); - if(destinationClaim != null && destinationClaim.siegeData != null) - { - instance.sendMessage(player, TextMode.Err, Messages.BesiegedNoTeleport); - event.setCancelled(true); - return; - } - } - - //when a player interacts with a specific part of entity... + //when a player teleports + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerTeleport(PlayerTeleportEvent event) + { + Player player = event.getPlayer(); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + + //FEATURE: prevent players from using ender pearls to gain access to secured claims + TeleportCause cause = event.getCause(); + if (cause == TeleportCause.CHORUS_FRUIT || (cause == TeleportCause.ENDER_PEARL && instance.config_claims_enderPearlsRequireAccessTrust)) + { + Claim toClaim = this.dataStore.getClaimAt(event.getTo(), false, playerData.lastClaim); + if (toClaim != null) + { + playerData.lastClaim = toClaim; + String noAccessReason = toClaim.allowAccess(player); + if (noAccessReason != null) + { + instance.sendMessage(player, TextMode.Err, noAccessReason); + event.setCancelled(true); + if (cause == TeleportCause.ENDER_PEARL) + player.getInventory().addItem(new ItemStack(Material.ENDER_PEARL)); + } + } + } + + //FEATURE: prevent teleport abuse to win sieges + + //these rules only apply to siege worlds only + if (!instance.config_siege_enabledWorlds.contains(player.getWorld())) return; + + //these rules do not apply to admins + if (player.hasPermission("griefprevention.siegeteleport")) return; + + //Ignore vanilla teleports (usually corrective teleports? See issue #210) + if (event.getCause() == TeleportCause.UNKNOWN) return; + + Location source = event.getFrom(); + Claim sourceClaim = this.dataStore.getClaimAt(source, false, playerData.lastClaim); + if (sourceClaim != null && sourceClaim.siegeData != null) + { + instance.sendMessage(player, TextMode.Err, Messages.SiegeNoTeleport); + event.setCancelled(true); + return; + } + + Location destination = event.getTo(); + Claim destinationClaim = this.dataStore.getClaimAt(destination, false, null); + if (destinationClaim != null && destinationClaim.siegeData != null) + { + instance.sendMessage(player, TextMode.Err, Messages.BesiegedNoTeleport); + event.setCancelled(true); + return; + } + } + + //when a player interacts with a specific part of entity... @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onPlayerInteractAtEntity(PlayerInteractAtEntityEvent event) { //treat it the same as interacting with an entity in general - if(event.getRightClicked().getType() == EntityType.ARMOR_STAND) + if (event.getRightClicked().getType() == EntityType.ARMOR_STAND) { - this.onPlayerInteractEntity((PlayerInteractEntityEvent)event); + this.onPlayerInteractEntity((PlayerInteractEntityEvent) event); } } - - //when a player interacts with an entity... - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerInteractEntity(PlayerInteractEntityEvent event) - { - Player player = event.getPlayer(); - Entity entity = event.getRightClicked(); - - if(!instance.claimsEnabledForWorld(entity.getWorld())) return; - - //allow horse protection to be overridden to allow management from other plugins - if (!instance.config_claims_protectHorses && entity instanceof AbstractHorse ) return; - if (!instance.config_claims_protectDonkeys && entity instanceof Donkey) return; - if (!instance.config_claims_protectDonkeys && entity instanceof Mule) return; - if (!instance.config_claims_protectLlamas && entity instanceof Llama ) return; + + //when a player interacts with an entity... + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPlayerInteractEntity(PlayerInteractEntityEvent event) + { + Player player = event.getPlayer(); + Entity entity = event.getRightClicked(); + + if (!instance.claimsEnabledForWorld(entity.getWorld())) return; + + //allow horse protection to be overridden to allow management from other plugins + if (!instance.config_claims_protectHorses && entity instanceof AbstractHorse) return; + if (!instance.config_claims_protectDonkeys && entity instanceof Donkey) return; + if (!instance.config_claims_protectDonkeys && entity instanceof Mule) return; + if (!instance.config_claims_protectLlamas && entity instanceof Llama) return; PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - - //if entity is tameable and has an owner, apply special rules - if(entity instanceof Tameable) + + //if entity is tameable and has an owner, apply special rules + if (entity instanceof Tameable) { - Tameable tameable = (Tameable)entity; - if(tameable.isTamed()) + Tameable tameable = (Tameable) entity; + if (tameable.isTamed()) { - if(tameable.getOwner() != null) + if (tameable.getOwner() != null) { - UUID ownerID = tameable.getOwner().getUniqueId(); - - //if the player interacting is the owner or an admin in ignore claims mode, always allow - if(player.getUniqueId().equals(ownerID) || playerData.ignoreClaims) - { - //if giving away pet, do that instead - if(playerData.petGiveawayRecipient != null) - { - tameable.setOwner(playerData.petGiveawayRecipient); - playerData.petGiveawayRecipient = null; - instance.sendMessage(player, TextMode.Success, Messages.PetGiveawayConfirmation); - event.setCancelled(true); - } - - return; - } - if(!instance.pvpRulesApply(entity.getLocation().getWorld()) || instance.config_pvp_protectPets) - { - //otherwise disallow - OfflinePlayer owner = instance.getServer().getOfflinePlayer(ownerID); - String ownerName = owner.getName(); - if(ownerName == null) ownerName = "someone"; - String message = instance.dataStore.getMessage(Messages.NotYourPet, ownerName); - if(player.hasPermission("griefprevention.ignoreclaims")) - message += " " + instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); - instance.sendMessage(player, TextMode.Err, message); - event.setCancelled(true); - return; - } + UUID ownerID = tameable.getOwner().getUniqueId(); + + //if the player interacting is the owner or an admin in ignore claims mode, always allow + if (player.getUniqueId().equals(ownerID) || playerData.ignoreClaims) + { + //if giving away pet, do that instead + if (playerData.petGiveawayRecipient != null) + { + tameable.setOwner(playerData.petGiveawayRecipient); + playerData.petGiveawayRecipient = null; + instance.sendMessage(player, TextMode.Success, Messages.PetGiveawayConfirmation); + event.setCancelled(true); + } + + return; + } + if (!instance.pvpRulesApply(entity.getLocation().getWorld()) || instance.config_pvp_protectPets) + { + //otherwise disallow + OfflinePlayer owner = instance.getServer().getOfflinePlayer(ownerID); + String ownerName = owner.getName(); + if (ownerName == null) ownerName = "someone"; + String message = instance.dataStore.getMessage(Messages.NotYourPet, ownerName); + if (player.hasPermission("griefprevention.ignoreclaims")) + message += " " + instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); + instance.sendMessage(player, TextMode.Err, message); + event.setCancelled(true); + return; + } } - } - else //world repair code for a now-fixed GP bug //TODO: necessary anymore? + } else //world repair code for a now-fixed GP bug //TODO: necessary anymore? { //ensure this entity can be tamed by players tameable.setOwner(null); - if(tameable instanceof InventoryHolder) + if (tameable instanceof InventoryHolder) { - InventoryHolder holder = (InventoryHolder)tameable; + InventoryHolder holder = (InventoryHolder) tameable; holder.getInventory().clear(); } } } - + //don't allow interaction with item frames or armor stands in claimed areas without build permission - if(entity.getType() == EntityType.ARMOR_STAND || entity instanceof Hanging) - { - String noBuildReason = instance.allowBuild(player, entity.getLocation(), Material.ITEM_FRAME); - if(noBuildReason != null) - { - instance.sendMessage(player, TextMode.Err, noBuildReason); - event.setCancelled(true); - return; - } - } - - //limit armor placements when entity count is too high - if(entity.getType() == EntityType.ARMOR_STAND && instance.creativeRulesApply(player.getLocation())) - { - if(playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); + if (entity.getType() == EntityType.ARMOR_STAND || entity instanceof Hanging) + { + String noBuildReason = instance.allowBuild(player, entity.getLocation(), Material.ITEM_FRAME); + if (noBuildReason != null) + { + instance.sendMessage(player, TextMode.Err, noBuildReason); + event.setCancelled(true); + return; + } + } + + //limit armor placements when entity count is too high + if (entity.getType() == EntityType.ARMOR_STAND && instance.creativeRulesApply(player.getLocation())) + { + if (playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); Claim claim = this.dataStore.getClaimAt(entity.getLocation(), false, playerData.lastClaim); - if(claim == null) return; - + if (claim == null) return; + String noEntitiesReason = claim.allowMoreEntities(false); - if(noEntitiesReason != null) + if (noEntitiesReason != null) { instance.sendMessage(player, TextMode.Err, noEntitiesReason); event.setCancelled(true); return; } - } - - //always allow interactions when player is in ignore claims mode - if(playerData.ignoreClaims) return; - + } + + //always allow interactions when player is in ignore claims mode + if (playerData.ignoreClaims) return; + //don't allow container access during pvp combat - if((entity instanceof StorageMinecart || entity instanceof PoweredMinecart)) + if ((entity instanceof StorageMinecart || entity instanceof PoweredMinecart)) { - if(playerData.siegeData != null) + if (playerData.siegeData != null) { instance.sendMessage(player, TextMode.Err, Messages.SiegeNoContainers); event.setCancelled(true); return; } - - if(playerData.inPvpCombat()) + + if (playerData.inPvpCombat()) { - instance.sendMessage(player, TextMode.Err, Messages.PvPNoContainers); + instance.sendMessage(player, TextMode.Err, Messages.PvPNoContainers); event.setCancelled(true); return; - } + } } - - //if the entity is a vehicle and we're preventing theft in claims - if(instance.config_claims_preventTheft && entity instanceof Vehicle) - { - //if the entity is in a claim - Claim claim = this.dataStore.getClaimAt(entity.getLocation(), false, null); - if(claim != null) - { - //for storage entities, apply container rules (this is a potential theft) - if(entity instanceof InventoryHolder) - { - String noContainersReason = claim.allowContainers(player); - if(noContainersReason != null) - { - instance.sendMessage(player, TextMode.Err, noContainersReason); - event.setCancelled(true); - return; - } - } - } - } - - //if the entity is an animal, apply container rules - if((instance.config_claims_preventTheft && (entity instanceof Animals || entity instanceof Fish)) || (entity.getType() == EntityType.VILLAGER && instance.config_claims_villagerTradingRequiresTrust)) + + //if the entity is a vehicle and we're preventing theft in claims + if (instance.config_claims_preventTheft && entity instanceof Vehicle) { //if the entity is in a claim Claim claim = this.dataStore.getClaimAt(entity.getLocation(), false, null); - if(claim != null) + if (claim != null) { - if(claim.allowContainers(player) != null) + //for storage entities, apply container rules (this is a potential theft) + if (entity instanceof InventoryHolder) + { + String noContainersReason = claim.allowContainers(player); + if (noContainersReason != null) + { + instance.sendMessage(player, TextMode.Err, noContainersReason); + event.setCancelled(true); + return; + } + } + } + } + + //if the entity is an animal, apply container rules + if ((instance.config_claims_preventTheft && (entity instanceof Animals || entity instanceof Fish)) || (entity.getType() == EntityType.VILLAGER && instance.config_claims_villagerTradingRequiresTrust)) + { + //if the entity is in a claim + Claim claim = this.dataStore.getClaimAt(entity.getLocation(), false, null); + if (claim != null) + { + if (claim.allowContainers(player) != null) { String message = instance.dataStore.getMessage(Messages.NoDamageClaimedEntity, claim.getOwnerName()); - if(player.hasPermission("griefprevention.ignoreclaims")) + if (player.hasPermission("griefprevention.ignoreclaims")) message += " " + instance.dataStore.getMessage(Messages.IgnoreClaimsAdvertisement); - instance.sendMessage(player, TextMode.Err, message); + instance.sendMessage(player, TextMode.Err, message); event.setCancelled(true); return; } } } - - //if preventing theft, prevent leashing claimed creatures - if(instance.config_claims_preventTheft && entity instanceof Creature && instance.getItemInHand(player, event.getHand()).getType() == Material.LEAD) - { - Claim claim = this.dataStore.getClaimAt(entity.getLocation(), false, playerData.lastClaim); - if(claim != null) + + //if preventing theft, prevent leashing claimed creatures + if (instance.config_claims_preventTheft && entity instanceof Creature && instance.getItemInHand(player, event.getHand()).getType() == Material.LEAD) + { + Claim claim = this.dataStore.getClaimAt(entity.getLocation(), false, playerData.lastClaim); + if (claim != null) { String failureReason = claim.allowContainers(player); - if(failureReason != null) + if (failureReason != null) { event.setCancelled(true); - instance.sendMessage(player, TextMode.Err, failureReason); - return; + instance.sendMessage(player, TextMode.Err, failureReason); + return; } } - } - } - - //when a player reels in his fishing rod - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerFish(PlayerFishEvent event) - { - Entity entity = event.getCaught(); - if(entity == null) return; //if nothing pulled, uninteresting event - - //if should be protected from pulling in land claims without permission - if(entity.getType() == EntityType.ARMOR_STAND || entity instanceof Animals) - { - Player player = event.getPlayer(); - PlayerData playerData = instance.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = instance.dataStore.getClaimAt(entity.getLocation(), false, playerData.lastClaim); - if(claim != null) - { - //if no permission, cancel - String errorMessage = claim.allowContainers(player); - if(errorMessage != null) - { - event.setCancelled(true); - instance.sendMessage(player, TextMode.Err, Messages.NoDamageClaimedEntity, claim.getOwnerName()); - return; - } - } - } - } - - //when a player picks up an item... - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerPickupItem(PlayerPickupItemEvent event) - { - Player player = event.getPlayer(); + } + } - //FEATURE: lock dropped items to player who dropped them - - //who owns this stack? - Item item = event.getItem(); - List data = item.getMetadata("GP_ITEMOWNER"); - if(data != null && data.size() > 0) - { - UUID ownerID = (UUID)data.get(0).value(); - - //has that player unlocked his drops? - OfflinePlayer owner = instance.getServer().getOfflinePlayer(ownerID); - String ownerName = instance.lookupPlayerName(ownerID); - if(owner.isOnline() && !player.equals(owner)) - { - PlayerData playerData = this.dataStore.getPlayerData(ownerID); + //when a player reels in his fishing rod + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPlayerFish(PlayerFishEvent event) + { + Entity entity = event.getCaught(); + if (entity == null) return; //if nothing pulled, uninteresting event + + //if should be protected from pulling in land claims without permission + if (entity.getType() == EntityType.ARMOR_STAND || entity instanceof Animals) + { + Player player = event.getPlayer(); + PlayerData playerData = instance.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = instance.dataStore.getClaimAt(entity.getLocation(), false, playerData.lastClaim); + if (claim != null) + { + //if no permission, cancel + String errorMessage = claim.allowContainers(player); + if (errorMessage != null) + { + event.setCancelled(true); + instance.sendMessage(player, TextMode.Err, Messages.NoDamageClaimedEntity, claim.getOwnerName()); + return; + } + } + } + } + + //when a player picks up an item... + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPlayerPickupItem(PlayerPickupItemEvent event) + { + Player player = event.getPlayer(); + + //FEATURE: lock dropped items to player who dropped them + + //who owns this stack? + Item item = event.getItem(); + List data = item.getMetadata("GP_ITEMOWNER"); + if (data != null && data.size() > 0) + { + UUID ownerID = (UUID) data.get(0).value(); + + //has that player unlocked his drops? + OfflinePlayer owner = instance.getServer().getOfflinePlayer(ownerID); + String ownerName = instance.lookupPlayerName(ownerID); + if (owner.isOnline() && !player.equals(owner)) + { + PlayerData playerData = this.dataStore.getPlayerData(ownerID); //if locked, don't allow pickup - if(!playerData.dropsAreUnlocked) - { - event.setCancelled(true); - - //if hasn't been instructed how to unlock, send explanatory messages - if(!playerData.receivedDropUnlockAdvertisement) - { - instance.sendMessage(owner.getPlayer(), TextMode.Instr, Messages.DropUnlockAdvertisement); - instance.sendMessage(player, TextMode.Err, Messages.PickupBlockedExplanation, ownerName); - playerData.receivedDropUnlockAdvertisement = true; - } - - return; - } - } - } - - //the rest of this code is specific to pvp worlds - if(!instance.pvpRulesApply(player.getWorld())) return; - - //if we're preventing spawn camping and the player was previously empty handed... - if(instance.config_pvp_protectFreshSpawns && (instance.getItemInHand(player, EquipmentSlot.HAND).getType() == Material.AIR)) - { - //if that player is currently immune to pvp - PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getUniqueId()); - if(playerData.pvpImmune) - { - //if it's been less than 10 seconds since the last time he spawned, don't pick up the item - long now = Calendar.getInstance().getTimeInMillis(); - long elapsedSinceLastSpawn = now - playerData.lastSpawn; - if(elapsedSinceLastSpawn < 10000) - { - event.setCancelled(true); - return; - } - - //otherwise take away his immunity. he may be armed now. at least, he's worth killing for some loot - playerData.pvpImmune = false; - instance.sendMessage(player, TextMode.Warn, Messages.PvPImmunityEnd); - } - } - } - - //when a player switches in-hand items - @EventHandler(ignoreCancelled = true) - public void onItemHeldChange(PlayerItemHeldEvent event) - { - Player player = event.getPlayer(); - - //if he's switching to the golden shovel - int newSlot = event.getNewSlot(); - ItemStack newItemStack = player.getInventory().getItem(newSlot); - if(newItemStack != null && newItemStack.getType() == instance.config_claims_modificationTool) - { - //give the player his available claim blocks count and claiming instructions, but only if he keeps the shovel equipped for a minimum time, to avoid mouse wheel spam - if(instance.claimsEnabledForWorld(player.getWorld())) - { - EquipShovelProcessingTask task = new EquipShovelProcessingTask(player); - instance.getServer().getScheduler().scheduleSyncDelayedTask(instance, task, 15L); //15L is approx. 3/4 of a second - } - } - } - - //block use of buckets within other players' claims - private HashSet commonAdjacentBlocks_water = new HashSet(Arrays.asList(Material.WATER, Material.FARMLAND, Material.DIRT, Material.STONE)); - private HashSet commonAdjacentBlocks_lava = new HashSet(Arrays.asList(Material.LAVA, Material.DIRT, Material.STONE)); - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerBucketEmpty (PlayerBucketEmptyEvent bucketEvent) - { - if(!instance.claimsEnabledForWorld(bucketEvent.getBlockClicked().getWorld())) return; - - Player player = bucketEvent.getPlayer(); - Block block = bucketEvent.getBlockClicked().getRelative(bucketEvent.getBlockFace()); - int minLavaDistance = 10; - - //make sure the player is allowed to build at the location - String noBuildReason = instance.allowBuild(player, block.getLocation(), Material.WATER); - if(noBuildReason != null) - { - instance.sendMessage(player, TextMode.Err, noBuildReason); - bucketEvent.setCancelled(true); - return; - } - - //if the bucket is being used in a claim, allow for dumping lava closer to other players - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, playerData.lastClaim); - if(claim != null) - { - minLavaDistance = 3; - } - - //otherwise no wilderness dumping in creative mode worlds - else if(instance.creativeRulesApply(block.getLocation())) - { - if(block.getY() >= instance.getSeaLevel(block.getWorld()) - 5 && !player.hasPermission("griefprevention.lava")) - { - if(bucketEvent.getBucket() == Material.LAVA_BUCKET) - { - instance.sendMessage(player, TextMode.Err, Messages.NoWildernessBuckets); - bucketEvent.setCancelled(true); - return; - } - } - } - - //lava buckets can't be dumped near other players unless pvp is on - if(!doesAllowLavaProximityInWorld(block.getWorld()) && !player.hasPermission("griefprevention.lava")) - { - if(bucketEvent.getBucket() == Material.LAVA_BUCKET) - { - List players = block.getWorld().getPlayers(); - for(int i = 0; i < players.size(); i++) - { - Player otherPlayer = players.get(i); - Location location = otherPlayer.getLocation(); - if(!otherPlayer.equals(player) && otherPlayer.getGameMode() == GameMode.SURVIVAL && player.canSee(otherPlayer) && block.getY() >= location.getBlockY() - 1 && location.distanceSquared(block.getLocation()) < minLavaDistance * minLavaDistance) - { - instance.sendMessage(player, TextMode.Err, Messages.NoLavaNearOtherPlayer, "another player"); - bucketEvent.setCancelled(true); - return; - } - } - } - } - - //log any suspicious placements (check sea level, world type, and adjacent blocks) - if(block.getY() >= instance.getSeaLevel(block.getWorld()) - 5 && !player.hasPermission("griefprevention.lava") && block.getWorld().getEnvironment() != Environment.NETHER) - { - //if certain blocks are nearby, it's less suspicious and not worth logging - HashSet exclusionAdjacentTypes; - if(bucketEvent.getBucket() == Material.WATER_BUCKET) - exclusionAdjacentTypes = this.commonAdjacentBlocks_water; - else - exclusionAdjacentTypes = this.commonAdjacentBlocks_lava; - - boolean makeLogEntry = true; - BlockFace [] adjacentDirections = new BlockFace[] {BlockFace.EAST, BlockFace.WEST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.DOWN}; - for(BlockFace direction : adjacentDirections) - { - Material adjacentBlockType = block.getRelative(direction).getType(); - if(exclusionAdjacentTypes.contains(adjacentBlockType)) - { - makeLogEntry = false; - break; - } - } - - if(makeLogEntry) - { - instance.AddLogEntry(player.getName() + " placed suspicious " + bucketEvent.getBucket().name() + " @ " + instance.getfriendlyLocationString(block.getLocation()), CustomLogEntryTypes.SuspiciousActivity); - } - } - } - - private boolean doesAllowLavaProximityInWorld(World world) { - if (GriefPrevention.instance.pvpRulesApply(world)) { - return GriefPrevention.instance.config_pvp_allowLavaNearPlayers; - } else { - return GriefPrevention.instance.config_pvp_allowLavaNearPlayers_NonPvp; - } - } - - //see above - @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerBucketFill (PlayerBucketFillEvent bucketEvent) - { - Player player = bucketEvent.getPlayer(); - Block block = bucketEvent.getBlockClicked(); - - if(!instance.claimsEnabledForWorld(block.getWorld())) return; - - //make sure the player is allowed to build at the location - String noBuildReason = instance.allowBuild(player, block.getLocation(), Material.AIR); - if(noBuildReason != null) - { - //exemption for cow milking (permissions will be handled by player interact with entity event instead) - Material blockType = block.getType(); - if (blockType == Material.AIR) - return; - if(blockType.isSolid()) - { - BlockData blockData = block.getBlockData(); - if (!(blockData instanceof Waterlogged) || !((Waterlogged)blockData).isWaterlogged()) - return; - } - - instance.sendMessage(player, TextMode.Err, noBuildReason); - bucketEvent.setCancelled(true); - return; - } - } - - //when a player interacts with the world - @EventHandler(priority = EventPriority.LOWEST) - void onPlayerInteract(PlayerInteractEvent event) - { - //not interested in left-click-on-air actions - Action action = event.getAction(); - if(action == Action.LEFT_CLICK_AIR) return; - - Player player = event.getPlayer(); - Block clickedBlock = event.getClickedBlock(); //null returned here means interacting with air - - Material clickedBlockType = null; - if(clickedBlock != null) - { - clickedBlockType = clickedBlock.getType(); - } - else - { - clickedBlockType = Material.AIR; - } - - PlayerData playerData = null; - - //Turtle eggs - if(action == Action.PHYSICAL) - { - if (clickedBlockType != Material.TURTLE_EGG) - return; - playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); - if(claim != null) - { - playerData.lastClaim = claim; - - String noAccessReason = claim.allowBreak(player, clickedBlockType); - if(noAccessReason != null) - { - event.setCancelled(true); - return; - } - } - return; - } - - //don't care about left-clicking on most blocks, this is probably a break action - if(action == Action.LEFT_CLICK_BLOCK && clickedBlock != null) + if (!playerData.dropsAreUnlocked) { - if(clickedBlock.getY() < clickedBlock.getWorld().getMaxHeight() - 1 || event.getBlockFace() != BlockFace.UP) - { - Block adjacentBlock = clickedBlock.getRelative(event.getBlockFace()); - byte lightLevel = adjacentBlock.getLightFromBlocks(); - if(lightLevel == 15 && adjacentBlock.getType() == Material.FIRE) - { - if(playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); - if(claim != null) - { - playerData.lastClaim = claim; + event.setCancelled(true); - String noBuildReason = claim.allowBuild(player, Material.AIR); - if(noBuildReason != null) - { - event.setCancelled(true); - instance.sendMessage(player, TextMode.Err, noBuildReason); - player.sendBlockChange(adjacentBlock.getLocation(), adjacentBlock.getType(), adjacentBlock.getData()); - return; - } - } - } - } + //if hasn't been instructed how to unlock, send explanatory messages + if (!playerData.receivedDropUnlockAdvertisement) + { + instance.sendMessage(owner.getPlayer(), TextMode.Instr, Messages.DropUnlockAdvertisement); + instance.sendMessage(player, TextMode.Err, Messages.PickupBlockedExplanation, ownerName); + playerData.receivedDropUnlockAdvertisement = true; + } - //exception for blocks on a specific watch list - if(!this.onLeftClickWatchList(clickedBlockType)) - { - return; - } + return; } - - //apply rules for containers and crafting blocks - if( clickedBlock != null && instance.config_claims_preventTheft && ( - event.getAction() == Action.RIGHT_CLICK_BLOCK && ( - (this.isInventoryHolder(clickedBlock) && clickedBlock.getType() != Material.LECTERN) || - clickedBlockType == Material.CAULDRON || - clickedBlockType == Material.JUKEBOX || - clickedBlockType == Material.ANVIL || - clickedBlockType == Material.CHIPPED_ANVIL || - clickedBlockType == Material.DAMAGED_ANVIL || - clickedBlockType == Material.CAKE || - clickedBlockType == Material.SWEET_BERRY_BUSH || - clickedBlockType == Material.BEE_NEST || - clickedBlockType == Material.BEEHIVE || - clickedBlockType == Material.BEACON || - clickedBlockType == Material.BELL || - clickedBlockType == Material.STONECUTTER || - clickedBlockType == Material.GRINDSTONE || - clickedBlockType == Material.CARTOGRAPHY_TABLE || - clickedBlockType == Material.LOOM))) - { - if(playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); - - //block container use while under siege, so players can't hide items from attackers - if(playerData.siegeData != null) - { - instance.sendMessage(player, TextMode.Err, Messages.SiegeNoContainers); - event.setCancelled(true); - return; - } - - //block container use during pvp combat, same reason - if(playerData.inPvpCombat()) - { - instance.sendMessage(player, TextMode.Err, Messages.PvPNoContainers); - event.setCancelled(true); - return; - } - - //otherwise check permissions for the claim the player is in - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); - if(claim != null) - { - playerData.lastClaim = claim; + } + } - String noContainersReason = claim.allowContainers(player); - if(noContainersReason != null) - { - event.setCancelled(true); - instance.sendMessage(player, TextMode.Err, noContainersReason); - return; - } - } - - //if the event hasn't been cancelled, then the player is allowed to use the container - //so drop any pvp protection - if(playerData.pvpImmune) - { - playerData.pvpImmune = false; - instance.sendMessage(player, TextMode.Warn, Messages.PvPImmunityEnd); - } - } - - //otherwise apply rules for doors and beds, if configured that way - else if( clickedBlock != null && - - (instance.config_claims_lockWoodenDoors && ( - clickedBlockType == Material.OAK_DOOR || - clickedBlockType == Material.ACACIA_DOOR || - clickedBlockType == Material.BIRCH_DOOR || - clickedBlockType == Material.JUNGLE_DOOR || - clickedBlockType == Material.SPRUCE_DOOR || - clickedBlockType == Material.DARK_OAK_DOOR)) || - - (instance.config_claims_preventButtonsSwitches && ( clickedBlockType == Material.WHITE_BED || - clickedBlockType == Material.ORANGE_BED || - clickedBlockType == Material.MAGENTA_BED || - clickedBlockType == Material.LIGHT_BLUE_BED || - clickedBlockType == Material.YELLOW_BED || - clickedBlockType == Material.LIME_BED || - clickedBlockType == Material.PINK_BED || - clickedBlockType == Material.GRAY_BED || - clickedBlockType == Material.LIGHT_GRAY_BED || - clickedBlockType == Material.CYAN_BED || - clickedBlockType == Material.PURPLE_BED || - clickedBlockType == Material.BLUE_BED || - clickedBlockType == Material.BROWN_BED || - clickedBlockType == Material.GREEN_BED || - clickedBlockType == Material.RED_BED || - clickedBlockType == Material.BLACK_BED)) || - - (instance.config_claims_lockTrapDoors && ( - clickedBlockType == Material.OAK_TRAPDOOR || - clickedBlockType == Material.SPRUCE_TRAPDOOR || - clickedBlockType == Material.BIRCH_TRAPDOOR || - clickedBlockType == Material.JUNGLE_TRAPDOOR || - clickedBlockType == Material.ACACIA_TRAPDOOR || - clickedBlockType == Material.DARK_OAK_TRAPDOOR)) || - - (instance.config_claims_lockFenceGates && ( - clickedBlockType == Material.OAK_FENCE_GATE || - clickedBlockType == Material.ACACIA_FENCE_GATE || - clickedBlockType == Material.BIRCH_FENCE_GATE || - clickedBlockType == Material.JUNGLE_FENCE_GATE || - clickedBlockType == Material.SPRUCE_FENCE_GATE || - clickedBlockType == Material.DARK_OAK_FENCE_GATE)) || - (instance.config_claims_lecternReadingRequiresAccessTrust && clickedBlockType == Material.LECTERN)) - { - if(playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); - if(claim != null) - { - playerData.lastClaim = claim; + //the rest of this code is specific to pvp worlds + if (!instance.pvpRulesApply(player.getWorld())) return; - String noAccessReason = claim.allowAccess(player); - if(noAccessReason != null) - { - event.setCancelled(true); - instance.sendMessage(player, TextMode.Err, noAccessReason); - return; - } - } - } - - //otherwise apply rules for buttons and switches - else if(clickedBlock != null && instance.config_claims_preventButtonsSwitches && (clickedBlockType == null || clickedBlockType == Material.STONE_BUTTON || Tag.BUTTONS.isTagged(clickedBlockType) || clickedBlockType == Material.LEVER)) - { - if(playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); - if(claim != null) - { - playerData.lastClaim = claim; - - String noAccessReason = claim.allowAccess(player); - if(noAccessReason != null) - { - event.setCancelled(true); - instance.sendMessage(player, TextMode.Err, noAccessReason); - return; - } - } - } - - //otherwise apply rule for cake - else if(clickedBlock != null && instance.config_claims_preventTheft && clickedBlockType == Material.CAKE) + //if we're preventing spawn camping and the player was previously empty handed... + if (instance.config_pvp_protectFreshSpawns && (instance.getItemInHand(player, EquipmentSlot.HAND).getType() == Material.AIR)) { - if(playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); + //if that player is currently immune to pvp + PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getUniqueId()); + if (playerData.pvpImmune) + { + //if it's been less than 10 seconds since the last time he spawned, don't pick up the item + long now = Calendar.getInstance().getTimeInMillis(); + long elapsedSinceLastSpawn = now - playerData.lastSpawn; + if (elapsedSinceLastSpawn < 10000) + { + event.setCancelled(true); + return; + } + + //otherwise take away his immunity. he may be armed now. at least, he's worth killing for some loot + playerData.pvpImmune = false; + instance.sendMessage(player, TextMode.Warn, Messages.PvPImmunityEnd); + } + } + } + + //when a player switches in-hand items + @EventHandler(ignoreCancelled = true) + public void onItemHeldChange(PlayerItemHeldEvent event) + { + Player player = event.getPlayer(); + + //if he's switching to the golden shovel + int newSlot = event.getNewSlot(); + ItemStack newItemStack = player.getInventory().getItem(newSlot); + if (newItemStack != null && newItemStack.getType() == instance.config_claims_modificationTool) + { + //give the player his available claim blocks count and claiming instructions, but only if he keeps the shovel equipped for a minimum time, to avoid mouse wheel spam + if (instance.claimsEnabledForWorld(player.getWorld())) + { + EquipShovelProcessingTask task = new EquipShovelProcessingTask(player); + instance.getServer().getScheduler().scheduleSyncDelayedTask(instance, task, 15L); //15L is approx. 3/4 of a second + } + } + } + + //block use of buckets within other players' claims + private HashSet commonAdjacentBlocks_water = new HashSet(Arrays.asList(Material.WATER, Material.FARMLAND, Material.DIRT, Material.STONE)); + private HashSet commonAdjacentBlocks_lava = new HashSet(Arrays.asList(Material.LAVA, Material.DIRT, Material.STONE)); + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPlayerBucketEmpty(PlayerBucketEmptyEvent bucketEvent) + { + if (!instance.claimsEnabledForWorld(bucketEvent.getBlockClicked().getWorld())) return; + + Player player = bucketEvent.getPlayer(); + Block block = bucketEvent.getBlockClicked().getRelative(bucketEvent.getBlockFace()); + int minLavaDistance = 10; + + //make sure the player is allowed to build at the location + String noBuildReason = instance.allowBuild(player, block.getLocation(), Material.WATER); + if (noBuildReason != null) + { + instance.sendMessage(player, TextMode.Err, noBuildReason); + bucketEvent.setCancelled(true); + return; + } + + //if the bucket is being used in a claim, allow for dumping lava closer to other players + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, playerData.lastClaim); + if (claim != null) + { + minLavaDistance = 3; + } + + //otherwise no wilderness dumping in creative mode worlds + else if (instance.creativeRulesApply(block.getLocation())) + { + if (block.getY() >= instance.getSeaLevel(block.getWorld()) - 5 && !player.hasPermission("griefprevention.lava")) + { + if (bucketEvent.getBucket() == Material.LAVA_BUCKET) + { + instance.sendMessage(player, TextMode.Err, Messages.NoWildernessBuckets); + bucketEvent.setCancelled(true); + return; + } + } + } + + //lava buckets can't be dumped near other players unless pvp is on + if (!doesAllowLavaProximityInWorld(block.getWorld()) && !player.hasPermission("griefprevention.lava")) + { + if (bucketEvent.getBucket() == Material.LAVA_BUCKET) + { + List players = block.getWorld().getPlayers(); + for (int i = 0; i < players.size(); i++) + { + Player otherPlayer = players.get(i); + Location location = otherPlayer.getLocation(); + if (!otherPlayer.equals(player) && otherPlayer.getGameMode() == GameMode.SURVIVAL && player.canSee(otherPlayer) && block.getY() >= location.getBlockY() - 1 && location.distanceSquared(block.getLocation()) < minLavaDistance * minLavaDistance) + { + instance.sendMessage(player, TextMode.Err, Messages.NoLavaNearOtherPlayer, "another player"); + bucketEvent.setCancelled(true); + return; + } + } + } + } + + //log any suspicious placements (check sea level, world type, and adjacent blocks) + if (block.getY() >= instance.getSeaLevel(block.getWorld()) - 5 && !player.hasPermission("griefprevention.lava") && block.getWorld().getEnvironment() != Environment.NETHER) + { + //if certain blocks are nearby, it's less suspicious and not worth logging + HashSet exclusionAdjacentTypes; + if (bucketEvent.getBucket() == Material.WATER_BUCKET) + exclusionAdjacentTypes = this.commonAdjacentBlocks_water; + else + exclusionAdjacentTypes = this.commonAdjacentBlocks_lava; + + boolean makeLogEntry = true; + BlockFace[] adjacentDirections = new BlockFace[]{BlockFace.EAST, BlockFace.WEST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.DOWN}; + for (BlockFace direction : adjacentDirections) + { + Material adjacentBlockType = block.getRelative(direction).getType(); + if (exclusionAdjacentTypes.contains(adjacentBlockType)) + { + makeLogEntry = false; + break; + } + } + + if (makeLogEntry) + { + instance.AddLogEntry(player.getName() + " placed suspicious " + bucketEvent.getBucket().name() + " @ " + instance.getfriendlyLocationString(block.getLocation()), CustomLogEntryTypes.SuspiciousActivity); + } + } + } + + private boolean doesAllowLavaProximityInWorld(World world) + { + if (GriefPrevention.instance.pvpRulesApply(world)) + { + return GriefPrevention.instance.config_pvp_allowLavaNearPlayers; + } else + { + return GriefPrevention.instance.config_pvp_allowLavaNearPlayers_NonPvp; + } + } + + //see above + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onPlayerBucketFill(PlayerBucketFillEvent bucketEvent) + { + Player player = bucketEvent.getPlayer(); + Block block = bucketEvent.getBlockClicked(); + + if (!instance.claimsEnabledForWorld(block.getWorld())) return; + + //make sure the player is allowed to build at the location + String noBuildReason = instance.allowBuild(player, block.getLocation(), Material.AIR); + if (noBuildReason != null) + { + //exemption for cow milking (permissions will be handled by player interact with entity event instead) + Material blockType = block.getType(); + if (blockType == Material.AIR) + return; + if (blockType.isSolid()) + { + BlockData blockData = block.getBlockData(); + if (!(blockData instanceof Waterlogged) || !((Waterlogged) blockData).isWaterlogged()) + return; + } + + instance.sendMessage(player, TextMode.Err, noBuildReason); + bucketEvent.setCancelled(true); + return; + } + } + + //when a player interacts with the world + @EventHandler(priority = EventPriority.LOWEST) + void onPlayerInteract(PlayerInteractEvent event) + { + //not interested in left-click-on-air actions + Action action = event.getAction(); + if (action == Action.LEFT_CLICK_AIR) return; + + Player player = event.getPlayer(); + Block clickedBlock = event.getClickedBlock(); //null returned here means interacting with air + + Material clickedBlockType = null; + if (clickedBlock != null) + { + clickedBlockType = clickedBlock.getType(); + } else + { + clickedBlockType = Material.AIR; + } + + PlayerData playerData = null; + + //Turtle eggs + if (action == Action.PHYSICAL) + { + if (clickedBlockType != Material.TURTLE_EGG) + return; + playerData = this.dataStore.getPlayerData(player.getUniqueId()); Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); - if(claim != null) + if (claim != null) { playerData.lastClaim = claim; - + + String noAccessReason = claim.allowBreak(player, clickedBlockType); + if (noAccessReason != null) + { + event.setCancelled(true); + return; + } + } + return; + } + + //don't care about left-clicking on most blocks, this is probably a break action + if (action == Action.LEFT_CLICK_BLOCK && clickedBlock != null) + { + if (clickedBlock.getY() < clickedBlock.getWorld().getMaxHeight() - 1 || event.getBlockFace() != BlockFace.UP) + { + Block adjacentBlock = clickedBlock.getRelative(event.getBlockFace()); + byte lightLevel = adjacentBlock.getLightFromBlocks(); + if (lightLevel == 15 && adjacentBlock.getType() == Material.FIRE) + { + if (playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); + if (claim != null) + { + playerData.lastClaim = claim; + + String noBuildReason = claim.allowBuild(player, Material.AIR); + if (noBuildReason != null) + { + event.setCancelled(true); + instance.sendMessage(player, TextMode.Err, noBuildReason); + player.sendBlockChange(adjacentBlock.getLocation(), adjacentBlock.getType(), adjacentBlock.getData()); + return; + } + } + } + } + + //exception for blocks on a specific watch list + if (!this.onLeftClickWatchList(clickedBlockType)) + { + return; + } + } + + //apply rules for containers and crafting blocks + if (clickedBlock != null && instance.config_claims_preventTheft && ( + event.getAction() == Action.RIGHT_CLICK_BLOCK && ( + (this.isInventoryHolder(clickedBlock) && clickedBlock.getType() != Material.LECTERN) || + clickedBlockType == Material.CAULDRON || + clickedBlockType == Material.JUKEBOX || + clickedBlockType == Material.ANVIL || + clickedBlockType == Material.CHIPPED_ANVIL || + clickedBlockType == Material.DAMAGED_ANVIL || + clickedBlockType == Material.CAKE || + clickedBlockType == Material.SWEET_BERRY_BUSH || + clickedBlockType == Material.BEE_NEST || + clickedBlockType == Material.BEEHIVE || + clickedBlockType == Material.BEACON || + clickedBlockType == Material.BELL || + clickedBlockType == Material.STONECUTTER || + clickedBlockType == Material.GRINDSTONE || + clickedBlockType == Material.CARTOGRAPHY_TABLE || + clickedBlockType == Material.LOOM))) + { + if (playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); + + //block container use while under siege, so players can't hide items from attackers + if (playerData.siegeData != null) + { + instance.sendMessage(player, TextMode.Err, Messages.SiegeNoContainers); + event.setCancelled(true); + return; + } + + //block container use during pvp combat, same reason + if (playerData.inPvpCombat()) + { + instance.sendMessage(player, TextMode.Err, Messages.PvPNoContainers); + event.setCancelled(true); + return; + } + + //otherwise check permissions for the claim the player is in + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); + if (claim != null) + { + playerData.lastClaim = claim; + + String noContainersReason = claim.allowContainers(player); + if (noContainersReason != null) + { + event.setCancelled(true); + instance.sendMessage(player, TextMode.Err, noContainersReason); + return; + } + } + + //if the event hasn't been cancelled, then the player is allowed to use the container + //so drop any pvp protection + if (playerData.pvpImmune) + { + playerData.pvpImmune = false; + instance.sendMessage(player, TextMode.Warn, Messages.PvPImmunityEnd); + } + } + + //otherwise apply rules for doors and beds, if configured that way + else if (clickedBlock != null && + + (instance.config_claims_lockWoodenDoors && ( + clickedBlockType == Material.OAK_DOOR || + clickedBlockType == Material.ACACIA_DOOR || + clickedBlockType == Material.BIRCH_DOOR || + clickedBlockType == Material.JUNGLE_DOOR || + clickedBlockType == Material.SPRUCE_DOOR || + clickedBlockType == Material.DARK_OAK_DOOR)) || + + (instance.config_claims_preventButtonsSwitches && (clickedBlockType == Material.WHITE_BED || + clickedBlockType == Material.ORANGE_BED || + clickedBlockType == Material.MAGENTA_BED || + clickedBlockType == Material.LIGHT_BLUE_BED || + clickedBlockType == Material.YELLOW_BED || + clickedBlockType == Material.LIME_BED || + clickedBlockType == Material.PINK_BED || + clickedBlockType == Material.GRAY_BED || + clickedBlockType == Material.LIGHT_GRAY_BED || + clickedBlockType == Material.CYAN_BED || + clickedBlockType == Material.PURPLE_BED || + clickedBlockType == Material.BLUE_BED || + clickedBlockType == Material.BROWN_BED || + clickedBlockType == Material.GREEN_BED || + clickedBlockType == Material.RED_BED || + clickedBlockType == Material.BLACK_BED)) || + + (instance.config_claims_lockTrapDoors && ( + clickedBlockType == Material.OAK_TRAPDOOR || + clickedBlockType == Material.SPRUCE_TRAPDOOR || + clickedBlockType == Material.BIRCH_TRAPDOOR || + clickedBlockType == Material.JUNGLE_TRAPDOOR || + clickedBlockType == Material.ACACIA_TRAPDOOR || + clickedBlockType == Material.DARK_OAK_TRAPDOOR)) || + + (instance.config_claims_lockFenceGates && ( + clickedBlockType == Material.OAK_FENCE_GATE || + clickedBlockType == Material.ACACIA_FENCE_GATE || + clickedBlockType == Material.BIRCH_FENCE_GATE || + clickedBlockType == Material.JUNGLE_FENCE_GATE || + clickedBlockType == Material.SPRUCE_FENCE_GATE || + clickedBlockType == Material.DARK_OAK_FENCE_GATE)) || + (instance.config_claims_lecternReadingRequiresAccessTrust && clickedBlockType == Material.LECTERN)) + { + if (playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); + if (claim != null) + { + playerData.lastClaim = claim; + + String noAccessReason = claim.allowAccess(player); + if (noAccessReason != null) + { + event.setCancelled(true); + instance.sendMessage(player, TextMode.Err, noAccessReason); + return; + } + } + } + + //otherwise apply rules for buttons and switches + else if (clickedBlock != null && instance.config_claims_preventButtonsSwitches && (clickedBlockType == null || clickedBlockType == Material.STONE_BUTTON || Tag.BUTTONS.isTagged(clickedBlockType) || clickedBlockType == Material.LEVER)) + { + if (playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); + if (claim != null) + { + playerData.lastClaim = claim; + + String noAccessReason = claim.allowAccess(player); + if (noAccessReason != null) + { + event.setCancelled(true); + instance.sendMessage(player, TextMode.Err, noAccessReason); + return; + } + } + } + + //otherwise apply rule for cake + else if (clickedBlock != null && instance.config_claims_preventTheft && clickedBlockType == Material.CAKE) + { + if (playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); + if (claim != null) + { + playerData.lastClaim = claim; + String noContainerReason = claim.allowAccess(player); - if(noContainerReason != null) + if (noContainerReason != null) { event.setCancelled(true); instance.sendMessage(player, TextMode.Err, noContainerReason); return; } - } + } } - - //apply rule for note blocks and repeaters and daylight sensors //RoboMWM: Include flower pots - else if(clickedBlock != null && - ( - clickedBlockType == Material.NOTE_BLOCK || - clickedBlockType == Material.REPEATER || - clickedBlockType == Material.DRAGON_EGG || - clickedBlockType == Material.DAYLIGHT_DETECTOR || - clickedBlockType == Material.COMPARATOR || - Tag.FLOWER_POTS.isTagged(clickedBlockType) - )) - { - if(playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); - if(claim != null) - { - String noBuildReason = claim.allowBuild(player, clickedBlockType); - if(noBuildReason != null) - { - event.setCancelled(true); - instance.sendMessage(player, TextMode.Err, noBuildReason); - return; - } - } - } - - //otherwise handle right click (shovel, string, bonemeal) //RoboMWM: flint and steel - else - { - //ignore all actions except right-click on a block or in the air - if(action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) return; - - //what's the player holding? - EquipmentSlot hand = event.getHand(); - ItemStack itemInHand = instance.getItemInHand(player, hand); - Material materialInHand = itemInHand.getType(); - - Set spawn_eggs = new HashSet<>(); - Set dyes = new HashSet<>(); - - spawn_eggs.add(Material.BAT_SPAWN_EGG); - spawn_eggs.add(Material.BLAZE_SPAWN_EGG); - spawn_eggs.add(Material.CAVE_SPIDER_SPAWN_EGG); - spawn_eggs.add(Material.CHICKEN_SPAWN_EGG); - spawn_eggs.add(Material.COD_SPAWN_EGG); - spawn_eggs.add(Material.COW_SPAWN_EGG); - spawn_eggs.add(Material.CREEPER_SPAWN_EGG); - spawn_eggs.add(Material.DOLPHIN_SPAWN_EGG); - spawn_eggs.add(Material.DONKEY_SPAWN_EGG); - spawn_eggs.add(Material.DROWNED_SPAWN_EGG); - spawn_eggs.add(Material.ELDER_GUARDIAN_SPAWN_EGG); - spawn_eggs.add(Material.ENDERMAN_SPAWN_EGG); - spawn_eggs.add(Material.ENDERMITE_SPAWN_EGG); - spawn_eggs.add(Material.EVOKER_SPAWN_EGG); - spawn_eggs.add(Material.GHAST_SPAWN_EGG); - spawn_eggs.add(Material.GUARDIAN_SPAWN_EGG); - spawn_eggs.add(Material.HORSE_SPAWN_EGG); - spawn_eggs.add(Material.HUSK_SPAWN_EGG); - spawn_eggs.add(Material.LLAMA_SPAWN_EGG); - spawn_eggs.add(Material.MAGMA_CUBE_SPAWN_EGG); - spawn_eggs.add(Material.MOOSHROOM_SPAWN_EGG); - spawn_eggs.add(Material.MULE_SPAWN_EGG); - spawn_eggs.add(Material.OCELOT_SPAWN_EGG); - spawn_eggs.add(Material.PARROT_SPAWN_EGG); - spawn_eggs.add(Material.PHANTOM_SPAWN_EGG); - spawn_eggs.add(Material.PIG_SPAWN_EGG); - spawn_eggs.add(Material.POLAR_BEAR_SPAWN_EGG); - spawn_eggs.add(Material.PUFFERFISH_SPAWN_EGG); - spawn_eggs.add(Material.RABBIT_SPAWN_EGG); - spawn_eggs.add(Material.SALMON_SPAWN_EGG); - spawn_eggs.add(Material.SHEEP_SPAWN_EGG); - spawn_eggs.add(Material.SHULKER_SPAWN_EGG); - spawn_eggs.add(Material.SILVERFISH_SPAWN_EGG); - spawn_eggs.add(Material.SKELETON_SPAWN_EGG); - spawn_eggs.add(Material.SKELETON_HORSE_SPAWN_EGG); - spawn_eggs.add(Material.SLIME_SPAWN_EGG); - spawn_eggs.add(Material.SPIDER_SPAWN_EGG); - spawn_eggs.add(Material.SQUID_SPAWN_EGG); - spawn_eggs.add(Material.STRAY_SPAWN_EGG); - spawn_eggs.add(Material.TROPICAL_FISH_SPAWN_EGG); - spawn_eggs.add(Material.TURTLE_SPAWN_EGG); - spawn_eggs.add(Material.VEX_SPAWN_EGG); - spawn_eggs.add(Material.VILLAGER_SPAWN_EGG); - spawn_eggs.add(Material.VINDICATOR_SPAWN_EGG); - spawn_eggs.add(Material.WITCH_SPAWN_EGG); - spawn_eggs.add(Material.WITHER_SKELETON_SPAWN_EGG); - spawn_eggs.add(Material.WOLF_SPAWN_EGG); - spawn_eggs.add(Material.ZOMBIE_SPAWN_EGG); - spawn_eggs.add(Material.ZOMBIE_HORSE_SPAWN_EGG); - spawn_eggs.add(Material.ZOMBIE_PIGMAN_SPAWN_EGG); - spawn_eggs.add(Material.ZOMBIE_VILLAGER_SPAWN_EGG); - for (Material material : Material.values()) - { - if (!material.isLegacy() && material.name().endsWith("_DYE")) - dyes.add(material); - } + //apply rule for note blocks and repeaters and daylight sensors //RoboMWM: Include flower pots + else if (clickedBlock != null && + ( + clickedBlockType == Material.NOTE_BLOCK || + clickedBlockType == Material.REPEATER || + clickedBlockType == Material.DRAGON_EGG || + clickedBlockType == Material.DAYLIGHT_DETECTOR || + clickedBlockType == Material.COMPARATOR || + Tag.FLOWER_POTS.isTagged(clickedBlockType) + )) + { + if (playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); + if (claim != null) + { + String noBuildReason = claim.allowBuild(player, clickedBlockType); + if (noBuildReason != null) + { + event.setCancelled(true); + instance.sendMessage(player, TextMode.Err, noBuildReason); + return; + } + } + } - - //if it's bonemeal, armor stand, spawn egg, etc - check for build permission //RoboMWM: also check flint and steel to stop TNT ignition - if(clickedBlock != null && (materialInHand == Material.BONE_MEAL - || materialInHand == Material.ARMOR_STAND - || (spawn_eggs.contains(materialInHand) && GriefPrevention.instance.config_claims_preventGlobalMonsterEggs) - || materialInHand == Material.END_CRYSTAL - || materialInHand == Material.FLINT_AND_STEEL - || dyes.contains(materialInHand))) - { - String noBuildReason = instance - .allowBuild(player, clickedBlock - .getLocation(), - clickedBlockType); - if(noBuildReason != null) - { - instance.sendMessage(player, TextMode.Err, noBuildReason); - event.setCancelled(true); - } - - return; - } - - else if(clickedBlock != null && ( - materialInHand == Material.OAK_BOAT || - materialInHand == Material.SPRUCE_BOAT || - materialInHand == Material.BIRCH_BOAT || - materialInHand == Material.JUNGLE_BOAT || - materialInHand == Material.ACACIA_BOAT || - materialInHand == Material.DARK_OAK_BOAT)) - { - if(playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); - if(claim != null) - { - String noBuildReason = claim.allowBuild(player, Material.OAK_BOAT); // Though only checks OAK_BOAT, permission should be same for all boats. Plus it being a boat doesn't seem to make a difference currently. - if(noBuildReason != null) - { - instance.sendMessage(player, TextMode.Err, noBuildReason); - event.setCancelled(true); - } - } - - return; - } - - //survival world minecart placement requires container trust, which is the permission required to remove the minecart later - else if(clickedBlock != null && - (materialInHand == Material.MINECART || - materialInHand == Material.FURNACE_MINECART || - materialInHand == Material.CHEST_MINECART || - materialInHand == Material.TNT_MINECART || - materialInHand == Material.HOPPER_MINECART) && - !instance.creativeRulesApply(clickedBlock.getLocation())) - { - if(playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); - if(claim != null) - { - String reason = claim.allowContainers(player); - if(reason != null) - { - instance.sendMessage(player, TextMode.Err, reason); - event.setCancelled(true); - } - } + //otherwise handle right click (shovel, string, bonemeal) //RoboMWM: flint and steel + else + { + //ignore all actions except right-click on a block or in the air + if (action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) return; - return; - } - - //if it's a spawn egg, minecart, or boat, and this is a creative world, apply special rules - else if(clickedBlock != null && (materialInHand == Material.MINECART || - materialInHand == Material.FURNACE_MINECART || - materialInHand == Material.CHEST_MINECART || - materialInHand == Material.TNT_MINECART || - materialInHand == Material.ARMOR_STAND || - materialInHand == Material.ITEM_FRAME || - spawn_eggs.contains(materialInHand) || - materialInHand == Material.INFESTED_STONE || - materialInHand == Material.INFESTED_COBBLESTONE || - materialInHand == Material.INFESTED_STONE_BRICKS || - materialInHand == Material.INFESTED_MOSSY_STONE_BRICKS || - materialInHand == Material.INFESTED_CRACKED_STONE_BRICKS || - materialInHand == Material.INFESTED_CHISELED_STONE_BRICKS || - materialInHand == Material.HOPPER_MINECART) && - instance.creativeRulesApply(clickedBlock.getLocation())) - { - //player needs build permission at this location - String noBuildReason = instance.allowBuild(player, clickedBlock.getLocation(), Material.MINECART); - if(noBuildReason != null) - { - instance.sendMessage(player, TextMode.Err, noBuildReason); - event.setCancelled(true); - return; - } - - //enforce limit on total number of entities in this claim - if(playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); - if(claim == null) return; - - String noEntitiesReason = claim.allowMoreEntities(false); - if(noEntitiesReason != null) - { - instance.sendMessage(player, TextMode.Err, noEntitiesReason); - event.setCancelled(true); - return; - } - - return; - } + //what's the player holding? + EquipmentSlot hand = event.getHand(); + ItemStack itemInHand = instance.getItemInHand(player, hand); + Material materialInHand = itemInHand.getType(); - //if he's investigating a claim - else if(materialInHand == instance.config_claims_investigationTool && hand == EquipmentSlot.HAND) - { - //if claims are disabled in this world, do nothing - if(!instance.claimsEnabledForWorld(player.getWorld())) return; + Set spawn_eggs = new HashSet<>(); + Set dyes = new HashSet<>(); - //if holding shift (sneaking), show all claims in area - if(player.isSneaking() && player.hasPermission("griefprevention.visualizenearbyclaims")) - { - //find nearby claims - Set claims = this.dataStore.getNearbyClaims(player.getLocation()); + spawn_eggs.add(Material.BAT_SPAWN_EGG); + spawn_eggs.add(Material.BLAZE_SPAWN_EGG); + spawn_eggs.add(Material.CAVE_SPIDER_SPAWN_EGG); + spawn_eggs.add(Material.CHICKEN_SPAWN_EGG); + spawn_eggs.add(Material.COD_SPAWN_EGG); + spawn_eggs.add(Material.COW_SPAWN_EGG); + spawn_eggs.add(Material.CREEPER_SPAWN_EGG); + spawn_eggs.add(Material.DOLPHIN_SPAWN_EGG); + spawn_eggs.add(Material.DONKEY_SPAWN_EGG); + spawn_eggs.add(Material.DROWNED_SPAWN_EGG); + spawn_eggs.add(Material.ELDER_GUARDIAN_SPAWN_EGG); + spawn_eggs.add(Material.ENDERMAN_SPAWN_EGG); + spawn_eggs.add(Material.ENDERMITE_SPAWN_EGG); + spawn_eggs.add(Material.EVOKER_SPAWN_EGG); + spawn_eggs.add(Material.GHAST_SPAWN_EGG); + spawn_eggs.add(Material.GUARDIAN_SPAWN_EGG); + spawn_eggs.add(Material.HORSE_SPAWN_EGG); + spawn_eggs.add(Material.HUSK_SPAWN_EGG); + spawn_eggs.add(Material.LLAMA_SPAWN_EGG); + spawn_eggs.add(Material.MAGMA_CUBE_SPAWN_EGG); + spawn_eggs.add(Material.MOOSHROOM_SPAWN_EGG); + spawn_eggs.add(Material.MULE_SPAWN_EGG); + spawn_eggs.add(Material.OCELOT_SPAWN_EGG); + spawn_eggs.add(Material.PARROT_SPAWN_EGG); + spawn_eggs.add(Material.PHANTOM_SPAWN_EGG); + spawn_eggs.add(Material.PIG_SPAWN_EGG); + spawn_eggs.add(Material.POLAR_BEAR_SPAWN_EGG); + spawn_eggs.add(Material.PUFFERFISH_SPAWN_EGG); + spawn_eggs.add(Material.RABBIT_SPAWN_EGG); + spawn_eggs.add(Material.SALMON_SPAWN_EGG); + spawn_eggs.add(Material.SHEEP_SPAWN_EGG); + spawn_eggs.add(Material.SHULKER_SPAWN_EGG); + spawn_eggs.add(Material.SILVERFISH_SPAWN_EGG); + spawn_eggs.add(Material.SKELETON_SPAWN_EGG); + spawn_eggs.add(Material.SKELETON_HORSE_SPAWN_EGG); + spawn_eggs.add(Material.SLIME_SPAWN_EGG); + spawn_eggs.add(Material.SPIDER_SPAWN_EGG); + spawn_eggs.add(Material.SQUID_SPAWN_EGG); + spawn_eggs.add(Material.STRAY_SPAWN_EGG); + spawn_eggs.add(Material.TROPICAL_FISH_SPAWN_EGG); + spawn_eggs.add(Material.TURTLE_SPAWN_EGG); + spawn_eggs.add(Material.VEX_SPAWN_EGG); + spawn_eggs.add(Material.VILLAGER_SPAWN_EGG); + spawn_eggs.add(Material.VINDICATOR_SPAWN_EGG); + spawn_eggs.add(Material.WITCH_SPAWN_EGG); + spawn_eggs.add(Material.WITHER_SKELETON_SPAWN_EGG); + spawn_eggs.add(Material.WOLF_SPAWN_EGG); + spawn_eggs.add(Material.ZOMBIE_SPAWN_EGG); + spawn_eggs.add(Material.ZOMBIE_HORSE_SPAWN_EGG); + spawn_eggs.add(Material.ZOMBIE_PIGMAN_SPAWN_EGG); + spawn_eggs.add(Material.ZOMBIE_VILLAGER_SPAWN_EGG); + + for (Material material : Material.values()) + { + if (!material.isLegacy() && material.name().endsWith("_DYE")) + dyes.add(material); + } + + + //if it's bonemeal, armor stand, spawn egg, etc - check for build permission //RoboMWM: also check flint and steel to stop TNT ignition + if (clickedBlock != null && (materialInHand == Material.BONE_MEAL + || materialInHand == Material.ARMOR_STAND + || (spawn_eggs.contains(materialInHand) && GriefPrevention.instance.config_claims_preventGlobalMonsterEggs) + || materialInHand == Material.END_CRYSTAL + || materialInHand == Material.FLINT_AND_STEEL + || dyes.contains(materialInHand))) + { + String noBuildReason = instance + .allowBuild(player, clickedBlock + .getLocation(), + clickedBlockType); + if (noBuildReason != null) + { + instance.sendMessage(player, TextMode.Err, noBuildReason); + event.setCancelled(true); + } + + return; + } else if (clickedBlock != null && ( + materialInHand == Material.OAK_BOAT || + materialInHand == Material.SPRUCE_BOAT || + materialInHand == Material.BIRCH_BOAT || + materialInHand == Material.JUNGLE_BOAT || + materialInHand == Material.ACACIA_BOAT || + materialInHand == Material.DARK_OAK_BOAT)) + { + if (playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); + if (claim != null) + { + String noBuildReason = claim.allowBuild(player, Material.OAK_BOAT); // Though only checks OAK_BOAT, permission should be same for all boats. Plus it being a boat doesn't seem to make a difference currently. + if (noBuildReason != null) + { + instance.sendMessage(player, TextMode.Err, noBuildReason); + event.setCancelled(true); + } + } + + return; + } + + //survival world minecart placement requires container trust, which is the permission required to remove the minecart later + else if (clickedBlock != null && + (materialInHand == Material.MINECART || + materialInHand == Material.FURNACE_MINECART || + materialInHand == Material.CHEST_MINECART || + materialInHand == Material.TNT_MINECART || + materialInHand == Material.HOPPER_MINECART) && + !instance.creativeRulesApply(clickedBlock.getLocation())) + { + if (playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); + if (claim != null) + { + String reason = claim.allowContainers(player); + if (reason != null) + { + instance.sendMessage(player, TextMode.Err, reason); + event.setCancelled(true); + } + } + + return; + } + + //if it's a spawn egg, minecart, or boat, and this is a creative world, apply special rules + else if (clickedBlock != null && (materialInHand == Material.MINECART || + materialInHand == Material.FURNACE_MINECART || + materialInHand == Material.CHEST_MINECART || + materialInHand == Material.TNT_MINECART || + materialInHand == Material.ARMOR_STAND || + materialInHand == Material.ITEM_FRAME || + spawn_eggs.contains(materialInHand) || + materialInHand == Material.INFESTED_STONE || + materialInHand == Material.INFESTED_COBBLESTONE || + materialInHand == Material.INFESTED_STONE_BRICKS || + materialInHand == Material.INFESTED_MOSSY_STONE_BRICKS || + materialInHand == Material.INFESTED_CRACKED_STONE_BRICKS || + materialInHand == Material.INFESTED_CHISELED_STONE_BRICKS || + materialInHand == Material.HOPPER_MINECART) && + instance.creativeRulesApply(clickedBlock.getLocation())) + { + //player needs build permission at this location + String noBuildReason = instance.allowBuild(player, clickedBlock.getLocation(), Material.MINECART); + if (noBuildReason != null) + { + instance.sendMessage(player, TextMode.Err, noBuildReason); + event.setCancelled(true); + return; + } + + //enforce limit on total number of entities in this claim + if (playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); + if (claim == null) return; + + String noEntitiesReason = claim.allowMoreEntities(false); + if (noEntitiesReason != null) + { + instance.sendMessage(player, TextMode.Err, noEntitiesReason); + event.setCancelled(true); + return; + } + + return; + } + + //if he's investigating a claim + else if (materialInHand == instance.config_claims_investigationTool && hand == EquipmentSlot.HAND) + { + //if claims are disabled in this world, do nothing + if (!instance.claimsEnabledForWorld(player.getWorld())) return; + + //if holding shift (sneaking), show all claims in area + if (player.isSneaking() && player.hasPermission("griefprevention.visualizenearbyclaims")) + { + //find nearby claims + Set claims = this.dataStore.getNearbyClaims(player.getLocation()); // alert plugins of a visualization Bukkit.getPluginManager().callEvent(new VisualizationEvent(player, claims)); - //visualize boundaries + //visualize boundaries Visualization visualization = Visualization.fromClaims(claims, player.getEyeLocation().getBlockY(), VisualizationType.Claim, player.getLocation()); Visualization.Apply(player, visualization); instance.sendMessage(player, TextMode.Info, Messages.ShowNearbyClaims, String.valueOf(claims.size())); return; - } + } - //FEATURE: shovel and stick can be used from a distance away - if(action == Action.RIGHT_CLICK_AIR) - { - //try to find a far away non-air block along line of sight - clickedBlock = getTargetBlock(player, 100); - clickedBlockType = clickedBlock.getType(); - } + //FEATURE: shovel and stick can be used from a distance away + if (action == Action.RIGHT_CLICK_AIR) + { + //try to find a far away non-air block along line of sight + clickedBlock = getTargetBlock(player, 100); + clickedBlockType = clickedBlock.getType(); + } - //if no block, stop here - if(clickedBlock == null) - { - return; - } + //if no block, stop here + if (clickedBlock == null) + { + return; + } - //air indicates too far away - if(clickedBlockType == Material.AIR) - { - instance.sendMessage(player, TextMode.Err, Messages.TooFarAway); + //air indicates too far away + if (clickedBlockType == Material.AIR) + { + instance.sendMessage(player, TextMode.Err, Messages.TooFarAway); // alert plugins of a visualization Bukkit.getPluginManager().callEvent(new VisualizationEvent(player, Collections.emptySet())); - Visualization.Revert(player); - return; - } + Visualization.Revert(player); + return; + } - if(playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false /*ignore height*/, playerData.lastClaim); + if (playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false /*ignore height*/, playerData.lastClaim); - //no claim case - if(claim == null) - { - instance.sendMessage(player, TextMode.Info, Messages.BlockNotClaimed); + //no claim case + if (claim == null) + { + instance.sendMessage(player, TextMode.Info, Messages.BlockNotClaimed); // alert plugins of a visualization Bukkit.getPluginManager().callEvent(new VisualizationEvent(player, Collections.emptySet())); - Visualization.Revert(player); - } + Visualization.Revert(player); + } - //claim case - else - { - playerData.lastClaim = claim; - instance.sendMessage(player, TextMode.Info, Messages.BlockClaimed, claim.getOwnerName()); + //claim case + else + { + playerData.lastClaim = claim; + instance.sendMessage(player, TextMode.Info, Messages.BlockClaimed, claim.getOwnerName()); - //visualize boundary - Visualization visualization = Visualization.FromClaim(claim, player.getEyeLocation().getBlockY(), VisualizationType.Claim, player.getLocation()); + //visualize boundary + Visualization visualization = Visualization.FromClaim(claim, player.getEyeLocation().getBlockY(), VisualizationType.Claim, player.getLocation()); // alert plugins of a visualization Bukkit.getPluginManager().callEvent(new VisualizationEvent(player, claim)); - Visualization.Apply(player, visualization); + Visualization.Apply(player, visualization); - if (player.hasPermission("griefprevention.seeclaimsize")) { - instance.sendMessage(player, TextMode.Info, " " + claim.getWidth() + "x" + claim.getHeight() + "=" + claim.getArea()); - } + if (player.hasPermission("griefprevention.seeclaimsize")) + { + instance.sendMessage(player, TextMode.Info, " " + claim.getWidth() + "x" + claim.getHeight() + "=" + claim.getArea()); + } - //if permission, tell about the player's offline time - if(!claim.isAdminClaim() && (player.hasPermission("griefprevention.deleteclaims") || player.hasPermission("griefprevention.seeinactivity"))) - { - if(claim.parent != null) - { - claim = claim.parent; - } - Date lastLogin = new Date(Bukkit.getOfflinePlayer(claim.ownerID).getLastPlayed()); - Date now = new Date(); - long daysElapsed = (now.getTime() - lastLogin.getTime()) / (1000 * 60 * 60 * 24); + //if permission, tell about the player's offline time + if (!claim.isAdminClaim() && (player.hasPermission("griefprevention.deleteclaims") || player.hasPermission("griefprevention.seeinactivity"))) + { + if (claim.parent != null) + { + claim = claim.parent; + } + Date lastLogin = new Date(Bukkit.getOfflinePlayer(claim.ownerID).getLastPlayed()); + Date now = new Date(); + long daysElapsed = (now.getTime() - lastLogin.getTime()) / (1000 * 60 * 60 * 24); - instance.sendMessage(player, TextMode.Info, Messages.PlayerOfflineTime, String.valueOf(daysElapsed)); + instance.sendMessage(player, TextMode.Info, Messages.PlayerOfflineTime, String.valueOf(daysElapsed)); - //drop the data we just loaded, if the player isn't online - if(instance.getServer().getPlayer(claim.ownerID) == null) - this.dataStore.clearCachedPlayerData(claim.ownerID); - } - } + //drop the data we just loaded, if the player isn't online + if (instance.getServer().getPlayer(claim.ownerID) == null) + this.dataStore.clearCachedPlayerData(claim.ownerID); + } + } - return; - } + return; + } - //if it's a golden shovel - else if(materialInHand != instance.config_claims_modificationTool || hand != EquipmentSlot.HAND) return; + //if it's a golden shovel + else if (materialInHand != instance.config_claims_modificationTool || hand != EquipmentSlot.HAND) return; - event.setCancelled(true); //GriefPrevention exclusively reserves this tool (e.g. no grass path creation for golden shovel) + event.setCancelled(true); //GriefPrevention exclusively reserves this tool (e.g. no grass path creation for golden shovel) - //disable golden shovel while under siege - if(playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); - if(playerData.siegeData != null) - { - instance.sendMessage(player, TextMode.Err, Messages.SiegeNoShovel); - event.setCancelled(true); - return; - } + //disable golden shovel while under siege + if (playerData == null) playerData = this.dataStore.getPlayerData(player.getUniqueId()); + if (playerData.siegeData != null) + { + instance.sendMessage(player, TextMode.Err, Messages.SiegeNoShovel); + event.setCancelled(true); + return; + } - //FEATURE: shovel and stick can be used from a distance away - if(action == Action.RIGHT_CLICK_AIR) + //FEATURE: shovel and stick can be used from a distance away + if (action == Action.RIGHT_CLICK_AIR) { //try to find a far away non-air block along line of sight clickedBlock = getTargetBlock(player, 100); @@ -2122,563 +2121,552 @@ class PlayerEventHandler implements Listener } //if no block, stop here - if(clickedBlock == null) + if (clickedBlock == null) { return; } - //can't use the shovel from too far away - if(clickedBlockType == Material.AIR) - { - instance.sendMessage(player, TextMode.Err, Messages.TooFarAway); - return; - } + //can't use the shovel from too far away + if (clickedBlockType == Material.AIR) + { + instance.sendMessage(player, TextMode.Err, Messages.TooFarAway); + return; + } - //if the player is in restore nature mode, do only that - UUID playerID = player.getUniqueId(); - playerData = this.dataStore.getPlayerData(player.getUniqueId()); - if(playerData.shovelMode == ShovelMode.RestoreNature || playerData.shovelMode == ShovelMode.RestoreNatureAggressive) - { - //if the clicked block is in a claim, visualize that claim and deliver an error message - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); - if(claim != null) - { - instance.sendMessage(player, TextMode.Err, Messages.BlockClaimed, claim.getOwnerName()); - Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.ErrorClaim, player.getLocation()); + //if the player is in restore nature mode, do only that + UUID playerID = player.getUniqueId(); + playerData = this.dataStore.getPlayerData(player.getUniqueId()); + if (playerData.shovelMode == ShovelMode.RestoreNature || playerData.shovelMode == ShovelMode.RestoreNatureAggressive) + { + //if the clicked block is in a claim, visualize that claim and deliver an error message + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); + if (claim != null) + { + instance.sendMessage(player, TextMode.Err, Messages.BlockClaimed, claim.getOwnerName()); + Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.ErrorClaim, player.getLocation()); // alert plugins of a visualization Bukkit.getPluginManager().callEvent(new VisualizationEvent(player, claim)); - Visualization.Apply(player, visualization); + Visualization.Apply(player, visualization); - return; - } + return; + } - //figure out which chunk to repair - Chunk chunk = player.getWorld().getChunkAt(clickedBlock.getLocation()); - //start the repair process + //figure out which chunk to repair + Chunk chunk = player.getWorld().getChunkAt(clickedBlock.getLocation()); + //start the repair process - //set boundaries for processing - int miny = clickedBlock.getY(); + //set boundaries for processing + int miny = clickedBlock.getY(); - //if not in aggressive mode, extend the selection down to a little below sea level - if(!(playerData.shovelMode == ShovelMode.RestoreNatureAggressive)) - { - if(miny > instance.getSeaLevel(chunk.getWorld()) - 10) - { - miny = instance.getSeaLevel(chunk.getWorld()) - 10; - } - } + //if not in aggressive mode, extend the selection down to a little below sea level + if (!(playerData.shovelMode == ShovelMode.RestoreNatureAggressive)) + { + if (miny > instance.getSeaLevel(chunk.getWorld()) - 10) + { + miny = instance.getSeaLevel(chunk.getWorld()) - 10; + } + } - instance.restoreChunk(chunk, miny, playerData.shovelMode == ShovelMode.RestoreNatureAggressive, 0, player); + instance.restoreChunk(chunk, miny, playerData.shovelMode == ShovelMode.RestoreNatureAggressive, 0, player); - return; - } + return; + } - //if in restore nature fill mode - if(playerData.shovelMode == ShovelMode.RestoreNatureFill) - { - ArrayList allowedFillBlocks = new ArrayList(); - Environment environment = clickedBlock.getWorld().getEnvironment(); - if(environment == Environment.NETHER) - { - allowedFillBlocks.add(Material.NETHERRACK); - } - else if(environment == Environment.THE_END) - { - allowedFillBlocks.add(Material.END_STONE); - } - else - { - allowedFillBlocks.add(Material.GRASS); - allowedFillBlocks.add(Material.DIRT); - allowedFillBlocks.add(Material.STONE); - allowedFillBlocks.add(Material.SAND); - allowedFillBlocks.add(Material.SANDSTONE); - allowedFillBlocks.add(Material.ICE); - } + //if in restore nature fill mode + if (playerData.shovelMode == ShovelMode.RestoreNatureFill) + { + ArrayList allowedFillBlocks = new ArrayList(); + Environment environment = clickedBlock.getWorld().getEnvironment(); + if (environment == Environment.NETHER) + { + allowedFillBlocks.add(Material.NETHERRACK); + } else if (environment == Environment.THE_END) + { + allowedFillBlocks.add(Material.END_STONE); + } else + { + allowedFillBlocks.add(Material.GRASS); + allowedFillBlocks.add(Material.DIRT); + allowedFillBlocks.add(Material.STONE); + allowedFillBlocks.add(Material.SAND); + allowedFillBlocks.add(Material.SANDSTONE); + allowedFillBlocks.add(Material.ICE); + } - Block centerBlock = clickedBlock; + Block centerBlock = clickedBlock; - int maxHeight = centerBlock.getY(); - int minx = centerBlock.getX() - playerData.fillRadius; - int maxx = centerBlock.getX() + playerData.fillRadius; - int minz = centerBlock.getZ() - playerData.fillRadius; - int maxz = centerBlock.getZ() + playerData.fillRadius; - int minHeight = maxHeight - 10; - if(minHeight < 0) minHeight = 0; + int maxHeight = centerBlock.getY(); + int minx = centerBlock.getX() - playerData.fillRadius; + int maxx = centerBlock.getX() + playerData.fillRadius; + int minz = centerBlock.getZ() - playerData.fillRadius; + int maxz = centerBlock.getZ() + playerData.fillRadius; + int minHeight = maxHeight - 10; + if (minHeight < 0) minHeight = 0; - Claim cachedClaim = null; - for(int x = minx; x <= maxx; x++) - { - for(int z = minz; z <= maxz; z++) - { - //circular brush - Location location = new Location(centerBlock.getWorld(), x, centerBlock.getY(), z); - if(location.distance(centerBlock.getLocation()) > playerData.fillRadius) continue; + Claim cachedClaim = null; + for (int x = minx; x <= maxx; x++) + { + for (int z = minz; z <= maxz; z++) + { + //circular brush + Location location = new Location(centerBlock.getWorld(), x, centerBlock.getY(), z); + if (location.distance(centerBlock.getLocation()) > playerData.fillRadius) continue; - //default fill block is initially the first from the allowed fill blocks list above - Material defaultFiller = allowedFillBlocks.get(0); + //default fill block is initially the first from the allowed fill blocks list above + Material defaultFiller = allowedFillBlocks.get(0); - //prefer to use the block the player clicked on, if it's an acceptable fill block - if(allowedFillBlocks.contains(centerBlock.getType())) - { - defaultFiller = centerBlock.getType(); - } + //prefer to use the block the player clicked on, if it's an acceptable fill block + if (allowedFillBlocks.contains(centerBlock.getType())) + { + defaultFiller = centerBlock.getType(); + } - //if the player clicks on water, try to sink through the water to find something underneath that's useful for a filler - else if(centerBlock.getType() == Material.WATER) - { - Block block = centerBlock.getWorld().getBlockAt(centerBlock.getLocation()); - while(!allowedFillBlocks.contains(block.getType()) && block.getY() > centerBlock.getY() - 10) - { - block = block.getRelative(BlockFace.DOWN); - } - if(allowedFillBlocks.contains(block.getType())) - { - defaultFiller = block.getType(); - } - } + //if the player clicks on water, try to sink through the water to find something underneath that's useful for a filler + else if (centerBlock.getType() == Material.WATER) + { + Block block = centerBlock.getWorld().getBlockAt(centerBlock.getLocation()); + while (!allowedFillBlocks.contains(block.getType()) && block.getY() > centerBlock.getY() - 10) + { + block = block.getRelative(BlockFace.DOWN); + } + if (allowedFillBlocks.contains(block.getType())) + { + defaultFiller = block.getType(); + } + } - //fill bottom to top - for(int y = minHeight; y <= maxHeight; y++) - { - Block block = centerBlock.getWorld().getBlockAt(x, y, z); + //fill bottom to top + for (int y = minHeight; y <= maxHeight; y++) + { + Block block = centerBlock.getWorld().getBlockAt(x, y, z); - //respect claims - Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, cachedClaim); - if(claim != null) - { - cachedClaim = claim; - break; - } + //respect claims + Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, cachedClaim); + if (claim != null) + { + cachedClaim = claim; + break; + } - //only replace air, spilling water, snow, long grass - if(block.getType() == Material.AIR || block.getType() == Material.SNOW || (block.getType() == Material.WATER && ((Levelled) block.getBlockData()).getLevel() != 0) || block.getType() == Material.GRASS) - { - //if the top level, always use the default filler picked above - if(y == maxHeight) - { - block.setType(defaultFiller); - } + //only replace air, spilling water, snow, long grass + if (block.getType() == Material.AIR || block.getType() == Material.SNOW || (block.getType() == Material.WATER && ((Levelled) block.getBlockData()).getLevel() != 0) || block.getType() == Material.GRASS) + { + //if the top level, always use the default filler picked above + if (y == maxHeight) + { + block.setType(defaultFiller); + } - //otherwise look to neighbors for an appropriate fill block - else - { - Block eastBlock = block.getRelative(BlockFace.EAST); - Block westBlock = block.getRelative(BlockFace.WEST); - Block northBlock = block.getRelative(BlockFace.NORTH); - Block southBlock = block.getRelative(BlockFace.SOUTH); + //otherwise look to neighbors for an appropriate fill block + else + { + Block eastBlock = block.getRelative(BlockFace.EAST); + Block westBlock = block.getRelative(BlockFace.WEST); + Block northBlock = block.getRelative(BlockFace.NORTH); + Block southBlock = block.getRelative(BlockFace.SOUTH); - //first, check lateral neighbors (ideally, want to keep natural layers) - if(allowedFillBlocks.contains(eastBlock.getType())) - { - block.setType(eastBlock.getType()); - } - else if(allowedFillBlocks.contains(westBlock.getType())) - { - block.setType(westBlock.getType()); - } - else if(allowedFillBlocks.contains(northBlock.getType())) - { - block.setType(northBlock.getType()); - } - else if(allowedFillBlocks.contains(southBlock.getType())) - { - block.setType(southBlock.getType()); - } + //first, check lateral neighbors (ideally, want to keep natural layers) + if (allowedFillBlocks.contains(eastBlock.getType())) + { + block.setType(eastBlock.getType()); + } else if (allowedFillBlocks.contains(westBlock.getType())) + { + block.setType(westBlock.getType()); + } else if (allowedFillBlocks.contains(northBlock.getType())) + { + block.setType(northBlock.getType()); + } else if (allowedFillBlocks.contains(southBlock.getType())) + { + block.setType(southBlock.getType()); + } - //if all else fails, use the default filler selected above - else - { - block.setType(defaultFiller); - } - } - } - } - } - } + //if all else fails, use the default filler selected above + else + { + block.setType(defaultFiller); + } + } + } + } + } + } - return; - } + return; + } - //if the player doesn't have claims permission, don't do anything - if(!player.hasPermission("griefprevention.createclaims")) - { - instance.sendMessage(player, TextMode.Err, Messages.NoCreateClaimPermission); - return; - } + //if the player doesn't have claims permission, don't do anything + if (!player.hasPermission("griefprevention.createclaims")) + { + instance.sendMessage(player, TextMode.Err, Messages.NoCreateClaimPermission); + return; + } - //if he's resizing a claim and that claim hasn't been deleted since he started resizing it - if(playerData.claimResizing != null && playerData.claimResizing.inDataStore) - { - if(clickedBlock.getLocation().equals(playerData.lastShovelLocation)) return; + //if he's resizing a claim and that claim hasn't been deleted since he started resizing it + if (playerData.claimResizing != null && playerData.claimResizing.inDataStore) + { + if (clickedBlock.getLocation().equals(playerData.lastShovelLocation)) return; - //figure out what the coords of his new claim would be - int newx1, newx2, newz1, newz2, newy1, newy2; - if(playerData.lastShovelLocation.getBlockX() == playerData.claimResizing.getLesserBoundaryCorner().getBlockX()) - { - newx1 = clickedBlock.getX(); - } - else - { - newx1 = playerData.claimResizing.getLesserBoundaryCorner().getBlockX(); - } + //figure out what the coords of his new claim would be + int newx1, newx2, newz1, newz2, newy1, newy2; + if (playerData.lastShovelLocation.getBlockX() == playerData.claimResizing.getLesserBoundaryCorner().getBlockX()) + { + newx1 = clickedBlock.getX(); + } else + { + newx1 = playerData.claimResizing.getLesserBoundaryCorner().getBlockX(); + } - if(playerData.lastShovelLocation.getBlockX() == playerData.claimResizing.getGreaterBoundaryCorner().getBlockX()) - { - newx2 = clickedBlock.getX(); - } - else - { - newx2 = playerData.claimResizing.getGreaterBoundaryCorner().getBlockX(); - } + if (playerData.lastShovelLocation.getBlockX() == playerData.claimResizing.getGreaterBoundaryCorner().getBlockX()) + { + newx2 = clickedBlock.getX(); + } else + { + newx2 = playerData.claimResizing.getGreaterBoundaryCorner().getBlockX(); + } - if(playerData.lastShovelLocation.getBlockZ() == playerData.claimResizing.getLesserBoundaryCorner().getBlockZ()) - { - newz1 = clickedBlock.getZ(); - } - else - { - newz1 = playerData.claimResizing.getLesserBoundaryCorner().getBlockZ(); - } + if (playerData.lastShovelLocation.getBlockZ() == playerData.claimResizing.getLesserBoundaryCorner().getBlockZ()) + { + newz1 = clickedBlock.getZ(); + } else + { + newz1 = playerData.claimResizing.getLesserBoundaryCorner().getBlockZ(); + } - if(playerData.lastShovelLocation.getBlockZ() == playerData.claimResizing.getGreaterBoundaryCorner().getBlockZ()) - { - newz2 = clickedBlock.getZ(); - } - else - { - newz2 = playerData.claimResizing.getGreaterBoundaryCorner().getBlockZ(); - } + if (playerData.lastShovelLocation.getBlockZ() == playerData.claimResizing.getGreaterBoundaryCorner().getBlockZ()) + { + newz2 = clickedBlock.getZ(); + } else + { + newz2 = playerData.claimResizing.getGreaterBoundaryCorner().getBlockZ(); + } - newy1 = playerData.claimResizing.getLesserBoundaryCorner().getBlockY(); - newy2 = clickedBlock.getY() - instance.config_claims_claimsExtendIntoGroundDistance; + newy1 = playerData.claimResizing.getLesserBoundaryCorner().getBlockY(); + newy2 = clickedBlock.getY() - instance.config_claims_claimsExtendIntoGroundDistance; - this.dataStore.resizeClaimWithChecks(player, playerData, newx1, newx2, newy1, newy2, newz1, newz2); + this.dataStore.resizeClaimWithChecks(player, playerData, newx1, newx2, newy1, newy2, newz1, newz2); - return; - } + return; + } - //otherwise, since not currently resizing a claim, must be starting a resize, creating a new claim, or creating a subdivision - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), true /*ignore height*/, playerData.lastClaim); + //otherwise, since not currently resizing a claim, must be starting a resize, creating a new claim, or creating a subdivision + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), true /*ignore height*/, playerData.lastClaim); - //if within an existing claim, he's not creating a new one - if(claim != null) - { - //if the player has permission to edit the claim or subdivision - String noEditReason = claim.allowEdit(player); - if(noEditReason == null) - { - //if he clicked on a corner, start resizing it - if((clickedBlock.getX() == claim.getLesserBoundaryCorner().getBlockX() || clickedBlock.getX() == claim.getGreaterBoundaryCorner().getBlockX()) && (clickedBlock.getZ() == claim.getLesserBoundaryCorner().getBlockZ() || clickedBlock.getZ() == claim.getGreaterBoundaryCorner().getBlockZ())) - { - playerData.claimResizing = claim; - playerData.lastShovelLocation = clickedBlock.getLocation(); - instance.sendMessage(player, TextMode.Instr, Messages.ResizeStart); - } + //if within an existing claim, he's not creating a new one + if (claim != null) + { + //if the player has permission to edit the claim or subdivision + String noEditReason = claim.allowEdit(player); + if (noEditReason == null) + { + //if he clicked on a corner, start resizing it + if ((clickedBlock.getX() == claim.getLesserBoundaryCorner().getBlockX() || clickedBlock.getX() == claim.getGreaterBoundaryCorner().getBlockX()) && (clickedBlock.getZ() == claim.getLesserBoundaryCorner().getBlockZ() || clickedBlock.getZ() == claim.getGreaterBoundaryCorner().getBlockZ())) + { + playerData.claimResizing = claim; + playerData.lastShovelLocation = clickedBlock.getLocation(); + instance.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 - else if(playerData.shovelMode == ShovelMode.Subdivide) - { - //if it's the first click, he's trying to start a new subdivision - if(playerData.lastShovelLocation == null) - { - //if the clicked claim was a subdivision, tell him he can't start a new subdivision here - if(claim.parent != null) - { - instance.sendMessage(player, TextMode.Err, Messages.ResizeFailOverlapSubdivision); - } + //if he didn't click on a corner and is in subdivision mode, he's creating a new subdivision + else if (playerData.shovelMode == ShovelMode.Subdivide) + { + //if it's the first click, he's trying to start a new subdivision + if (playerData.lastShovelLocation == null) + { + //if the clicked claim was a subdivision, tell him he can't start a new subdivision here + if (claim.parent != null) + { + instance.sendMessage(player, TextMode.Err, Messages.ResizeFailOverlapSubdivision); + } - //otherwise start a new subdivision - else - { - instance.sendMessage(player, TextMode.Instr, Messages.SubdivisionStart); - playerData.lastShovelLocation = clickedBlock.getLocation(); - playerData.claimSubdividing = claim; - } - } + //otherwise start a new subdivision + else + { + instance.sendMessage(player, TextMode.Instr, Messages.SubdivisionStart); + playerData.lastShovelLocation = clickedBlock.getLocation(); + playerData.claimSubdividing = claim; + } + } - //otherwise, he's trying to finish creating a subdivision by setting the other boundary corner - else - { - //if last shovel location was in a different world, assume the player is starting the create-claim workflow over - if(!playerData.lastShovelLocation.getWorld().equals(clickedBlock.getWorld())) - { - playerData.lastShovelLocation = null; - this.onPlayerInteract(event); - return; - } + //otherwise, he's trying to finish creating a subdivision by setting the other boundary corner + else + { + //if last shovel location was in a different world, assume the player is starting the create-claim workflow over + if (!playerData.lastShovelLocation.getWorld().equals(clickedBlock.getWorld())) + { + playerData.lastShovelLocation = null; + this.onPlayerInteract(event); + return; + } - //try to create a new claim (will return null if this subdivision overlaps another) - CreateClaimResult result = this.dataStore.createClaim( - player.getWorld(), - playerData.lastShovelLocation.getBlockX(), clickedBlock.getX(), - playerData.lastShovelLocation.getBlockY() - instance.config_claims_claimsExtendIntoGroundDistance, clickedBlock.getY() - instance.config_claims_claimsExtendIntoGroundDistance, - playerData.lastShovelLocation.getBlockZ(), clickedBlock.getZ(), - null, //owner is not used for subdivisions - playerData.claimSubdividing, - null, player); + //try to create a new claim (will return null if this subdivision overlaps another) + CreateClaimResult result = this.dataStore.createClaim( + player.getWorld(), + playerData.lastShovelLocation.getBlockX(), clickedBlock.getX(), + playerData.lastShovelLocation.getBlockY() - instance.config_claims_claimsExtendIntoGroundDistance, clickedBlock.getY() - instance.config_claims_claimsExtendIntoGroundDistance, + playerData.lastShovelLocation.getBlockZ(), clickedBlock.getZ(), + null, //owner is not used for subdivisions + playerData.claimSubdividing, + null, player); - //if it didn't succeed, tell the player why - if(!result.succeeded) - { - instance.sendMessage(player, TextMode.Err, Messages.CreateSubdivisionOverlap); + //if it didn't succeed, tell the player why + if (!result.succeeded) + { + instance.sendMessage(player, TextMode.Err, Messages.CreateSubdivisionOverlap); - Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.ErrorClaim, player.getLocation()); + Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.ErrorClaim, player.getLocation()); // alert plugins of a visualization Bukkit.getPluginManager().callEvent(new VisualizationEvent(player, result.claim)); - Visualization.Apply(player, visualization); + Visualization.Apply(player, visualization); - return; - } + return; + } - //otherwise, advise him on the /trust command and show him his new subdivision - else - { - instance.sendMessage(player, TextMode.Success, Messages.SubdivisionSuccess); - Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.Claim, player.getLocation()); + //otherwise, advise him on the /trust command and show him his new subdivision + else + { + instance.sendMessage(player, TextMode.Success, Messages.SubdivisionSuccess); + Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.Claim, player.getLocation()); // alert plugins of a visualization Bukkit.getPluginManager().callEvent(new VisualizationEvent(player, result.claim)); - Visualization.Apply(player, visualization); - playerData.lastShovelLocation = null; - playerData.claimSubdividing = null; - } - } - } + Visualization.Apply(player, visualization); + playerData.lastShovelLocation = null; + playerData.claimSubdividing = null; + } + } + } - //otherwise tell him he can't create a claim here, and show him the existing claim - //also advise him to consider /abandonclaim or resizing the existing claim - else - { - instance.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlap); - Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.Claim, player.getLocation()); + //otherwise tell him he can't create a claim here, and show him the existing claim + //also advise him to consider /abandonclaim or resizing the existing claim + else + { + instance.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlap); + Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.Claim, player.getLocation()); // alert plugins of a visualization Bukkit.getPluginManager().callEvent(new VisualizationEvent(player, claim)); - Visualization.Apply(player, visualization); - } - } + Visualization.Apply(player, visualization); + } + } - //otherwise tell the player he can't claim here because it's someone else's claim, and show him the claim - else - { - instance.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlapOtherPlayer, claim.getOwnerName()); - Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.ErrorClaim, player.getLocation()); + //otherwise tell the player he can't claim here because it's someone else's claim, and show him the claim + else + { + instance.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlapOtherPlayer, claim.getOwnerName()); + Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.ErrorClaim, player.getLocation()); // alert plugins of a visualization Bukkit.getPluginManager().callEvent(new VisualizationEvent(player, claim)); - Visualization.Apply(player, visualization); - } + Visualization.Apply(player, visualization); + } - return; - } + return; + } - //otherwise, the player isn't in an existing claim! + //otherwise, the player isn't in an existing claim! - //if he hasn't already start a claim with a previous shovel action - Location lastShovelLocation = playerData.lastShovelLocation; - if(lastShovelLocation == null) - { - //if claims are not enabled in this world and it's not an administrative claim, display an error message and stop - if(!instance.claimsEnabledForWorld(player.getWorld())) - { - instance.sendMessage(player, TextMode.Err, Messages.ClaimsDisabledWorld); - return; - } + //if he hasn't already start a claim with a previous shovel action + Location lastShovelLocation = playerData.lastShovelLocation; + if (lastShovelLocation == null) + { + //if claims are not enabled in this world and it's not an administrative claim, display an error message and stop + if (!instance.claimsEnabledForWorld(player.getWorld())) + { + instance.sendMessage(player, TextMode.Err, Messages.ClaimsDisabledWorld); + return; + } - //if he's at the claim count per player limit already and doesn't have permission to bypass, display an error message - if(instance.config_claims_maxClaimsPerPlayer > 0 && - !player.hasPermission("griefprevention.overrideclaimcountlimit") && - playerData.getClaims().size() >= instance.config_claims_maxClaimsPerPlayer) - { - instance.sendMessage(player, TextMode.Err, Messages.ClaimCreationFailedOverClaimCountLimit); - return; - } + //if he's at the claim count per player limit already and doesn't have permission to bypass, display an error message + if (instance.config_claims_maxClaimsPerPlayer > 0 && + !player.hasPermission("griefprevention.overrideclaimcountlimit") && + playerData.getClaims().size() >= instance.config_claims_maxClaimsPerPlayer) + { + instance.sendMessage(player, TextMode.Err, Messages.ClaimCreationFailedOverClaimCountLimit); + return; + } - //remember it, and start him on the new claim - playerData.lastShovelLocation = clickedBlock.getLocation(); - instance.sendMessage(player, TextMode.Instr, Messages.ClaimStart); + //remember it, and start him on the new claim + playerData.lastShovelLocation = clickedBlock.getLocation(); + instance.sendMessage(player, TextMode.Instr, Messages.ClaimStart); - //show him where he's working + //show him where he's working Claim newClaim = new Claim(clickedBlock.getLocation(), clickedBlock.getLocation(), null, new ArrayList(), new ArrayList(), new ArrayList(), new ArrayList(), null); - Visualization visualization = Visualization.FromClaim(newClaim, clickedBlock.getY(), VisualizationType.RestoreNature, player.getLocation()); + Visualization visualization = Visualization.FromClaim(newClaim, clickedBlock.getY(), VisualizationType.RestoreNature, player.getLocation()); // alert plugins of a visualization Bukkit.getPluginManager().callEvent(new VisualizationEvent(player, newClaim)); - Visualization.Apply(player, visualization); - } + Visualization.Apply(player, visualization); + } - //otherwise, he's trying to finish creating a claim by setting the other boundary corner - else - { - //if last shovel location was in a different world, assume the player is starting the create-claim workflow over - if(!lastShovelLocation.getWorld().equals(clickedBlock.getWorld())) - { - playerData.lastShovelLocation = null; - this.onPlayerInteract(event); - return; - } + //otherwise, he's trying to finish creating a claim by setting the other boundary corner + else + { + //if last shovel location was in a different world, assume the player is starting the create-claim workflow over + if (!lastShovelLocation.getWorld().equals(clickedBlock.getWorld())) + { + playerData.lastShovelLocation = null; + this.onPlayerInteract(event); + return; + } - //apply pvp rule - if(playerData.inPvpCombat()) - { - instance.sendMessage(player, TextMode.Err, Messages.NoClaimDuringPvP); - return; - } + //apply pvp rule + if (playerData.inPvpCombat()) + { + instance.sendMessage(player, TextMode.Err, Messages.NoClaimDuringPvP); + return; + } - //apply minimum claim dimensions rule - int newClaimWidth = Math.abs(playerData.lastShovelLocation.getBlockX() - clickedBlock.getX()) + 1; - int newClaimHeight = Math.abs(playerData.lastShovelLocation.getBlockZ() - clickedBlock.getZ()) + 1; + //apply minimum claim dimensions rule + int newClaimWidth = Math.abs(playerData.lastShovelLocation.getBlockX() - clickedBlock.getX()) + 1; + int newClaimHeight = Math.abs(playerData.lastShovelLocation.getBlockZ() - clickedBlock.getZ()) + 1; - if(playerData.shovelMode != ShovelMode.Admin) - { - if(newClaimWidth < instance.config_claims_minWidth || newClaimHeight < instance.config_claims_minWidth) - { - //this IF block is a workaround for craftbukkit bug which fires two events for one interaction - if(newClaimWidth != 1 && newClaimHeight != 1) - { - instance.sendMessage(player, TextMode.Err, Messages.NewClaimTooNarrow, String.valueOf(instance.config_claims_minWidth)); - } - return; - } - - int newArea = newClaimWidth * newClaimHeight; - if(newArea < instance.config_claims_minArea) + if (playerData.shovelMode != ShovelMode.Admin) + { + if (newClaimWidth < instance.config_claims_minWidth || newClaimHeight < instance.config_claims_minWidth) { - if(newArea != 1) + //this IF block is a workaround for craftbukkit bug which fires two events for one interaction + if (newClaimWidth != 1 && newClaimHeight != 1) + { + instance.sendMessage(player, TextMode.Err, Messages.NewClaimTooNarrow, String.valueOf(instance.config_claims_minWidth)); + } + return; + } + + int newArea = newClaimWidth * newClaimHeight; + if (newArea < instance.config_claims_minArea) + { + if (newArea != 1) { instance.sendMessage(player, TextMode.Err, Messages.ResizeClaimInsufficientArea, String.valueOf(instance.config_claims_minArea)); } return; } - } + } - //if not an administrative claim, verify the player has enough claim blocks for this new claim - if(playerData.shovelMode != ShovelMode.Admin) - { - int newClaimArea = newClaimWidth * newClaimHeight; - int remainingBlocks = playerData.getRemainingClaimBlocks(); - if(newClaimArea > remainingBlocks) - { - instance.sendMessage(player, TextMode.Err, Messages.CreateClaimInsufficientBlocks, String.valueOf(newClaimArea - remainingBlocks)); - instance.dataStore.tryAdvertiseAdminAlternatives(player); - return; - } - } - else - { - playerID = null; - } + //if not an administrative claim, verify the player has enough claim blocks for this new claim + if (playerData.shovelMode != ShovelMode.Admin) + { + int newClaimArea = newClaimWidth * newClaimHeight; + int remainingBlocks = playerData.getRemainingClaimBlocks(); + if (newClaimArea > remainingBlocks) + { + instance.sendMessage(player, TextMode.Err, Messages.CreateClaimInsufficientBlocks, String.valueOf(newClaimArea - remainingBlocks)); + instance.dataStore.tryAdvertiseAdminAlternatives(player); + return; + } + } else + { + playerID = null; + } - //try to create a new claim - CreateClaimResult result = this.dataStore.createClaim( - player.getWorld(), - lastShovelLocation.getBlockX(), clickedBlock.getX(), - lastShovelLocation.getBlockY() - instance.config_claims_claimsExtendIntoGroundDistance, clickedBlock.getY() - instance.config_claims_claimsExtendIntoGroundDistance, - lastShovelLocation.getBlockZ(), clickedBlock.getZ(), - playerID, - null, null, - player); + //try to create a new claim + CreateClaimResult result = this.dataStore.createClaim( + player.getWorld(), + lastShovelLocation.getBlockX(), clickedBlock.getX(), + lastShovelLocation.getBlockY() - instance.config_claims_claimsExtendIntoGroundDistance, clickedBlock.getY() - instance.config_claims_claimsExtendIntoGroundDistance, + lastShovelLocation.getBlockZ(), clickedBlock.getZ(), + playerID, + null, null, + player); - //if it didn't succeed, tell the player why - if(!result.succeeded) - { - if(result.claim != null) - { - instance.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlapShort); + //if it didn't succeed, tell the player why + if (!result.succeeded) + { + if (result.claim != null) + { + instance.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlapShort); - Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.ErrorClaim, player.getLocation()); + Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.ErrorClaim, player.getLocation()); // alert plugins of a visualization Bukkit.getPluginManager().callEvent(new VisualizationEvent(player, result.claim)); - Visualization.Apply(player, visualization); - } - else - { - instance.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlapRegion); - } + Visualization.Apply(player, visualization); + } else + { + instance.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlapRegion); + } - return; - } + return; + } - //otherwise, advise him on the /trust command and show him his new claim - else - { - instance.sendMessage(player, TextMode.Success, Messages.CreateClaimSuccess); - Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.Claim, player.getLocation()); + //otherwise, advise him on the /trust command and show him his new claim + else + { + instance.sendMessage(player, TextMode.Success, Messages.CreateClaimSuccess); + Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.Claim, player.getLocation()); // alert plugins of a visualization Bukkit.getPluginManager().callEvent(new VisualizationEvent(player, result.claim)); - Visualization.Apply(player, visualization); - playerData.lastShovelLocation = null; + Visualization.Apply(player, visualization); + playerData.lastShovelLocation = null; - //if it's a big claim, tell the player about subdivisions - if(!player.hasPermission("griefprevention.adminclaims") && result.claim.getArea() >= 1000) - { - instance.sendMessage(player, TextMode.Info, Messages.BecomeMayor, 200L); - instance.sendMessage(player, TextMode.Instr, Messages.SubdivisionVideo2, 201L, DataStore.SUBDIVISION_VIDEO_URL); - } + //if it's a big claim, tell the player about subdivisions + if (!player.hasPermission("griefprevention.adminclaims") && result.claim.getArea() >= 1000) + { + instance.sendMessage(player, TextMode.Info, Messages.BecomeMayor, 200L); + instance.sendMessage(player, TextMode.Instr, Messages.SubdivisionVideo2, 201L, DataStore.SUBDIVISION_VIDEO_URL); + } - instance.autoExtendClaim(result.claim); - } - } - } - } - - // Stops an untrusted player from removing a book from a lectern - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - void onTakeBook(PlayerTakeLecternBookEvent event) - { - Player player = event.getPlayer(); - PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); - Claim claim = this.dataStore.getClaimAt(event.getLectern().getLocation(), false, playerData.lastClaim); - if (claim != null) - { - playerData.lastClaim = claim; - String noContainerReason = claim.allowContainers(player); - if (noContainerReason != null) - { - event.setCancelled(true); - player.closeInventory(); - GriefPrevention.sendMessage(player, TextMode.Err, noContainerReason); - } - } - } - - //determines whether a block type is an inventory holder. uses a caching strategy to save cpu time - private ConcurrentHashMap inventoryHolderCache = new ConcurrentHashMap(); - private boolean isInventoryHolder(Block clickedBlock) - { - @SuppressWarnings("deprecation") - Material cacheKey = clickedBlock.getType(); - Boolean cachedValue = this.inventoryHolderCache.get(cacheKey); - if(cachedValue != null) - { - return cachedValue.booleanValue(); - - } - else - { - boolean isHolder = clickedBlock.getState() instanceof InventoryHolder; - this.inventoryHolderCache.put(cacheKey, isHolder); - return isHolder; - } + instance.autoExtendClaim(result.claim); + } + } } + } + + // Stops an untrusted player from removing a book from a lectern + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + void onTakeBook(PlayerTakeLecternBookEvent event) + { + Player player = event.getPlayer(); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); + Claim claim = this.dataStore.getClaimAt(event.getLectern().getLocation(), false, playerData.lastClaim); + if (claim != null) + { + playerData.lastClaim = claim; + String noContainerReason = claim.allowContainers(player); + if (noContainerReason != null) + { + event.setCancelled(true); + player.closeInventory(); + GriefPrevention.sendMessage(player, TextMode.Err, noContainerReason); + } + } + } + + //determines whether a block type is an inventory holder. uses a caching strategy to save cpu time + private ConcurrentHashMap inventoryHolderCache = new ConcurrentHashMap(); + + private boolean isInventoryHolder(Block clickedBlock) + { + @SuppressWarnings("deprecation") + Material cacheKey = clickedBlock.getType(); + Boolean cachedValue = this.inventoryHolderCache.get(cacheKey); + if (cachedValue != null) + { + return cachedValue.booleanValue(); + + } else + { + boolean isHolder = clickedBlock.getState() instanceof InventoryHolder; + this.inventoryHolderCache.put(cacheKey, isHolder); + return isHolder; + } + } private boolean onLeftClickWatchList(Material material) - { - switch(material) + { + switch (material) { case OAK_BUTTON: case SPRUCE_BUTTON: @@ -2698,22 +2686,22 @@ class PlayerEventHandler implements Listener } static Block getTargetBlock(Player player, int maxDistance) throws IllegalStateException - { + { Location eye = player.getEyeLocation(); Material eyeMaterial = eye.getBlock().getType(); - boolean passThroughWater = (eyeMaterial == Material.WATER); + boolean passThroughWater = (eyeMaterial == Material.WATER); BlockIterator iterator = new BlockIterator(player.getLocation(), player.getEyeHeight(), maxDistance); - Block result = player.getLocation().getBlock().getRelative(BlockFace.UP); - while (iterator.hasNext()) - { - result = iterator.next(); - Material type = result.getType(); - if(type != Material.AIR && - (!passThroughWater || type != Material.WATER) && - type != Material.GRASS && - type != Material.SNOW) return result; - } - - return result; + Block result = player.getLocation().getBlock().getRelative(BlockFace.UP); + while (iterator.hasNext()) + { + result = iterator.next(); + Material type = result.getType(); + if (type != Material.AIR && + (!passThroughWater || type != Material.WATER) && + type != Material.GRASS && + type != Material.SNOW) return result; + } + + return result; } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/PlayerKickBanTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/PlayerKickBanTask.java index a06eb8d..68051e9 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/PlayerKickBanTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/PlayerKickBanTask.java @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - + package me.ryanhamshire.GriefPrevention; import me.ryanhamshire.GriefPrevention.events.PlayerKickBanEvent; @@ -25,47 +25,46 @@ import org.bukkit.entity.Player; //kicks or bans a player //need a task for this because async threads (like the chat event handlers) can't kick or ban. //but they CAN schedule a task to run in the main thread to do that job -class PlayerKickBanTask implements Runnable +class PlayerKickBanTask implements Runnable { - //player to kick or ban - private Player player; - - //message to send player. - private String reason; - - //source of ban - private String source; - - //whether to ban - private boolean ban; - - public PlayerKickBanTask(Player player, String reason, String source, boolean ban) - { - this.player = player; - this.reason = reason; - this.source = source; - this.ban = ban; - } - - @Override - public void run() - { - PlayerKickBanEvent kickBanEvent = new PlayerKickBanEvent(player, reason, source, ban); - Bukkit.getPluginManager().callEvent(kickBanEvent); + //player to kick or ban + private Player player; - if (kickBanEvent.isCancelled()) - { - return; // cancelled by a plugin - } + //message to send player. + private String reason; - if(this.ban) - { - //ban - GriefPrevention.banPlayer(this.player, this.reason, this.source); - } - else if(this.player.isOnline()) - { - this.player.kickPlayer(this.reason); - } - } + //source of ban + private String source; + + //whether to ban + private boolean ban; + + public PlayerKickBanTask(Player player, String reason, String source, boolean ban) + { + this.player = player; + this.reason = reason; + this.source = source; + this.ban = ban; + } + + @Override + public void run() + { + PlayerKickBanEvent kickBanEvent = new PlayerKickBanEvent(player, reason, source, ban); + Bukkit.getPluginManager().callEvent(kickBanEvent); + + if (kickBanEvent.isCancelled()) + { + return; // cancelled by a plugin + } + + if (this.ban) + { + //ban + GriefPrevention.banPlayer(this.player, this.reason, this.source); + } else if (this.player.isOnline()) + { + this.player.kickPlayer(this.reason); + } + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/PlayerRescueTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/PlayerRescueTask.java index 44a6802..ee1939d 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/PlayerRescueTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/PlayerRescueTask.java @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - + package me.ryanhamshire.GriefPrevention; import org.bukkit.Location; @@ -24,52 +24,51 @@ import org.bukkit.entity.Player; //tries to rescue a trapped player from a claim where he doesn't have permission to save himself //related to the /trapped slash command //this does run in the main thread, so it's okay to make non-thread-safe calls -class PlayerRescueTask implements Runnable +class PlayerRescueTask implements Runnable { - //original location where /trapped was used - private Location location; - - //rescue destination, may be decided at instantiation or at execution + //original location where /trapped was used + private Location location; + + //rescue destination, may be decided at instantiation or at execution private Location destination; - - //player data - private Player player; - - public PlayerRescueTask(Player player, Location location, Location destination) - { - this.player = player; - this.location = location; - this.destination = destination; - } - - @Override - public void run() - { - //if he logged out, don't do anything - if(!player.isOnline()) return; - - //he no longer has a pending /trapped slash command, so he can try to use it again now - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); - playerData.pendingTrapped = false; - - //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, Messages.RescueAbortedMoved); - return; - } - - //otherwise find a place to teleport him - if(this.destination == null) - { - this.destination = GriefPrevention.instance.ejectPlayer(this.player); - } - else - { - player.teleport(this.destination); - } - - //log entry, in case admins want to investigate the "trap" - GriefPrevention.AddLogEntry("Rescued trapped player " + player.getName() + " from " + GriefPrevention.getfriendlyLocationString(this.location) + " to " + GriefPrevention.getfriendlyLocationString(this.destination) + "."); - } + + //player data + private Player player; + + public PlayerRescueTask(Player player, Location location, Location destination) + { + this.player = player; + this.location = location; + this.destination = destination; + } + + @Override + public void run() + { + //if he logged out, don't do anything + if (!player.isOnline()) return; + + //he no longer has a pending /trapped slash command, so he can try to use it again now + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); + playerData.pendingTrapped = false; + + //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, Messages.RescueAbortedMoved); + return; + } + + //otherwise find a place to teleport him + if (this.destination == null) + { + this.destination = GriefPrevention.instance.ejectPlayer(this.player); + } else + { + player.teleport(this.destination); + } + + //log entry, in case admins want to investigate the "trap" + GriefPrevention.AddLogEntry("Rescued trapped player " + player.getName() + " from " + GriefPrevention.getfriendlyLocationString(this.location) + " to " + GriefPrevention.getfriendlyLocationString(this.destination) + "."); + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/PvPImmunityValidationTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/PvPImmunityValidationTask.java index a03e748..0fc4fe7 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/PvPImmunityValidationTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/PvPImmunityValidationTask.java @@ -15,41 +15,40 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - + package me.ryanhamshire.GriefPrevention; import org.bukkit.entity.Player; //sends a message to a player //used to send delayed messages, for example help text triggered by a player's chat -class PvPImmunityValidationTask implements Runnable +class PvPImmunityValidationTask implements Runnable { - private Player player; - - public PvPImmunityValidationTask(Player player) - { - this.player = player; - } + private Player player; - @Override - public void run() - { - if(!player.isOnline()) return; - - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); - if(!playerData.pvpImmune) return; - - //check the player's inventory for anything - if(!GriefPrevention.isInventoryEmpty(player)) - { - //if found, cancel invulnerability and notify - playerData.pvpImmune = false; - GriefPrevention.sendMessage(player, TextMode.Warn, Messages.PvPImmunityEnd); - } - else - { - //otherwise check again in one minute - GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, this, 1200L); - } - } + public PvPImmunityValidationTask(Player player) + { + this.player = player; + } + + @Override + public void run() + { + if (!player.isOnline()) return; + + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); + if (!playerData.pvpImmune) return; + + //check the player's inventory for anything + if (!GriefPrevention.isInventoryEmpty(player)) + { + //if found, cancel invulnerability and notify + playerData.pvpImmune = false; + GriefPrevention.sendMessage(player, TextMode.Warn, Messages.PvPImmunityEnd); + } else + { + //otherwise check again in one minute + GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, this, 1200L); + } + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java index b5c5f56..331b9b3 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java @@ -15,10 +15,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - -package me.ryanhamshire.GriefPrevention; -import java.util.ArrayList; +package me.ryanhamshire.GriefPrevention; import org.bukkit.Chunk; import org.bukkit.Location; @@ -30,101 +28,103 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.Hanging; import org.bukkit.entity.Player; +import java.util.ArrayList; + //this main thread task takes the output from the RestoreNatureProcessingTask\ //and updates the world accordingly -class RestoreNatureExecutionTask implements Runnable +class RestoreNatureExecutionTask implements Runnable { - //results from processing thread - //will be applied to the world - private BlockSnapshot[][][] snapshots; - - //boundaries for changes - private int miny; - private Location lesserCorner; - private Location greaterCorner; - - //player who should be notified about the result (will see a visualization when the restoration is complete) - private Player player; + //results from processing thread + //will be applied to the world + private BlockSnapshot[][][] snapshots; - public RestoreNatureExecutionTask(BlockSnapshot[][][] snapshots, int miny, Location lesserCorner, Location greaterCorner, Player player) - { - this.snapshots = snapshots; - this.miny = miny; - this.lesserCorner = lesserCorner; - this.greaterCorner = greaterCorner; - this.player = player; - } - - @SuppressWarnings("deprecation") - @Override - public void run() - { - //apply changes to the world, but ONLY to unclaimed blocks - //note that the edge of the results is not applied (the 1-block-wide band around the outside of the chunk) - //those data were sent to the processing thread for referernce purposes, but aren't part of the area selected for restoration - Claim cachedClaim = null; - for(int x = 1; x < this.snapshots.length - 1; x++) - { - for(int z = 1; z < this.snapshots[0][0].length - 1; z++) - { - for(int y = this.miny; y < this.snapshots[0].length; y++) - { - BlockSnapshot blockUpdate = this.snapshots[x][y][z]; - Block currentBlock = blockUpdate.location.getBlock(); - if(blockUpdate.typeId != currentBlock.getType()|| !blockUpdate.data.equals(currentBlock.getBlockData())) - { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(blockUpdate.location, false, cachedClaim); - if(claim != null) - { - cachedClaim = claim; - break; - } - - try - { - currentBlock.setType(blockUpdate.typeId, false); - // currentBlock.setBlockData(blockUpdate.data, false); - } - catch(IllegalArgumentException e) - { - //just don't update this block and continue trying to update other blocks - } - } - } - } - } - - //clean up any entities in the chunk, ensure no players are suffocated - Chunk chunk = this.lesserCorner.getChunk(); - Entity [] entities = chunk.getEntities(); - for(int i = 0; i < entities.length; i++) - { - Entity entity = entities[i]; - if(!(entity instanceof Player || entity instanceof Animals)) - { - //hanging entities (paintings, item frames) are protected when they're in land claims - if(!(entity instanceof Hanging) || GriefPrevention.instance.dataStore.getClaimAt(entity.getLocation(), false, null) == null) - { - //everything else is removed - entity.remove(); - } - } - - //for players, always ensure there's air where the player is standing - else - { - Block feetBlock = entity.getLocation().getBlock(); - feetBlock.setType(Material.AIR); - feetBlock.getRelative(BlockFace.UP).setType(Material.AIR); - } - } - - //show visualization to player who started the restoration - if(player != null) - { - Claim claim = new Claim(lesserCorner, greaterCorner, null, new ArrayList(), new ArrayList(), new ArrayList(), new ArrayList(), null); - Visualization visualization = Visualization.FromClaim(claim, player.getLocation().getBlockY(), VisualizationType.RestoreNature, player.getLocation()); - Visualization.Apply(player, visualization); - } - } + //boundaries for changes + private int miny; + private Location lesserCorner; + private Location greaterCorner; + + //player who should be notified about the result (will see a visualization when the restoration is complete) + private Player player; + + public RestoreNatureExecutionTask(BlockSnapshot[][][] snapshots, int miny, Location lesserCorner, Location greaterCorner, Player player) + { + this.snapshots = snapshots; + this.miny = miny; + this.lesserCorner = lesserCorner; + this.greaterCorner = greaterCorner; + this.player = player; + } + + @SuppressWarnings("deprecation") + @Override + public void run() + { + //apply changes to the world, but ONLY to unclaimed blocks + //note that the edge of the results is not applied (the 1-block-wide band around the outside of the chunk) + //those data were sent to the processing thread for referernce purposes, but aren't part of the area selected for restoration + Claim cachedClaim = null; + for (int x = 1; x < this.snapshots.length - 1; x++) + { + for (int z = 1; z < this.snapshots[0][0].length - 1; z++) + { + for (int y = this.miny; y < this.snapshots[0].length; y++) + { + BlockSnapshot blockUpdate = this.snapshots[x][y][z]; + Block currentBlock = blockUpdate.location.getBlock(); + if (blockUpdate.typeId != currentBlock.getType() || !blockUpdate.data.equals(currentBlock.getBlockData())) + { + Claim claim = GriefPrevention.instance.dataStore.getClaimAt(blockUpdate.location, false, cachedClaim); + if (claim != null) + { + cachedClaim = claim; + break; + } + + try + { + currentBlock.setType(blockUpdate.typeId, false); + // currentBlock.setBlockData(blockUpdate.data, false); + } + catch (IllegalArgumentException e) + { + //just don't update this block and continue trying to update other blocks + } + } + } + } + } + + //clean up any entities in the chunk, ensure no players are suffocated + Chunk chunk = this.lesserCorner.getChunk(); + Entity[] entities = chunk.getEntities(); + for (int i = 0; i < entities.length; i++) + { + Entity entity = entities[i]; + if (!(entity instanceof Player || entity instanceof Animals)) + { + //hanging entities (paintings, item frames) are protected when they're in land claims + if (!(entity instanceof Hanging) || GriefPrevention.instance.dataStore.getClaimAt(entity.getLocation(), false, null) == null) + { + //everything else is removed + entity.remove(); + } + } + + //for players, always ensure there's air where the player is standing + else + { + Block feetBlock = entity.getLocation().getBlock(); + feetBlock.setType(Material.AIR); + feetBlock.getRelative(BlockFace.UP).setType(Material.AIR); + } + } + + //show visualization to player who started the restoration + if (player != null) + { + Claim claim = new Claim(lesserCorner, greaterCorner, null, new ArrayList(), new ArrayList(), new ArrayList(), new ArrayList(), null); + Visualization visualization = Visualization.FromClaim(claim, player.getLocation().getBlockY(), VisualizationType.RestoreNature, player.getLocation()); + Visualization.Apply(player, visualization); + } + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/RestoreNatureProcessingTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/RestoreNatureProcessingTask.java index d26b196..338d7c8 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/RestoreNatureProcessingTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/RestoreNatureProcessingTask.java @@ -15,10 +15,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - -package me.ryanhamshire.GriefPrevention; -import java.util.ArrayList; +package me.ryanhamshire.GriefPrevention; import org.bukkit.Location; import org.bukkit.Material; @@ -29,944 +27,945 @@ import org.bukkit.block.data.Levelled; import org.bukkit.block.data.type.Leaves; import org.bukkit.entity.Player; +import java.util.ArrayList; + //non-main-thread task which processes world data to repair the unnatural //after processing is complete, creates a main thread task to make the necessary changes to the world -class RestoreNatureProcessingTask implements Runnable +class RestoreNatureProcessingTask implements Runnable { - //world information captured from the main thread - //will be updated and sent back to main thread to be applied to the world - private BlockSnapshot[][][] snapshots; - - //other information collected from the main thread. - //not to be updated, only to be passed back to main thread to provide some context about the operation - private int miny; - private Environment environment; - private Location lesserBoundaryCorner; - private Location greaterBoundaryCorner; - private Player player; //absolutely must not be accessed. not thread safe. - private Biome biome; - private boolean creativeMode; - private int seaLevel; - private boolean aggressiveMode; - - //two lists of materials - private ArrayList notAllowedToHang; //natural blocks which don't naturally hang in their air - private ArrayList playerBlocks; //a "complete" list of player-placed blocks. MUST BE MAINTAINED as patches introduce more - - @SuppressWarnings("deprecation") + //world information captured from the main thread + //will be updated and sent back to main thread to be applied to the world + private BlockSnapshot[][][] snapshots; + + //other information collected from the main thread. + //not to be updated, only to be passed back to main thread to provide some context about the operation + private int miny; + private Environment environment; + private Location lesserBoundaryCorner; + private Location greaterBoundaryCorner; + private Player player; //absolutely must not be accessed. not thread safe. + private Biome biome; + private boolean creativeMode; + private int seaLevel; + private boolean aggressiveMode; + + //two lists of materials + private ArrayList notAllowedToHang; //natural blocks which don't naturally hang in their air + private ArrayList playerBlocks; //a "complete" list of player-placed blocks. MUST BE MAINTAINED as patches introduce more + + @SuppressWarnings("deprecation") public RestoreNatureProcessingTask(BlockSnapshot[][][] snapshots, int miny, Environment environment, Biome biome, Location lesserBoundaryCorner, Location greaterBoundaryCorner, int seaLevel, boolean aggressiveMode, boolean creativeMode, Player player) - { - this.snapshots = snapshots; - this.miny = miny; - if(this.miny < 0) this.miny = 0; - this.environment = environment; - this.lesserBoundaryCorner = lesserBoundaryCorner; - this.greaterBoundaryCorner = greaterBoundaryCorner; - this.biome = biome; - this.seaLevel = seaLevel; - this.aggressiveMode = aggressiveMode; - this.player = player; - this.creativeMode = creativeMode; - - this.notAllowedToHang = new ArrayList(); - this.notAllowedToHang.add(Material.DIRT); - this.notAllowedToHang.add(Material.GRASS); - this.notAllowedToHang.add(Material.SNOW); - this.notAllowedToHang.add(Material.OAK_LOG); - this.notAllowedToHang.add(Material.SPRUCE_LOG); - this.notAllowedToHang.add(Material.BIRCH_LOG); - this.notAllowedToHang.add(Material.JUNGLE_LOG); - this.notAllowedToHang.add(Material.ACACIA_LOG); - this.notAllowedToHang.add(Material.DARK_OAK_LOG); - - if(this.aggressiveMode) - { - this.notAllowedToHang.add(Material.GRASS); - this.notAllowedToHang.add(Material.STONE); - } - - this.playerBlocks = new ArrayList(); - this.playerBlocks.addAll(RestoreNatureProcessingTask.getPlayerBlocks(this.environment, this.biome)); - - //in aggressive or creative world mode, also treat these blocks as user placed, to be removed - //this is helpful in the few cases where griefers intentionally use natural blocks to grief, - //like a single-block tower of iron ore or a giant penis constructed with melons - if(this.aggressiveMode || this.creativeMode) - { - this.playerBlocks.add(Material.IRON_ORE); - this.playerBlocks.add(Material.GOLD_ORE); - this.playerBlocks.add(Material.DIAMOND_ORE); - this.playerBlocks.add(Material.MELON); - this.playerBlocks.add(Material.MELON_STEM); - this.playerBlocks.add(Material.BEDROCK); - this.playerBlocks.add(Material.COAL_ORE); - this.playerBlocks.add(Material.PUMPKIN); - this.playerBlocks.add(Material.PUMPKIN_STEM); - } - - if(this.aggressiveMode) - { - this.playerBlocks.add(Material.OAK_LEAVES); - this.playerBlocks.add(Material.SPRUCE_LEAVES); - this.playerBlocks.add(Material.BIRCH_LEAVES); - this.playerBlocks.add(Material.JUNGLE_LEAVES); - this.playerBlocks.add(Material.ACACIA_LEAVES); - this.playerBlocks.add(Material.DARK_OAK_LEAVES); - this.playerBlocks.add(Material.OAK_LOG); - this.playerBlocks.add(Material.SPRUCE_LOG); - this.playerBlocks.add(Material.BIRCH_LOG); - this.playerBlocks.add(Material.JUNGLE_LOG); - this.playerBlocks.add(Material.ACACIA_LOG); - this.playerBlocks.add(Material.DARK_OAK_LOG); - this.playerBlocks.add(Material.VINE); - } - } - - @Override - public void run() - { - //order is important! - - //remove sandstone which appears to be unnatural - this.removeSandstone(); - - //remove any blocks which are definitely player placed - this.removePlayerBlocks(); - - //reduce large outcroppings of stone, sandstone - this.reduceStone(); - - //reduce logs, except in jungle biomes - this.reduceLogs(); - - //remove natural blocks which are unnaturally hanging in the air - this.removeHanging(); - - //remove natural blocks which are unnaturally stacked high - this.removeWallsAndTowers(); - - //fill unnatural thin trenches and single-block potholes - this.fillHolesAndTrenches(); - - //fill water depressions and fix unnatural surface ripples - //this.fixWater(); - - //remove water/lava above sea level - this.removeDumpedFluids(); - - //cover surface stone and gravel with sand or grass, as the biome requires - this.coverSurfaceStone(); - - //remove any player-placed leaves - ///this.removePlayerLeaves(); - - //schedule main thread task to apply the result to the world - RestoreNatureExecutionTask task = new RestoreNatureExecutionTask(this.snapshots, this.miny, this.lesserBoundaryCorner, this.greaterBoundaryCorner, this.player); - GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task); - } - - @SuppressWarnings("deprecation") + { + this.snapshots = snapshots; + this.miny = miny; + if (this.miny < 0) this.miny = 0; + this.environment = environment; + this.lesserBoundaryCorner = lesserBoundaryCorner; + this.greaterBoundaryCorner = greaterBoundaryCorner; + this.biome = biome; + this.seaLevel = seaLevel; + this.aggressiveMode = aggressiveMode; + this.player = player; + this.creativeMode = creativeMode; + + this.notAllowedToHang = new ArrayList(); + this.notAllowedToHang.add(Material.DIRT); + this.notAllowedToHang.add(Material.GRASS); + this.notAllowedToHang.add(Material.SNOW); + this.notAllowedToHang.add(Material.OAK_LOG); + this.notAllowedToHang.add(Material.SPRUCE_LOG); + this.notAllowedToHang.add(Material.BIRCH_LOG); + this.notAllowedToHang.add(Material.JUNGLE_LOG); + this.notAllowedToHang.add(Material.ACACIA_LOG); + this.notAllowedToHang.add(Material.DARK_OAK_LOG); + + if (this.aggressiveMode) + { + this.notAllowedToHang.add(Material.GRASS); + this.notAllowedToHang.add(Material.STONE); + } + + this.playerBlocks = new ArrayList(); + this.playerBlocks.addAll(RestoreNatureProcessingTask.getPlayerBlocks(this.environment, this.biome)); + + //in aggressive or creative world mode, also treat these blocks as user placed, to be removed + //this is helpful in the few cases where griefers intentionally use natural blocks to grief, + //like a single-block tower of iron ore or a giant penis constructed with melons + if (this.aggressiveMode || this.creativeMode) + { + this.playerBlocks.add(Material.IRON_ORE); + this.playerBlocks.add(Material.GOLD_ORE); + this.playerBlocks.add(Material.DIAMOND_ORE); + this.playerBlocks.add(Material.MELON); + this.playerBlocks.add(Material.MELON_STEM); + this.playerBlocks.add(Material.BEDROCK); + this.playerBlocks.add(Material.COAL_ORE); + this.playerBlocks.add(Material.PUMPKIN); + this.playerBlocks.add(Material.PUMPKIN_STEM); + } + + if (this.aggressiveMode) + { + this.playerBlocks.add(Material.OAK_LEAVES); + this.playerBlocks.add(Material.SPRUCE_LEAVES); + this.playerBlocks.add(Material.BIRCH_LEAVES); + this.playerBlocks.add(Material.JUNGLE_LEAVES); + this.playerBlocks.add(Material.ACACIA_LEAVES); + this.playerBlocks.add(Material.DARK_OAK_LEAVES); + this.playerBlocks.add(Material.OAK_LOG); + this.playerBlocks.add(Material.SPRUCE_LOG); + this.playerBlocks.add(Material.BIRCH_LOG); + this.playerBlocks.add(Material.JUNGLE_LOG); + this.playerBlocks.add(Material.ACACIA_LOG); + this.playerBlocks.add(Material.DARK_OAK_LOG); + this.playerBlocks.add(Material.VINE); + } + } + + @Override + public void run() + { + //order is important! + + //remove sandstone which appears to be unnatural + this.removeSandstone(); + + //remove any blocks which are definitely player placed + this.removePlayerBlocks(); + + //reduce large outcroppings of stone, sandstone + this.reduceStone(); + + //reduce logs, except in jungle biomes + this.reduceLogs(); + + //remove natural blocks which are unnaturally hanging in the air + this.removeHanging(); + + //remove natural blocks which are unnaturally stacked high + this.removeWallsAndTowers(); + + //fill unnatural thin trenches and single-block potholes + this.fillHolesAndTrenches(); + + //fill water depressions and fix unnatural surface ripples + //this.fixWater(); + + //remove water/lava above sea level + this.removeDumpedFluids(); + + //cover surface stone and gravel with sand or grass, as the biome requires + this.coverSurfaceStone(); + + //remove any player-placed leaves + ///this.removePlayerLeaves(); + + //schedule main thread task to apply the result to the world + RestoreNatureExecutionTask task = new RestoreNatureExecutionTask(this.snapshots, this.miny, this.lesserBoundaryCorner, this.greaterBoundaryCorner, this.player); + GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task); + } + + @SuppressWarnings("deprecation") private void removePlayerLeaves() - { - if(this.seaLevel < 1) return; - - for(int x = 1; x < snapshots.length - 1; x++) - { - for(int z = 1; z < snapshots[0][0].length - 1; z++) - { - for(int y = this.seaLevel - 1; y < snapshots[0].length; y++) - { - BlockSnapshot block = snapshots[x][y][z]; - if(Tag.LEAVES.isTagged(block.typeId) && ((Leaves) block.data).isPersistent()) - { - block.typeId = Material.AIR; - } - } - } - } - } + { + if (this.seaLevel < 1) return; - //converts sandstone adjacent to sand to sand, and any other sandstone to air - @SuppressWarnings("deprecation") + for (int x = 1; x < snapshots.length - 1; x++) + { + for (int z = 1; z < snapshots[0][0].length - 1; z++) + { + for (int y = this.seaLevel - 1; y < snapshots[0].length; y++) + { + BlockSnapshot block = snapshots[x][y][z]; + if (Tag.LEAVES.isTagged(block.typeId) && ((Leaves) block.data).isPersistent()) + { + block.typeId = Material.AIR; + } + } + } + } + } + + //converts sandstone adjacent to sand to sand, and any other sandstone to air + @SuppressWarnings("deprecation") private void removeSandstone() - { - for(int x = 1; x < snapshots.length - 1; x++) - { - for(int z = 1; z < snapshots[0][0].length - 1; z++) - { - for(int y = snapshots[0].length - 2; y > miny; y--) - { - if(snapshots[x][y][z].typeId != Material.SANDSTONE) continue; - - BlockSnapshot leftBlock = this.snapshots[x + 1][y][z]; - BlockSnapshot rightBlock = this.snapshots[x - 1][y][z]; - BlockSnapshot upBlock = this.snapshots[x][y][z + 1]; - BlockSnapshot downBlock = this.snapshots[x][y][z - 1]; - BlockSnapshot underBlock = this.snapshots[x][y - 1][z]; - BlockSnapshot aboveBlock = this.snapshots[x][y + 1][z]; - - //skip blocks which may cause a cave-in - if(aboveBlock.typeId == Material.SAND && underBlock.typeId == Material.AIR) continue; - - //count adjacent non-air/non-leaf blocks - if( leftBlock.typeId == Material.SAND || - rightBlock.typeId == Material.SAND || - upBlock.typeId == Material.SAND || - downBlock.typeId == Material.SAND || - aboveBlock.typeId == Material.SAND || - underBlock.typeId == Material.SAND) - { - snapshots[x][y][z].typeId = Material.SAND; - } - else - { - snapshots[x][y][z].typeId = Material.AIR; - } - } - } - } - } - - @SuppressWarnings("deprecation") + { + for (int x = 1; x < snapshots.length - 1; x++) + { + for (int z = 1; z < snapshots[0][0].length - 1; z++) + { + for (int y = snapshots[0].length - 2; y > miny; y--) + { + if (snapshots[x][y][z].typeId != Material.SANDSTONE) continue; + + BlockSnapshot leftBlock = this.snapshots[x + 1][y][z]; + BlockSnapshot rightBlock = this.snapshots[x - 1][y][z]; + BlockSnapshot upBlock = this.snapshots[x][y][z + 1]; + BlockSnapshot downBlock = this.snapshots[x][y][z - 1]; + BlockSnapshot underBlock = this.snapshots[x][y - 1][z]; + BlockSnapshot aboveBlock = this.snapshots[x][y + 1][z]; + + //skip blocks which may cause a cave-in + if (aboveBlock.typeId == Material.SAND && underBlock.typeId == Material.AIR) continue; + + //count adjacent non-air/non-leaf blocks + if (leftBlock.typeId == Material.SAND || + rightBlock.typeId == Material.SAND || + upBlock.typeId == Material.SAND || + downBlock.typeId == Material.SAND || + aboveBlock.typeId == Material.SAND || + underBlock.typeId == Material.SAND) + { + snapshots[x][y][z].typeId = Material.SAND; + } else + { + snapshots[x][y][z].typeId = Material.AIR; + } + } + } + } + } + + @SuppressWarnings("deprecation") private void reduceStone() - { - if(this.seaLevel < 1) return; - - for(int x = 1; x < snapshots.length - 1; x++) - { - for(int z = 1; z < snapshots[0][0].length - 1; z++) - { - int thisy = this.highestY(x, z, true); - - while(thisy > this.seaLevel - 1 && (this.snapshots[x][thisy][z].typeId == Material.STONE || this.snapshots[x][thisy][z].typeId == Material.SANDSTONE)) - { - BlockSnapshot leftBlock = this.snapshots[x + 1][thisy][z]; - BlockSnapshot rightBlock = this.snapshots[x - 1][thisy][z]; - BlockSnapshot upBlock = this.snapshots[x][thisy][z + 1]; - BlockSnapshot downBlock = this.snapshots[x][thisy][z - 1]; - - //count adjacent non-air/non-leaf blocks - byte adjacentBlockCount = 0; - if(leftBlock.typeId != Material.AIR && !Tag.LEAVES.isTagged(leftBlock.typeId) && leftBlock.typeId != Material.VINE) - { - adjacentBlockCount++; - } - if(rightBlock.typeId != Material.AIR && !Tag.LEAVES.isTagged(rightBlock.typeId) && rightBlock.typeId != Material.VINE) - { - adjacentBlockCount++; - } - if(downBlock.typeId != Material.AIR && !Tag.LEAVES.isTagged(downBlock.typeId) && downBlock.typeId != Material.VINE) - { - adjacentBlockCount++; - } - if(upBlock.typeId != Material.AIR && !Tag.LEAVES.isTagged(upBlock.typeId) && upBlock.typeId != Material.VINE) - { - adjacentBlockCount++; - } - - if(adjacentBlockCount < 3) - { - this.snapshots[x][thisy][z].typeId = Material.AIR; - } + { + if (this.seaLevel < 1) return; - thisy--; - } - } - } - } - - @SuppressWarnings("deprecation") + for (int x = 1; x < snapshots.length - 1; x++) + { + for (int z = 1; z < snapshots[0][0].length - 1; z++) + { + int thisy = this.highestY(x, z, true); + + while (thisy > this.seaLevel - 1 && (this.snapshots[x][thisy][z].typeId == Material.STONE || this.snapshots[x][thisy][z].typeId == Material.SANDSTONE)) + { + BlockSnapshot leftBlock = this.snapshots[x + 1][thisy][z]; + BlockSnapshot rightBlock = this.snapshots[x - 1][thisy][z]; + BlockSnapshot upBlock = this.snapshots[x][thisy][z + 1]; + BlockSnapshot downBlock = this.snapshots[x][thisy][z - 1]; + + //count adjacent non-air/non-leaf blocks + byte adjacentBlockCount = 0; + if (leftBlock.typeId != Material.AIR && !Tag.LEAVES.isTagged(leftBlock.typeId) && leftBlock.typeId != Material.VINE) + { + adjacentBlockCount++; + } + if (rightBlock.typeId != Material.AIR && !Tag.LEAVES.isTagged(rightBlock.typeId) && rightBlock.typeId != Material.VINE) + { + adjacentBlockCount++; + } + if (downBlock.typeId != Material.AIR && !Tag.LEAVES.isTagged(downBlock.typeId) && downBlock.typeId != Material.VINE) + { + adjacentBlockCount++; + } + if (upBlock.typeId != Material.AIR && !Tag.LEAVES.isTagged(upBlock.typeId) && upBlock.typeId != Material.VINE) + { + adjacentBlockCount++; + } + + if (adjacentBlockCount < 3) + { + this.snapshots[x][thisy][z].typeId = Material.AIR; + } + + thisy--; + } + } + } + } + + @SuppressWarnings("deprecation") private void reduceLogs() - { - if(this.seaLevel < 1) return; - - boolean jungleBiome = this.biome == Biome.JUNGLE || this.biome == Biome.JUNGLE_HILLS; - - //scan all blocks above sea level - for(int x = 1; x < snapshots.length - 1; x++) - { - for(int z = 1; z < snapshots[0][0].length - 1; z++) - { - for(int y = this.seaLevel - 1; y < snapshots[0].length; y++) - { - BlockSnapshot block = snapshots[x][y][z]; - - //skip non-logs - if(!Tag.LOGS.isTagged(block.typeId)) continue; - - //if in jungle biome, skip jungle logs - if(jungleBiome && block.typeId == Material.JUNGLE_LOG) continue; - - //examine adjacent blocks for logs - BlockSnapshot leftBlock = this.snapshots[x + 1][y][z]; - BlockSnapshot rightBlock = this.snapshots[x - 1][y][z]; - BlockSnapshot upBlock = this.snapshots[x][y][z + 1]; - BlockSnapshot downBlock = this.snapshots[x][y][z - 1]; - - //if any, remove the log - if(Tag.LOGS.isTagged(leftBlock.typeId) || Tag.LOGS.isTagged(rightBlock.typeId) || Tag.LOGS.isTagged(upBlock.typeId) || Tag.LOGS.isTagged(downBlock.typeId)) - { - this.snapshots[x][y][z].typeId = Material.AIR; - } - } - } - } - } - - @SuppressWarnings("deprecation") - private void removePlayerBlocks() - { - int miny = this.miny; - if(miny < 1) miny = 1; - - //remove all player blocks - for(int x = 1; x < snapshots.length - 1; x++) - { - for(int z = 1; z < snapshots[0][0].length - 1; z++) - { - for(int y = miny; y < snapshots[0].length - 1; y++) - { - BlockSnapshot block = snapshots[x][y][z]; + { + if (this.seaLevel < 1) return; - if(this.playerBlocks.contains(block.typeId)) - { - block.typeId = Material.AIR; - } - } - } - } - } - - @SuppressWarnings("deprecation") + boolean jungleBiome = this.biome == Biome.JUNGLE || this.biome == Biome.JUNGLE_HILLS; + + //scan all blocks above sea level + for (int x = 1; x < snapshots.length - 1; x++) + { + for (int z = 1; z < snapshots[0][0].length - 1; z++) + { + for (int y = this.seaLevel - 1; y < snapshots[0].length; y++) + { + BlockSnapshot block = snapshots[x][y][z]; + + //skip non-logs + if (!Tag.LOGS.isTagged(block.typeId)) continue; + + //if in jungle biome, skip jungle logs + if (jungleBiome && block.typeId == Material.JUNGLE_LOG) continue; + + //examine adjacent blocks for logs + BlockSnapshot leftBlock = this.snapshots[x + 1][y][z]; + BlockSnapshot rightBlock = this.snapshots[x - 1][y][z]; + BlockSnapshot upBlock = this.snapshots[x][y][z + 1]; + BlockSnapshot downBlock = this.snapshots[x][y][z - 1]; + + //if any, remove the log + if (Tag.LOGS.isTagged(leftBlock.typeId) || Tag.LOGS.isTagged(rightBlock.typeId) || Tag.LOGS.isTagged(upBlock.typeId) || Tag.LOGS.isTagged(downBlock.typeId)) + { + this.snapshots[x][y][z].typeId = Material.AIR; + } + } + } + } + } + + @SuppressWarnings("deprecation") + private void removePlayerBlocks() + { + int miny = this.miny; + if (miny < 1) miny = 1; + + //remove all player blocks + for (int x = 1; x < snapshots.length - 1; x++) + { + for (int z = 1; z < snapshots[0][0].length - 1; z++) + { + for (int y = miny; y < snapshots[0].length - 1; y++) + { + BlockSnapshot block = snapshots[x][y][z]; + + if (this.playerBlocks.contains(block.typeId)) + { + block.typeId = Material.AIR; + } + } + } + } + } + + @SuppressWarnings("deprecation") private void removeHanging() - { - int miny = this.miny; - if(miny < 1) miny = 1; - - for(int x = 1; x < snapshots.length - 1; x++) - { - for(int z = 1; z < snapshots[0][0].length - 1; z++) - { - for(int y = miny; y < snapshots[0].length - 1; y++) - { - BlockSnapshot block = snapshots[x][y][z]; - BlockSnapshot underBlock = snapshots[x][y - 1][z]; - - if(underBlock.typeId == Material.AIR || underBlock.typeId == Material.WATER || Tag.LEAVES.isTagged(underBlock.typeId)) - { - if(this.notAllowedToHang.contains(block.typeId)) - { - block.typeId = Material.AIR; - } - } - } - } - } - } - - @SuppressWarnings("deprecation") + { + int miny = this.miny; + if (miny < 1) miny = 1; + + for (int x = 1; x < snapshots.length - 1; x++) + { + for (int z = 1; z < snapshots[0][0].length - 1; z++) + { + for (int y = miny; y < snapshots[0].length - 1; y++) + { + BlockSnapshot block = snapshots[x][y][z]; + BlockSnapshot underBlock = snapshots[x][y - 1][z]; + + if (underBlock.typeId == Material.AIR || underBlock.typeId == Material.WATER || Tag.LEAVES.isTagged(underBlock.typeId)) + { + if (this.notAllowedToHang.contains(block.typeId)) + { + block.typeId = Material.AIR; + } + } + } + } + } + } + + @SuppressWarnings("deprecation") private void removeWallsAndTowers() - { - Material [] excludedBlocksArray = new Material [] - { - Material.CACTUS, - Material.GRASS, - Material.RED_MUSHROOM, - Material.BROWN_MUSHROOM, - Material.DEAD_BUSH, - Material.DANDELION, - Material.POPPY, - Material.ALLIUM, - Material.BLUE_ORCHID, - Material.AZURE_BLUET, - Material.RED_TULIP, - Material.ORANGE_TULIP, - Material.WHITE_TULIP, - Material.PINK_TULIP, - Material.OXEYE_DAISY, - Material.SUGAR_CANE, - Material.VINE, - Material.PUMPKIN, - Material.LILY_PAD - }; - - ArrayList excludedBlocks = new ArrayList(); - for(int i = 0; i < excludedBlocksArray.length; i++) excludedBlocks.add(excludedBlocksArray[i]); - - excludedBlocks.addAll(Tag.SAPLINGS.getValues()); - excludedBlocks.addAll(Tag.LEAVES.getValues()); - - boolean changed; - do - { - changed = false; - for(int x = 1; x < snapshots.length - 1; x++) - { - for(int z = 1; z < snapshots[0][0].length - 1; z++) - { - int thisy = this.highestY(x, z, false); - if(excludedBlocks.contains(this.snapshots[x][thisy][z].typeId)) continue; - - int righty = this.highestY(x + 1, z, false); - int lefty = this.highestY(x - 1, z, false); - while(lefty < thisy && righty < thisy) - { - this.snapshots[x][thisy--][z].typeId = Material.AIR; - changed = true; - } - - int upy = this.highestY(x, z + 1, false); - int downy = this.highestY(x, z - 1, false); - while(upy < thisy && downy < thisy) - { - this.snapshots[x][thisy--][z].typeId = Material.AIR; - changed = true; - } - } - } - }while(changed); - } - - @SuppressWarnings("deprecation") + { + Material[] excludedBlocksArray = new Material[] + { + Material.CACTUS, + Material.GRASS, + Material.RED_MUSHROOM, + Material.BROWN_MUSHROOM, + Material.DEAD_BUSH, + Material.DANDELION, + Material.POPPY, + Material.ALLIUM, + Material.BLUE_ORCHID, + Material.AZURE_BLUET, + Material.RED_TULIP, + Material.ORANGE_TULIP, + Material.WHITE_TULIP, + Material.PINK_TULIP, + Material.OXEYE_DAISY, + Material.SUGAR_CANE, + Material.VINE, + Material.PUMPKIN, + Material.LILY_PAD + }; + + ArrayList excludedBlocks = new ArrayList(); + for (int i = 0; i < excludedBlocksArray.length; i++) excludedBlocks.add(excludedBlocksArray[i]); + + excludedBlocks.addAll(Tag.SAPLINGS.getValues()); + excludedBlocks.addAll(Tag.LEAVES.getValues()); + + boolean changed; + do + { + changed = false; + for (int x = 1; x < snapshots.length - 1; x++) + { + for (int z = 1; z < snapshots[0][0].length - 1; z++) + { + int thisy = this.highestY(x, z, false); + if (excludedBlocks.contains(this.snapshots[x][thisy][z].typeId)) continue; + + int righty = this.highestY(x + 1, z, false); + int lefty = this.highestY(x - 1, z, false); + while (lefty < thisy && righty < thisy) + { + this.snapshots[x][thisy--][z].typeId = Material.AIR; + changed = true; + } + + int upy = this.highestY(x, z + 1, false); + int downy = this.highestY(x, z - 1, false); + while (upy < thisy && downy < thisy) + { + this.snapshots[x][thisy--][z].typeId = Material.AIR; + changed = true; + } + } + } + } while (changed); + } + + @SuppressWarnings("deprecation") private void coverSurfaceStone() - { - for(int x = 1; x < snapshots.length - 1; x++) - { - for(int z = 1; z < snapshots[0][0].length - 1; z++) - { - int y = this.highestY(x, z, true); - BlockSnapshot block = snapshots[x][y][z]; - - if(block.typeId == Material.STONE || block.typeId == Material.GRAVEL || block.typeId == Material.FARMLAND || block.typeId == Material.DIRT || block.typeId == Material.SANDSTONE) - { - if(this.biome == Biome.DESERT || this.biome == Biome.DESERT_HILLS || this.biome == Biome.BEACH) - { - this.snapshots[x][y][z].typeId = Material.SAND; - } - else - { - this.snapshots[x][y][z].typeId = Material.GRASS_BLOCK; - } - } - } - } - } - - @SuppressWarnings("deprecation") + { + for (int x = 1; x < snapshots.length - 1; x++) + { + for (int z = 1; z < snapshots[0][0].length - 1; z++) + { + int y = this.highestY(x, z, true); + BlockSnapshot block = snapshots[x][y][z]; + + if (block.typeId == Material.STONE || block.typeId == Material.GRAVEL || block.typeId == Material.FARMLAND || block.typeId == Material.DIRT || block.typeId == Material.SANDSTONE) + { + if (this.biome == Biome.DESERT || this.biome == Biome.DESERT_HILLS || this.biome == Biome.BEACH) + { + this.snapshots[x][y][z].typeId = Material.SAND; + } else + { + this.snapshots[x][y][z].typeId = Material.GRASS_BLOCK; + } + } + } + } + } + + @SuppressWarnings("deprecation") private void fillHolesAndTrenches() - { - ArrayList fillableBlocks = new ArrayList(); - fillableBlocks.add(Material.AIR); - fillableBlocks.add(Material.WATER); - fillableBlocks.add(Material.LAVA); - fillableBlocks.add(Material.GRASS); - - ArrayList notSuitableForFillBlocks = new ArrayList(); - notSuitableForFillBlocks.add(Material.GRASS); - notSuitableForFillBlocks.add(Material.CACTUS); - notSuitableForFillBlocks.add(Material.WATER); - notSuitableForFillBlocks.add(Material.LAVA); - notSuitableForFillBlocks.addAll(Tag.LOGS.getValues()); - - boolean changed; - do - { - changed = false; - for(int x = 1; x < snapshots.length - 1; x++) - { - for(int z = 1; z < snapshots[0][0].length - 1; z++) - { - for(int y = 0; y < snapshots[0].length - 1; y++) - { - BlockSnapshot block = this.snapshots[x][y][z]; - if(!fillableBlocks.contains(block.typeId)) continue; - - BlockSnapshot leftBlock = this.snapshots[x + 1][y][z]; - BlockSnapshot rightBlock = this.snapshots[x - 1][y][z]; - - if(!fillableBlocks.contains(leftBlock.typeId) && !fillableBlocks.contains(rightBlock.typeId)) - { - if(!notSuitableForFillBlocks.contains(rightBlock.typeId)) - { - block.typeId = rightBlock.typeId; - changed = true; - } - } - - BlockSnapshot upBlock = this.snapshots[x][y][z + 1]; - BlockSnapshot downBlock = this.snapshots[x][y][z - 1]; - - if(!fillableBlocks.contains(upBlock.typeId) && !fillableBlocks.contains(downBlock.typeId)) - { - if(!notSuitableForFillBlocks.contains(downBlock.typeId)) - { - block.typeId = downBlock.typeId; - changed = true; - } - } - } - } - } - }while(changed); - } - - @SuppressWarnings("deprecation") + { + ArrayList fillableBlocks = new ArrayList(); + fillableBlocks.add(Material.AIR); + fillableBlocks.add(Material.WATER); + fillableBlocks.add(Material.LAVA); + fillableBlocks.add(Material.GRASS); + + ArrayList notSuitableForFillBlocks = new ArrayList(); + notSuitableForFillBlocks.add(Material.GRASS); + notSuitableForFillBlocks.add(Material.CACTUS); + notSuitableForFillBlocks.add(Material.WATER); + notSuitableForFillBlocks.add(Material.LAVA); + notSuitableForFillBlocks.addAll(Tag.LOGS.getValues()); + + boolean changed; + do + { + changed = false; + for (int x = 1; x < snapshots.length - 1; x++) + { + for (int z = 1; z < snapshots[0][0].length - 1; z++) + { + for (int y = 0; y < snapshots[0].length - 1; y++) + { + BlockSnapshot block = this.snapshots[x][y][z]; + if (!fillableBlocks.contains(block.typeId)) continue; + + BlockSnapshot leftBlock = this.snapshots[x + 1][y][z]; + BlockSnapshot rightBlock = this.snapshots[x - 1][y][z]; + + if (!fillableBlocks.contains(leftBlock.typeId) && !fillableBlocks.contains(rightBlock.typeId)) + { + if (!notSuitableForFillBlocks.contains(rightBlock.typeId)) + { + block.typeId = rightBlock.typeId; + changed = true; + } + } + + BlockSnapshot upBlock = this.snapshots[x][y][z + 1]; + BlockSnapshot downBlock = this.snapshots[x][y][z - 1]; + + if (!fillableBlocks.contains(upBlock.typeId) && !fillableBlocks.contains(downBlock.typeId)) + { + if (!notSuitableForFillBlocks.contains(downBlock.typeId)) + { + block.typeId = downBlock.typeId; + changed = true; + } + } + } + } + } + } while (changed); + } + + @SuppressWarnings("deprecation") private void fixWater() - { - int miny = this.miny; - if(miny < 1) miny = 1; - - boolean changed; - - //remove hanging water or lava - for(int x = 1; x < snapshots.length - 1; x++) - { - for(int z = 1; z < snapshots[0][0].length - 1; z++) - { - for(int y = miny; y < snapshots[0].length - 1; y++) - { - BlockSnapshot block = this.snapshots[x][y][z]; - BlockSnapshot underBlock = this.snapshots[x][y--][z]; - if(block.typeId == Material.WATER || block.typeId == Material.LAVA) - { - // check if block below is air or is a non-source fluid block (level 1-7 = flowing, 8 = falling) - if(underBlock.typeId == Material.AIR || (underBlock.typeId == Material.WATER && (((Levelled) underBlock.data).getLevel() != 0))) - { - block.typeId = Material.AIR; - } - } - } - } - } - - //fill water depressions - do - { - changed = false; - for(int y = Math.max(this.seaLevel - 10, 0); y <= this.seaLevel; y++) - { - for(int x = 1; x < snapshots.length - 1; x++) - { - for(int z = 1; z < snapshots[0][0].length - 1; z++) - { - BlockSnapshot block = snapshots[x][y][z]; - - //only consider air blocks and flowing water blocks for upgrade to water source blocks - if(block.typeId == Material.AIR || (block.typeId == Material.WATER && ((Levelled) block.data).getLevel() != 0)) - { - BlockSnapshot leftBlock = this.snapshots[x + 1][y][z]; - BlockSnapshot rightBlock = this.snapshots[x - 1][y][z]; - BlockSnapshot upBlock = this.snapshots[x][y][z + 1]; - BlockSnapshot downBlock = this.snapshots[x][y][z - 1]; - BlockSnapshot underBlock = this.snapshots[x][y - 1][z]; - - //block underneath MUST be source water - if(!(underBlock.typeId == Material.WATER && ((Levelled) underBlock.data).getLevel() == 0)) continue; - - //count adjacent source water blocks - byte adjacentSourceWaterCount = 0; - if(leftBlock.typeId == Material.WATER && ((Levelled) leftBlock.data).getLevel() == 0) - { - adjacentSourceWaterCount++; - } - if(rightBlock.typeId == Material.WATER && ((Levelled) rightBlock.data).getLevel() == 0) - { - adjacentSourceWaterCount++; - } - if(upBlock.typeId == Material.WATER && ((Levelled) upBlock.data).getLevel() == 0) - { - adjacentSourceWaterCount++; - } - if(downBlock.typeId == Material.WATER && ((Levelled) downBlock.data).getLevel() == 0) - { - adjacentSourceWaterCount++; - } - - //at least two adjacent blocks must be source water - if(adjacentSourceWaterCount >= 2) - { - block.typeId = Material.WATER; - ((Levelled) downBlock.data).setLevel(0); - changed = true; - } - } - } - } - } - }while(changed); - } - - @SuppressWarnings("deprecation") + { + int miny = this.miny; + if (miny < 1) miny = 1; + + boolean changed; + + //remove hanging water or lava + for (int x = 1; x < snapshots.length - 1; x++) + { + for (int z = 1; z < snapshots[0][0].length - 1; z++) + { + for (int y = miny; y < snapshots[0].length - 1; y++) + { + BlockSnapshot block = this.snapshots[x][y][z]; + BlockSnapshot underBlock = this.snapshots[x][y--][z]; + if (block.typeId == Material.WATER || block.typeId == Material.LAVA) + { + // check if block below is air or is a non-source fluid block (level 1-7 = flowing, 8 = falling) + if (underBlock.typeId == Material.AIR || (underBlock.typeId == Material.WATER && (((Levelled) underBlock.data).getLevel() != 0))) + { + block.typeId = Material.AIR; + } + } + } + } + } + + //fill water depressions + do + { + changed = false; + for (int y = Math.max(this.seaLevel - 10, 0); y <= this.seaLevel; y++) + { + for (int x = 1; x < snapshots.length - 1; x++) + { + for (int z = 1; z < snapshots[0][0].length - 1; z++) + { + BlockSnapshot block = snapshots[x][y][z]; + + //only consider air blocks and flowing water blocks for upgrade to water source blocks + if (block.typeId == Material.AIR || (block.typeId == Material.WATER && ((Levelled) block.data).getLevel() != 0)) + { + BlockSnapshot leftBlock = this.snapshots[x + 1][y][z]; + BlockSnapshot rightBlock = this.snapshots[x - 1][y][z]; + BlockSnapshot upBlock = this.snapshots[x][y][z + 1]; + BlockSnapshot downBlock = this.snapshots[x][y][z - 1]; + BlockSnapshot underBlock = this.snapshots[x][y - 1][z]; + + //block underneath MUST be source water + if (!(underBlock.typeId == Material.WATER && ((Levelled) underBlock.data).getLevel() == 0)) + continue; + + //count adjacent source water blocks + byte adjacentSourceWaterCount = 0; + if (leftBlock.typeId == Material.WATER && ((Levelled) leftBlock.data).getLevel() == 0) + { + adjacentSourceWaterCount++; + } + if (rightBlock.typeId == Material.WATER && ((Levelled) rightBlock.data).getLevel() == 0) + { + adjacentSourceWaterCount++; + } + if (upBlock.typeId == Material.WATER && ((Levelled) upBlock.data).getLevel() == 0) + { + adjacentSourceWaterCount++; + } + if (downBlock.typeId == Material.WATER && ((Levelled) downBlock.data).getLevel() == 0) + { + adjacentSourceWaterCount++; + } + + //at least two adjacent blocks must be source water + if (adjacentSourceWaterCount >= 2) + { + block.typeId = Material.WATER; + ((Levelled) downBlock.data).setLevel(0); + changed = true; + } + } + } + } + } + } while (changed); + } + + @SuppressWarnings("deprecation") private void removeDumpedFluids() - { - if(this.seaLevel < 1) return; - - //remove any surface water or lava above sea level, presumed to be placed by players - //sometimes, this is naturally generated. but replacing it is very easy with a bucket, so overall this is a good plan - if(this.environment == Environment.NETHER) return; - for(int x = 1; x < snapshots.length - 1; x++) - { - for(int z = 1; z < snapshots[0][0].length - 1; z++) - { - for(int y = this.seaLevel - 1; y < snapshots[0].length - 1; y++) - { - BlockSnapshot block = snapshots[x][y][z]; - if(block.typeId == Material.WATER || block.typeId == Material.LAVA) - { - block.typeId = Material.AIR; - } - } - } - } - } - - @SuppressWarnings("deprecation") - private int highestY(int x, int z, boolean ignoreLeaves) - { - int y; - for(y = snapshots[0].length - 1; y > 0; y--) - { - BlockSnapshot block = this.snapshots[x][y][z]; - if(block.typeId != Material.AIR && - !(ignoreLeaves && block.typeId == Material.SNOW) && - !(ignoreLeaves && Tag.LEAVES.isTagged(block.typeId)) && - !(block.typeId == Material.WATER) && - !(block.typeId == Material.LAVA)) - { - return y; - } - } - - return y; - } - - @SuppressWarnings("deprecation") - static ArrayList getPlayerBlocks(Environment environment, Biome biome) - { - //NOTE on this list. why not make a list of natural blocks? - //answer: better to leave a few player blocks than to remove too many natural blocks. remember we're "restoring nature" - //a few extra player blocks can be manually removed, but it will be impossible to guess exactly which natural materials to use in manual repair of an overzealous block removal - ArrayList playerBlocks = new ArrayList(); - playerBlocks.add(Material.FIRE); - playerBlocks.add(Material.WHITE_BED); - playerBlocks.add(Material.ORANGE_BED); - playerBlocks.add(Material.MAGENTA_BED); - playerBlocks.add(Material.LIGHT_BLUE_BED); - playerBlocks.add(Material.YELLOW_BED); - playerBlocks.add(Material.LIME_BED); - playerBlocks.add(Material.PINK_BED); - playerBlocks.add(Material.GRAY_BED); - playerBlocks.add(Material.LIGHT_GRAY_BED); - playerBlocks.add(Material.CYAN_BED); - playerBlocks.add(Material.PURPLE_BED); - playerBlocks.add(Material.BLUE_BED); - playerBlocks.add(Material.BROWN_BED); - playerBlocks.add(Material.GREEN_BED); - playerBlocks.add(Material.RED_BED); - playerBlocks.add(Material.BLACK_BED); - playerBlocks.add(Material.OAK_PLANKS); - playerBlocks.add(Material.SPRUCE_PLANKS); - playerBlocks.add(Material.BIRCH_PLANKS); - playerBlocks.add(Material.JUNGLE_PLANKS); - playerBlocks.add(Material.ACACIA_PLANKS); - playerBlocks.add(Material.DARK_OAK_PLANKS); - playerBlocks.add(Material.BOOKSHELF); - playerBlocks.add(Material.BREWING_STAND); - playerBlocks.add(Material.BRICK); - playerBlocks.add(Material.COBBLESTONE); - playerBlocks.add(Material.GLASS); - playerBlocks.add(Material.LAPIS_BLOCK); - playerBlocks.add(Material.DISPENSER); - playerBlocks.add(Material.NOTE_BLOCK); - playerBlocks.add(Material.POWERED_RAIL); - playerBlocks.add(Material.DETECTOR_RAIL); - playerBlocks.add(Material.STICKY_PISTON); - playerBlocks.add(Material.PISTON); - playerBlocks.add(Material.PISTON_HEAD); - playerBlocks.add(Material.MOVING_PISTON); - playerBlocks.add(Material.WHITE_WOOL); - playerBlocks.add(Material.ORANGE_WOOL); - playerBlocks.add(Material.MAGENTA_WOOL); - playerBlocks.add(Material.LIGHT_BLUE_WOOL); - playerBlocks.add(Material.YELLOW_WOOL); - playerBlocks.add(Material.LIME_WOOL); - playerBlocks.add(Material.PINK_WOOL); - playerBlocks.add(Material.GRAY_WOOL); - playerBlocks.add(Material.LIGHT_GRAY_WOOL); - playerBlocks.add(Material.CYAN_WOOL); - playerBlocks.add(Material.PURPLE_WOOL); - playerBlocks.add(Material.BLUE_WOOL); - playerBlocks.add(Material.BROWN_WOOL); - playerBlocks.add(Material.GREEN_WOOL); - playerBlocks.add(Material.RED_WOOL); - playerBlocks.add(Material.BLACK_WOOL); - playerBlocks.add(Material.GOLD_BLOCK); - playerBlocks.add(Material.IRON_BLOCK); - playerBlocks.add(Material.OAK_SLAB); - playerBlocks.add(Material.SPRUCE_SLAB); - playerBlocks.add(Material.BIRCH_SLAB); - playerBlocks.add(Material.JUNGLE_SLAB); - playerBlocks.add(Material.ACACIA_SLAB); - playerBlocks.add(Material.DARK_OAK_SLAB); - playerBlocks.add(Material.STONE_SLAB); - playerBlocks.add(Material.SANDSTONE_SLAB); - playerBlocks.add(Material.PETRIFIED_OAK_SLAB); - playerBlocks.add(Material.COBBLESTONE_SLAB); - playerBlocks.add(Material.BRICK_SLAB); - playerBlocks.add(Material.STONE_BRICK_SLAB); - playerBlocks.add(Material.NETHER_BRICK_SLAB); - playerBlocks.add(Material.QUARTZ_SLAB); - playerBlocks.add(Material.RED_SANDSTONE_SLAB); - playerBlocks.add(Material.PURPUR_SLAB); - playerBlocks.add(Material.PRISMARINE_SLAB); - playerBlocks.add(Material.PRISMARINE_BRICK_SLAB); - playerBlocks.add(Material.DARK_PRISMARINE_SLAB); - playerBlocks.add(Material.WHEAT); - playerBlocks.add(Material.TNT); - playerBlocks.add(Material.MOSSY_COBBLESTONE); - playerBlocks.add(Material.TORCH); - playerBlocks.add(Material.FIRE); - playerBlocks.add(Material.OAK_STAIRS); - playerBlocks.add(Material.SPRUCE_STAIRS); - playerBlocks.add(Material.BIRCH_STAIRS); - playerBlocks.add(Material.JUNGLE_STAIRS); - playerBlocks.add(Material.ACACIA_STAIRS); - playerBlocks.add(Material.DARK_OAK_STAIRS); - playerBlocks.add(Material.CHEST); - playerBlocks.add(Material.REDSTONE_WIRE); - playerBlocks.add(Material.DIAMOND_BLOCK); - playerBlocks.add(Material.CRAFTING_TABLE); - playerBlocks.add(Material.FURNACE); - playerBlocks.add(Material.OAK_DOOR); - playerBlocks.add(Material.ACACIA_SIGN); - playerBlocks.addAll(Tag.SIGNS.getValues()); - playerBlocks.addAll(Tag.WALL_SIGNS.getValues()); - playerBlocks.add(Material.LADDER); - playerBlocks.add(Material.RAIL); - playerBlocks.add(Material.COBBLESTONE_STAIRS); - playerBlocks.add(Material.STONE_PRESSURE_PLATE); - playerBlocks.add(Material.LEVER); - playerBlocks.add(Material.IRON_DOOR); - playerBlocks.add(Material.OAK_PRESSURE_PLATE); - playerBlocks.add(Material.SPRUCE_PRESSURE_PLATE); - playerBlocks.add(Material.BIRCH_PRESSURE_PLATE); - playerBlocks.add(Material.JUNGLE_PRESSURE_PLATE); - playerBlocks.add(Material.ACACIA_PRESSURE_PLATE); - playerBlocks.add(Material.DARK_OAK_PRESSURE_PLATE); - playerBlocks.add(Material.REDSTONE_TORCH); - playerBlocks.add(Material.STONE_BUTTON); - playerBlocks.add(Material.SNOW_BLOCK); - playerBlocks.add(Material.JUKEBOX); - playerBlocks.add(Material.OAK_FENCE); - playerBlocks.add(Material.SPRUCE_FENCE); - playerBlocks.add(Material.BIRCH_FENCE); - playerBlocks.add(Material.JUNGLE_FENCE); - playerBlocks.add(Material.ACACIA_FENCE); - playerBlocks.add(Material.DARK_OAK_FENCE); - playerBlocks.add(Material.NETHER_PORTAL); - playerBlocks.add(Material.JACK_O_LANTERN); - playerBlocks.add(Material.CAKE); - playerBlocks.add(Material.REPEATER); - playerBlocks.add(Material.OAK_TRAPDOOR); - playerBlocks.add(Material.SPRUCE_TRAPDOOR); - playerBlocks.add(Material.BIRCH_TRAPDOOR); - playerBlocks.add(Material.JUNGLE_TRAPDOOR); - playerBlocks.add(Material.ACACIA_TRAPDOOR); - playerBlocks.add(Material.DARK_OAK_TRAPDOOR); - playerBlocks.add(Material.STONE_BRICKS); - playerBlocks.add(Material.MOSSY_STONE_BRICKS); - playerBlocks.add(Material.CRACKED_STONE_BRICKS); - playerBlocks.add(Material.CHISELED_STONE_BRICKS); - playerBlocks.add(Material.MUSHROOM_STEM); - playerBlocks.add(Material.RED_MUSHROOM_BLOCK); - playerBlocks.add(Material.BROWN_MUSHROOM_BLOCK); - playerBlocks.add(Material.IRON_BARS); - playerBlocks.add(Material.GLASS_PANE); - playerBlocks.add(Material.MELON_STEM); - playerBlocks.add(Material.OAK_FENCE_GATE); - playerBlocks.add(Material.SPRUCE_FENCE_GATE); - playerBlocks.add(Material.BIRCH_FENCE_GATE); - playerBlocks.add(Material.JUNGLE_FENCE_GATE); - playerBlocks.add(Material.ACACIA_FENCE_GATE); - playerBlocks.add(Material.DARK_OAK_FENCE_GATE); - playerBlocks.add(Material.BRICK_STAIRS); - playerBlocks.add(Material.ENCHANTING_TABLE); - playerBlocks.add(Material.BREWING_STAND); - playerBlocks.add(Material.CAULDRON); - playerBlocks.add(Material.COBWEB); - playerBlocks.add(Material.SPONGE); - playerBlocks.add(Material.GRAVEL); - playerBlocks.add(Material.EMERALD_BLOCK); - playerBlocks.add(Material.SANDSTONE); - playerBlocks.add(Material.ENDER_CHEST); - playerBlocks.add(Material.SANDSTONE_STAIRS); - playerBlocks.add(Material.COMMAND_BLOCK); - playerBlocks.add(Material.REPEATING_COMMAND_BLOCK); - playerBlocks.add(Material.CHAIN_COMMAND_BLOCK); - playerBlocks.add(Material.BEACON); - playerBlocks.add(Material.COBBLESTONE_WALL); - playerBlocks.add(Material.MOSSY_COBBLESTONE_WALL); - playerBlocks.add(Material.FLOWER_POT); - playerBlocks.add(Material.CARROT); - playerBlocks.add(Material.POTATO); - playerBlocks.add(Material.OAK_BUTTON); - playerBlocks.add(Material.SPRUCE_BUTTON); - playerBlocks.add(Material.BIRCH_BUTTON); - playerBlocks.add(Material.JUNGLE_BUTTON); - playerBlocks.add(Material.ACACIA_BUTTON); - playerBlocks.add(Material.DARK_OAK_BUTTON); - playerBlocks.add(Material.SKELETON_SKULL); - playerBlocks.add(Material.WITHER_SKELETON_SKULL); - playerBlocks.add(Material.CREEPER_HEAD); - playerBlocks.add(Material.ZOMBIE_HEAD); - playerBlocks.add(Material.PLAYER_HEAD); - playerBlocks.add(Material.DRAGON_HEAD); - playerBlocks.add(Material.ANVIL); - playerBlocks.add(Material.SPONGE); - playerBlocks.add(Material.WHITE_STAINED_GLASS); - playerBlocks.add(Material.ORANGE_STAINED_GLASS); - playerBlocks.add(Material.MAGENTA_STAINED_GLASS); - playerBlocks.add(Material.LIGHT_BLUE_STAINED_GLASS); - playerBlocks.add(Material.YELLOW_STAINED_GLASS); - playerBlocks.add(Material.LIME_STAINED_GLASS); - playerBlocks.add(Material.PINK_STAINED_GLASS); - playerBlocks.add(Material.GRAY_STAINED_GLASS); - playerBlocks.add(Material.LIGHT_GRAY_STAINED_GLASS); - playerBlocks.add(Material.CYAN_STAINED_GLASS); - playerBlocks.add(Material.PURPLE_STAINED_GLASS); - playerBlocks.add(Material.BLUE_STAINED_GLASS); - playerBlocks.add(Material.BROWN_STAINED_GLASS); - playerBlocks.add(Material.GREEN_STAINED_GLASS); - playerBlocks.add(Material.RED_STAINED_GLASS); - playerBlocks.add(Material.BLACK_STAINED_GLASS); - playerBlocks.add(Material.WHITE_STAINED_GLASS_PANE); - playerBlocks.add(Material.ORANGE_STAINED_GLASS_PANE); - playerBlocks.add(Material.MAGENTA_STAINED_GLASS_PANE); - playerBlocks.add(Material.LIGHT_BLUE_STAINED_GLASS_PANE); - playerBlocks.add(Material.YELLOW_STAINED_GLASS_PANE); - playerBlocks.add(Material.LIME_STAINED_GLASS_PANE); - playerBlocks.add(Material.PINK_STAINED_GLASS_PANE); - playerBlocks.add(Material.GRAY_STAINED_GLASS_PANE); - playerBlocks.add(Material.LIGHT_GRAY_STAINED_GLASS_PANE); - playerBlocks.add(Material.CYAN_STAINED_GLASS_PANE); - playerBlocks.add(Material.PURPLE_STAINED_GLASS_PANE); - playerBlocks.add(Material.BLUE_STAINED_GLASS_PANE); - playerBlocks.add(Material.BROWN_STAINED_GLASS_PANE); - playerBlocks.add(Material.GREEN_STAINED_GLASS_PANE); - playerBlocks.add(Material.RED_STAINED_GLASS_PANE); - playerBlocks.add(Material.BLACK_STAINED_GLASS_PANE); - playerBlocks.add(Material.WHITE_BANNER); - playerBlocks.add(Material.ORANGE_BANNER); - playerBlocks.add(Material.MAGENTA_BANNER); - playerBlocks.add(Material.LIGHT_BLUE_BANNER); - playerBlocks.add(Material.YELLOW_BANNER); - playerBlocks.add(Material.LIME_BANNER); - playerBlocks.add(Material.PINK_BANNER); - playerBlocks.add(Material.GRAY_BANNER); - playerBlocks.add(Material.LIGHT_GRAY_BANNER); - playerBlocks.add(Material.CYAN_BANNER); - playerBlocks.add(Material.PURPLE_BANNER); - playerBlocks.add(Material.BLUE_BANNER); - playerBlocks.add(Material.BROWN_BANNER); - playerBlocks.add(Material.GREEN_BANNER); - playerBlocks.add(Material.RED_BANNER); - playerBlocks.add(Material.BLACK_BANNER); - playerBlocks.add(Material.TRAPPED_CHEST); - playerBlocks.add(Material.LIGHT_WEIGHTED_PRESSURE_PLATE); - playerBlocks.add(Material.HEAVY_WEIGHTED_PRESSURE_PLATE); - playerBlocks.add(Material.COMPARATOR); - playerBlocks.add(Material.DAYLIGHT_DETECTOR); - playerBlocks.add(Material.REDSTONE_BLOCK); - playerBlocks.add(Material.HOPPER); - playerBlocks.add(Material.QUARTZ_BLOCK); - playerBlocks.add(Material.QUARTZ_STAIRS); - playerBlocks.add(Material.DROPPER); - playerBlocks.add(Material.SLIME_BLOCK); - playerBlocks.add(Material.IRON_TRAPDOOR); - playerBlocks.add(Material.PRISMARINE); - playerBlocks.add(Material.HAY_BLOCK); - playerBlocks.add(Material.WHITE_CARPET); - playerBlocks.add(Material.ORANGE_CARPET); - playerBlocks.add(Material.MAGENTA_CARPET); - playerBlocks.add(Material.LIGHT_BLUE_CARPET); - playerBlocks.add(Material.YELLOW_CARPET); - playerBlocks.add(Material.LIME_CARPET); - playerBlocks.add(Material.PINK_CARPET); - playerBlocks.add(Material.GRAY_CARPET); - playerBlocks.add(Material.LIGHT_GRAY_CARPET); - playerBlocks.add(Material.CYAN_CARPET); - playerBlocks.add(Material.PURPLE_CARPET); - playerBlocks.add(Material.BLUE_CARPET); - playerBlocks.add(Material.BROWN_CARPET); - playerBlocks.add(Material.GREEN_CARPET); - playerBlocks.add(Material.RED_CARPET); - playerBlocks.add(Material.BLACK_CARPET); - playerBlocks.add(Material.SEA_LANTERN); - playerBlocks.add(Material.RED_SANDSTONE_STAIRS); - playerBlocks.add(Material.ACACIA_FENCE); - playerBlocks.add(Material.ACACIA_FENCE_GATE); - playerBlocks.add(Material.BIRCH_FENCE); - playerBlocks.add(Material.BIRCH_FENCE_GATE); - playerBlocks.add(Material.DARK_OAK_FENCE); - playerBlocks.add(Material.DARK_OAK_FENCE_GATE); - playerBlocks.add(Material.JUNGLE_FENCE); - playerBlocks.add(Material.JUNGLE_FENCE_GATE); - playerBlocks.add(Material.SPRUCE_FENCE); - playerBlocks.add(Material.SPRUCE_FENCE_GATE); - playerBlocks.add(Material.ACACIA_DOOR); - playerBlocks.add(Material.SPRUCE_DOOR); - playerBlocks.add(Material.DARK_OAK_DOOR); - playerBlocks.add(Material.JUNGLE_DOOR); - playerBlocks.add(Material.BIRCH_DOOR); - playerBlocks.add(Material.COAL_BLOCK); - playerBlocks.add(Material.REDSTONE_LAMP); - playerBlocks.add(Material.PURPUR_BLOCK); - playerBlocks.add(Material.PURPUR_SLAB); - playerBlocks.add(Material.PURPUR_PILLAR); - playerBlocks.add(Material.PURPUR_STAIRS); - playerBlocks.add(Material.NETHER_WART_BLOCK); - playerBlocks.add(Material.RED_NETHER_BRICKS); - playerBlocks.add(Material.BONE_BLOCK); - - //these are unnatural in the standard world, but not in the nether - if(environment != Environment.NETHER) - { - playerBlocks.add(Material.NETHERRACK); - playerBlocks.add(Material.SOUL_SAND); - playerBlocks.add(Material.GLOWSTONE); - playerBlocks.add(Material.NETHER_BRICK); - playerBlocks.add(Material.NETHER_BRICK_FENCE); - playerBlocks.add(Material.NETHER_BRICK_STAIRS); - playerBlocks.add(Material.MAGMA_BLOCK); - } - - //these are unnatural in the standard and nether worlds, but not in the end - if(environment != Environment.THE_END) - { - playerBlocks.add(Material.OBSIDIAN); - playerBlocks.add(Material.END_STONE); - playerBlocks.add(Material.END_PORTAL_FRAME); - playerBlocks.add(Material.CHORUS_PLANT); - playerBlocks.add(Material.CHORUS_FLOWER); - } - - //these are unnatural in sandy biomes, but not elsewhere - if(biome == Biome.DESERT || biome == Biome.DESERT_HILLS || biome == Biome.BEACH || environment != Environment.NORMAL) - { - playerBlocks.add(Material.OAK_LEAVES); - playerBlocks.add(Material.SPRUCE_LEAVES); - playerBlocks.add(Material.BIRCH_LEAVES); - playerBlocks.add(Material.JUNGLE_LEAVES); - playerBlocks.add(Material.ACACIA_LEAVES); - playerBlocks.add(Material.DARK_OAK_LEAVES); - playerBlocks.add(Material.OAK_LOG); - playerBlocks.add(Material.SPRUCE_LOG); - playerBlocks.add(Material.BIRCH_LOG); - playerBlocks.add(Material.JUNGLE_LOG); - playerBlocks.add(Material.ACACIA_LOG); - playerBlocks.add(Material.DARK_OAK_LOG); - } - - return playerBlocks; - } + { + if (this.seaLevel < 1) return; + + //remove any surface water or lava above sea level, presumed to be placed by players + //sometimes, this is naturally generated. but replacing it is very easy with a bucket, so overall this is a good plan + if (this.environment == Environment.NETHER) return; + for (int x = 1; x < snapshots.length - 1; x++) + { + for (int z = 1; z < snapshots[0][0].length - 1; z++) + { + for (int y = this.seaLevel - 1; y < snapshots[0].length - 1; y++) + { + BlockSnapshot block = snapshots[x][y][z]; + if (block.typeId == Material.WATER || block.typeId == Material.LAVA) + { + block.typeId = Material.AIR; + } + } + } + } + } + + @SuppressWarnings("deprecation") + private int highestY(int x, int z, boolean ignoreLeaves) + { + int y; + for (y = snapshots[0].length - 1; y > 0; y--) + { + BlockSnapshot block = this.snapshots[x][y][z]; + if (block.typeId != Material.AIR && + !(ignoreLeaves && block.typeId == Material.SNOW) && + !(ignoreLeaves && Tag.LEAVES.isTagged(block.typeId)) && + !(block.typeId == Material.WATER) && + !(block.typeId == Material.LAVA)) + { + return y; + } + } + + return y; + } + + @SuppressWarnings("deprecation") + static ArrayList getPlayerBlocks(Environment environment, Biome biome) + { + //NOTE on this list. why not make a list of natural blocks? + //answer: better to leave a few player blocks than to remove too many natural blocks. remember we're "restoring nature" + //a few extra player blocks can be manually removed, but it will be impossible to guess exactly which natural materials to use in manual repair of an overzealous block removal + ArrayList playerBlocks = new ArrayList(); + playerBlocks.add(Material.FIRE); + playerBlocks.add(Material.WHITE_BED); + playerBlocks.add(Material.ORANGE_BED); + playerBlocks.add(Material.MAGENTA_BED); + playerBlocks.add(Material.LIGHT_BLUE_BED); + playerBlocks.add(Material.YELLOW_BED); + playerBlocks.add(Material.LIME_BED); + playerBlocks.add(Material.PINK_BED); + playerBlocks.add(Material.GRAY_BED); + playerBlocks.add(Material.LIGHT_GRAY_BED); + playerBlocks.add(Material.CYAN_BED); + playerBlocks.add(Material.PURPLE_BED); + playerBlocks.add(Material.BLUE_BED); + playerBlocks.add(Material.BROWN_BED); + playerBlocks.add(Material.GREEN_BED); + playerBlocks.add(Material.RED_BED); + playerBlocks.add(Material.BLACK_BED); + playerBlocks.add(Material.OAK_PLANKS); + playerBlocks.add(Material.SPRUCE_PLANKS); + playerBlocks.add(Material.BIRCH_PLANKS); + playerBlocks.add(Material.JUNGLE_PLANKS); + playerBlocks.add(Material.ACACIA_PLANKS); + playerBlocks.add(Material.DARK_OAK_PLANKS); + playerBlocks.add(Material.BOOKSHELF); + playerBlocks.add(Material.BREWING_STAND); + playerBlocks.add(Material.BRICK); + playerBlocks.add(Material.COBBLESTONE); + playerBlocks.add(Material.GLASS); + playerBlocks.add(Material.LAPIS_BLOCK); + playerBlocks.add(Material.DISPENSER); + playerBlocks.add(Material.NOTE_BLOCK); + playerBlocks.add(Material.POWERED_RAIL); + playerBlocks.add(Material.DETECTOR_RAIL); + playerBlocks.add(Material.STICKY_PISTON); + playerBlocks.add(Material.PISTON); + playerBlocks.add(Material.PISTON_HEAD); + playerBlocks.add(Material.MOVING_PISTON); + playerBlocks.add(Material.WHITE_WOOL); + playerBlocks.add(Material.ORANGE_WOOL); + playerBlocks.add(Material.MAGENTA_WOOL); + playerBlocks.add(Material.LIGHT_BLUE_WOOL); + playerBlocks.add(Material.YELLOW_WOOL); + playerBlocks.add(Material.LIME_WOOL); + playerBlocks.add(Material.PINK_WOOL); + playerBlocks.add(Material.GRAY_WOOL); + playerBlocks.add(Material.LIGHT_GRAY_WOOL); + playerBlocks.add(Material.CYAN_WOOL); + playerBlocks.add(Material.PURPLE_WOOL); + playerBlocks.add(Material.BLUE_WOOL); + playerBlocks.add(Material.BROWN_WOOL); + playerBlocks.add(Material.GREEN_WOOL); + playerBlocks.add(Material.RED_WOOL); + playerBlocks.add(Material.BLACK_WOOL); + playerBlocks.add(Material.GOLD_BLOCK); + playerBlocks.add(Material.IRON_BLOCK); + playerBlocks.add(Material.OAK_SLAB); + playerBlocks.add(Material.SPRUCE_SLAB); + playerBlocks.add(Material.BIRCH_SLAB); + playerBlocks.add(Material.JUNGLE_SLAB); + playerBlocks.add(Material.ACACIA_SLAB); + playerBlocks.add(Material.DARK_OAK_SLAB); + playerBlocks.add(Material.STONE_SLAB); + playerBlocks.add(Material.SANDSTONE_SLAB); + playerBlocks.add(Material.PETRIFIED_OAK_SLAB); + playerBlocks.add(Material.COBBLESTONE_SLAB); + playerBlocks.add(Material.BRICK_SLAB); + playerBlocks.add(Material.STONE_BRICK_SLAB); + playerBlocks.add(Material.NETHER_BRICK_SLAB); + playerBlocks.add(Material.QUARTZ_SLAB); + playerBlocks.add(Material.RED_SANDSTONE_SLAB); + playerBlocks.add(Material.PURPUR_SLAB); + playerBlocks.add(Material.PRISMARINE_SLAB); + playerBlocks.add(Material.PRISMARINE_BRICK_SLAB); + playerBlocks.add(Material.DARK_PRISMARINE_SLAB); + playerBlocks.add(Material.WHEAT); + playerBlocks.add(Material.TNT); + playerBlocks.add(Material.MOSSY_COBBLESTONE); + playerBlocks.add(Material.TORCH); + playerBlocks.add(Material.FIRE); + playerBlocks.add(Material.OAK_STAIRS); + playerBlocks.add(Material.SPRUCE_STAIRS); + playerBlocks.add(Material.BIRCH_STAIRS); + playerBlocks.add(Material.JUNGLE_STAIRS); + playerBlocks.add(Material.ACACIA_STAIRS); + playerBlocks.add(Material.DARK_OAK_STAIRS); + playerBlocks.add(Material.CHEST); + playerBlocks.add(Material.REDSTONE_WIRE); + playerBlocks.add(Material.DIAMOND_BLOCK); + playerBlocks.add(Material.CRAFTING_TABLE); + playerBlocks.add(Material.FURNACE); + playerBlocks.add(Material.OAK_DOOR); + playerBlocks.add(Material.ACACIA_SIGN); + playerBlocks.addAll(Tag.SIGNS.getValues()); + playerBlocks.addAll(Tag.WALL_SIGNS.getValues()); + playerBlocks.add(Material.LADDER); + playerBlocks.add(Material.RAIL); + playerBlocks.add(Material.COBBLESTONE_STAIRS); + playerBlocks.add(Material.STONE_PRESSURE_PLATE); + playerBlocks.add(Material.LEVER); + playerBlocks.add(Material.IRON_DOOR); + playerBlocks.add(Material.OAK_PRESSURE_PLATE); + playerBlocks.add(Material.SPRUCE_PRESSURE_PLATE); + playerBlocks.add(Material.BIRCH_PRESSURE_PLATE); + playerBlocks.add(Material.JUNGLE_PRESSURE_PLATE); + playerBlocks.add(Material.ACACIA_PRESSURE_PLATE); + playerBlocks.add(Material.DARK_OAK_PRESSURE_PLATE); + playerBlocks.add(Material.REDSTONE_TORCH); + playerBlocks.add(Material.STONE_BUTTON); + playerBlocks.add(Material.SNOW_BLOCK); + playerBlocks.add(Material.JUKEBOX); + playerBlocks.add(Material.OAK_FENCE); + playerBlocks.add(Material.SPRUCE_FENCE); + playerBlocks.add(Material.BIRCH_FENCE); + playerBlocks.add(Material.JUNGLE_FENCE); + playerBlocks.add(Material.ACACIA_FENCE); + playerBlocks.add(Material.DARK_OAK_FENCE); + playerBlocks.add(Material.NETHER_PORTAL); + playerBlocks.add(Material.JACK_O_LANTERN); + playerBlocks.add(Material.CAKE); + playerBlocks.add(Material.REPEATER); + playerBlocks.add(Material.OAK_TRAPDOOR); + playerBlocks.add(Material.SPRUCE_TRAPDOOR); + playerBlocks.add(Material.BIRCH_TRAPDOOR); + playerBlocks.add(Material.JUNGLE_TRAPDOOR); + playerBlocks.add(Material.ACACIA_TRAPDOOR); + playerBlocks.add(Material.DARK_OAK_TRAPDOOR); + playerBlocks.add(Material.STONE_BRICKS); + playerBlocks.add(Material.MOSSY_STONE_BRICKS); + playerBlocks.add(Material.CRACKED_STONE_BRICKS); + playerBlocks.add(Material.CHISELED_STONE_BRICKS); + playerBlocks.add(Material.MUSHROOM_STEM); + playerBlocks.add(Material.RED_MUSHROOM_BLOCK); + playerBlocks.add(Material.BROWN_MUSHROOM_BLOCK); + playerBlocks.add(Material.IRON_BARS); + playerBlocks.add(Material.GLASS_PANE); + playerBlocks.add(Material.MELON_STEM); + playerBlocks.add(Material.OAK_FENCE_GATE); + playerBlocks.add(Material.SPRUCE_FENCE_GATE); + playerBlocks.add(Material.BIRCH_FENCE_GATE); + playerBlocks.add(Material.JUNGLE_FENCE_GATE); + playerBlocks.add(Material.ACACIA_FENCE_GATE); + playerBlocks.add(Material.DARK_OAK_FENCE_GATE); + playerBlocks.add(Material.BRICK_STAIRS); + playerBlocks.add(Material.ENCHANTING_TABLE); + playerBlocks.add(Material.BREWING_STAND); + playerBlocks.add(Material.CAULDRON); + playerBlocks.add(Material.COBWEB); + playerBlocks.add(Material.SPONGE); + playerBlocks.add(Material.GRAVEL); + playerBlocks.add(Material.EMERALD_BLOCK); + playerBlocks.add(Material.SANDSTONE); + playerBlocks.add(Material.ENDER_CHEST); + playerBlocks.add(Material.SANDSTONE_STAIRS); + playerBlocks.add(Material.COMMAND_BLOCK); + playerBlocks.add(Material.REPEATING_COMMAND_BLOCK); + playerBlocks.add(Material.CHAIN_COMMAND_BLOCK); + playerBlocks.add(Material.BEACON); + playerBlocks.add(Material.COBBLESTONE_WALL); + playerBlocks.add(Material.MOSSY_COBBLESTONE_WALL); + playerBlocks.add(Material.FLOWER_POT); + playerBlocks.add(Material.CARROT); + playerBlocks.add(Material.POTATO); + playerBlocks.add(Material.OAK_BUTTON); + playerBlocks.add(Material.SPRUCE_BUTTON); + playerBlocks.add(Material.BIRCH_BUTTON); + playerBlocks.add(Material.JUNGLE_BUTTON); + playerBlocks.add(Material.ACACIA_BUTTON); + playerBlocks.add(Material.DARK_OAK_BUTTON); + playerBlocks.add(Material.SKELETON_SKULL); + playerBlocks.add(Material.WITHER_SKELETON_SKULL); + playerBlocks.add(Material.CREEPER_HEAD); + playerBlocks.add(Material.ZOMBIE_HEAD); + playerBlocks.add(Material.PLAYER_HEAD); + playerBlocks.add(Material.DRAGON_HEAD); + playerBlocks.add(Material.ANVIL); + playerBlocks.add(Material.SPONGE); + playerBlocks.add(Material.WHITE_STAINED_GLASS); + playerBlocks.add(Material.ORANGE_STAINED_GLASS); + playerBlocks.add(Material.MAGENTA_STAINED_GLASS); + playerBlocks.add(Material.LIGHT_BLUE_STAINED_GLASS); + playerBlocks.add(Material.YELLOW_STAINED_GLASS); + playerBlocks.add(Material.LIME_STAINED_GLASS); + playerBlocks.add(Material.PINK_STAINED_GLASS); + playerBlocks.add(Material.GRAY_STAINED_GLASS); + playerBlocks.add(Material.LIGHT_GRAY_STAINED_GLASS); + playerBlocks.add(Material.CYAN_STAINED_GLASS); + playerBlocks.add(Material.PURPLE_STAINED_GLASS); + playerBlocks.add(Material.BLUE_STAINED_GLASS); + playerBlocks.add(Material.BROWN_STAINED_GLASS); + playerBlocks.add(Material.GREEN_STAINED_GLASS); + playerBlocks.add(Material.RED_STAINED_GLASS); + playerBlocks.add(Material.BLACK_STAINED_GLASS); + playerBlocks.add(Material.WHITE_STAINED_GLASS_PANE); + playerBlocks.add(Material.ORANGE_STAINED_GLASS_PANE); + playerBlocks.add(Material.MAGENTA_STAINED_GLASS_PANE); + playerBlocks.add(Material.LIGHT_BLUE_STAINED_GLASS_PANE); + playerBlocks.add(Material.YELLOW_STAINED_GLASS_PANE); + playerBlocks.add(Material.LIME_STAINED_GLASS_PANE); + playerBlocks.add(Material.PINK_STAINED_GLASS_PANE); + playerBlocks.add(Material.GRAY_STAINED_GLASS_PANE); + playerBlocks.add(Material.LIGHT_GRAY_STAINED_GLASS_PANE); + playerBlocks.add(Material.CYAN_STAINED_GLASS_PANE); + playerBlocks.add(Material.PURPLE_STAINED_GLASS_PANE); + playerBlocks.add(Material.BLUE_STAINED_GLASS_PANE); + playerBlocks.add(Material.BROWN_STAINED_GLASS_PANE); + playerBlocks.add(Material.GREEN_STAINED_GLASS_PANE); + playerBlocks.add(Material.RED_STAINED_GLASS_PANE); + playerBlocks.add(Material.BLACK_STAINED_GLASS_PANE); + playerBlocks.add(Material.WHITE_BANNER); + playerBlocks.add(Material.ORANGE_BANNER); + playerBlocks.add(Material.MAGENTA_BANNER); + playerBlocks.add(Material.LIGHT_BLUE_BANNER); + playerBlocks.add(Material.YELLOW_BANNER); + playerBlocks.add(Material.LIME_BANNER); + playerBlocks.add(Material.PINK_BANNER); + playerBlocks.add(Material.GRAY_BANNER); + playerBlocks.add(Material.LIGHT_GRAY_BANNER); + playerBlocks.add(Material.CYAN_BANNER); + playerBlocks.add(Material.PURPLE_BANNER); + playerBlocks.add(Material.BLUE_BANNER); + playerBlocks.add(Material.BROWN_BANNER); + playerBlocks.add(Material.GREEN_BANNER); + playerBlocks.add(Material.RED_BANNER); + playerBlocks.add(Material.BLACK_BANNER); + playerBlocks.add(Material.TRAPPED_CHEST); + playerBlocks.add(Material.LIGHT_WEIGHTED_PRESSURE_PLATE); + playerBlocks.add(Material.HEAVY_WEIGHTED_PRESSURE_PLATE); + playerBlocks.add(Material.COMPARATOR); + playerBlocks.add(Material.DAYLIGHT_DETECTOR); + playerBlocks.add(Material.REDSTONE_BLOCK); + playerBlocks.add(Material.HOPPER); + playerBlocks.add(Material.QUARTZ_BLOCK); + playerBlocks.add(Material.QUARTZ_STAIRS); + playerBlocks.add(Material.DROPPER); + playerBlocks.add(Material.SLIME_BLOCK); + playerBlocks.add(Material.IRON_TRAPDOOR); + playerBlocks.add(Material.PRISMARINE); + playerBlocks.add(Material.HAY_BLOCK); + playerBlocks.add(Material.WHITE_CARPET); + playerBlocks.add(Material.ORANGE_CARPET); + playerBlocks.add(Material.MAGENTA_CARPET); + playerBlocks.add(Material.LIGHT_BLUE_CARPET); + playerBlocks.add(Material.YELLOW_CARPET); + playerBlocks.add(Material.LIME_CARPET); + playerBlocks.add(Material.PINK_CARPET); + playerBlocks.add(Material.GRAY_CARPET); + playerBlocks.add(Material.LIGHT_GRAY_CARPET); + playerBlocks.add(Material.CYAN_CARPET); + playerBlocks.add(Material.PURPLE_CARPET); + playerBlocks.add(Material.BLUE_CARPET); + playerBlocks.add(Material.BROWN_CARPET); + playerBlocks.add(Material.GREEN_CARPET); + playerBlocks.add(Material.RED_CARPET); + playerBlocks.add(Material.BLACK_CARPET); + playerBlocks.add(Material.SEA_LANTERN); + playerBlocks.add(Material.RED_SANDSTONE_STAIRS); + playerBlocks.add(Material.ACACIA_FENCE); + playerBlocks.add(Material.ACACIA_FENCE_GATE); + playerBlocks.add(Material.BIRCH_FENCE); + playerBlocks.add(Material.BIRCH_FENCE_GATE); + playerBlocks.add(Material.DARK_OAK_FENCE); + playerBlocks.add(Material.DARK_OAK_FENCE_GATE); + playerBlocks.add(Material.JUNGLE_FENCE); + playerBlocks.add(Material.JUNGLE_FENCE_GATE); + playerBlocks.add(Material.SPRUCE_FENCE); + playerBlocks.add(Material.SPRUCE_FENCE_GATE); + playerBlocks.add(Material.ACACIA_DOOR); + playerBlocks.add(Material.SPRUCE_DOOR); + playerBlocks.add(Material.DARK_OAK_DOOR); + playerBlocks.add(Material.JUNGLE_DOOR); + playerBlocks.add(Material.BIRCH_DOOR); + playerBlocks.add(Material.COAL_BLOCK); + playerBlocks.add(Material.REDSTONE_LAMP); + playerBlocks.add(Material.PURPUR_BLOCK); + playerBlocks.add(Material.PURPUR_SLAB); + playerBlocks.add(Material.PURPUR_PILLAR); + playerBlocks.add(Material.PURPUR_STAIRS); + playerBlocks.add(Material.NETHER_WART_BLOCK); + playerBlocks.add(Material.RED_NETHER_BRICKS); + playerBlocks.add(Material.BONE_BLOCK); + + //these are unnatural in the standard world, but not in the nether + if (environment != Environment.NETHER) + { + playerBlocks.add(Material.NETHERRACK); + playerBlocks.add(Material.SOUL_SAND); + playerBlocks.add(Material.GLOWSTONE); + playerBlocks.add(Material.NETHER_BRICK); + playerBlocks.add(Material.NETHER_BRICK_FENCE); + playerBlocks.add(Material.NETHER_BRICK_STAIRS); + playerBlocks.add(Material.MAGMA_BLOCK); + } + + //these are unnatural in the standard and nether worlds, but not in the end + if (environment != Environment.THE_END) + { + playerBlocks.add(Material.OBSIDIAN); + playerBlocks.add(Material.END_STONE); + playerBlocks.add(Material.END_PORTAL_FRAME); + playerBlocks.add(Material.CHORUS_PLANT); + playerBlocks.add(Material.CHORUS_FLOWER); + } + + //these are unnatural in sandy biomes, but not elsewhere + if (biome == Biome.DESERT || biome == Biome.DESERT_HILLS || biome == Biome.BEACH || environment != Environment.NORMAL) + { + playerBlocks.add(Material.OAK_LEAVES); + playerBlocks.add(Material.SPRUCE_LEAVES); + playerBlocks.add(Material.BIRCH_LEAVES); + playerBlocks.add(Material.JUNGLE_LEAVES); + playerBlocks.add(Material.ACACIA_LEAVES); + playerBlocks.add(Material.DARK_OAK_LEAVES); + playerBlocks.add(Material.OAK_LOG); + playerBlocks.add(Material.SPRUCE_LOG); + playerBlocks.add(Material.BIRCH_LOG); + playerBlocks.add(Material.JUNGLE_LOG); + playerBlocks.add(Material.ACACIA_LOG); + playerBlocks.add(Material.DARK_OAK_LOG); + } + + return playerBlocks; + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/SecureClaimTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/SecureClaimTask.java index 0bc7e94..e63199f 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/SecureClaimTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/SecureClaimTask.java @@ -15,44 +15,44 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - - package me.ryanhamshire.GriefPrevention; -import java.util.Collection; +package me.ryanhamshire.GriefPrevention; import org.bukkit.entity.Player; +import java.util.Collection; + //secures a claim after a siege looting window has closed -class SecureClaimTask implements Runnable +class SecureClaimTask implements Runnable { - private SiegeData siegeData; - - public SecureClaimTask(SiegeData siegeData) - { - this.siegeData = siegeData; - } - - @Override - public void run() - { - //for each claim involved in this siege - for(int i = 0; i < this.siegeData.claims.size(); i++) - { - //lock the doors - Claim claim = this.siegeData.claims.get(i); - claim.doorsOpen = false; - - //eject bad guys - @SuppressWarnings("unchecked") - Collection onlinePlayers = (Collection)GriefPrevention.instance.getServer().getOnlinePlayers(); - for(Player player : onlinePlayers) - { - if(claim.contains(player.getLocation(), false, false) && claim.allowAccess(player) != null) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeDoorsLockedEjection); - GriefPrevention.instance.ejectPlayer(player); - } - } - } - } + private SiegeData siegeData; + + public SecureClaimTask(SiegeData siegeData) + { + this.siegeData = siegeData; + } + + @Override + public void run() + { + //for each claim involved in this siege + for (int i = 0; i < this.siegeData.claims.size(); i++) + { + //lock the doors + Claim claim = this.siegeData.claims.get(i); + claim.doorsOpen = false; + + //eject bad guys + @SuppressWarnings("unchecked") + Collection onlinePlayers = (Collection) GriefPrevention.instance.getServer().getOnlinePlayers(); + for (Player player : onlinePlayers) + { + if (claim.contains(player.getLocation(), false, false) && claim.allowAccess(player) != null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeDoorsLockedEjection); + GriefPrevention.instance.ejectPlayer(player); + } + } + } + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/SendPlayerMessageTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/SendPlayerMessageTask.java index 4157efa..9979b9a 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/SendPlayerMessageTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/SendPlayerMessageTask.java @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - + package me.ryanhamshire.GriefPrevention; import org.bukkit.ChatColor; @@ -23,39 +23,39 @@ import org.bukkit.entity.Player; //sends a message to a player //used to send delayed messages, for example help text triggered by a player's chat -class SendPlayerMessageTask implements Runnable +class SendPlayerMessageTask implements Runnable { - private Player player; - private ChatColor color; - private String message; - - public SendPlayerMessageTask(Player player, ChatColor color, String message) - { - this.player = player; - this.color = color; - this.message = message; - } + private Player player; + private ChatColor color; + private String message; - @Override - public void run() - { - if(player == null) - { - GriefPrevention.AddLogEntry(color + message); - return; - } - - //if the player is dead, save it for after his respawn - if(this.player.isDead()) - { - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(this.player.getUniqueId()); - playerData.messageOnRespawn = this.color + this.message; - } - - //otherwise send it immediately - else - { - GriefPrevention.sendMessage(this.player, this.color, this.message); - } - } + public SendPlayerMessageTask(Player player, ChatColor color, String message) + { + this.player = player; + this.color = color; + this.message = message; + } + + @Override + public void run() + { + if (player == null) + { + GriefPrevention.AddLogEntry(color + message); + return; + } + + //if the player is dead, save it for after his respawn + if (this.player.isDead()) + { + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(this.player.getUniqueId()); + playerData.messageOnRespawn = this.color + this.message; + } + + //otherwise send it immediately + else + { + GriefPrevention.sendMessage(this.player, this.color, this.message); + } + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/ShovelMode.java b/src/main/java/me/ryanhamshire/GriefPrevention/ShovelMode.java index e14459d..1fbb5fe 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/ShovelMode.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/ShovelMode.java @@ -15,16 +15,16 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - - package me.ryanhamshire.GriefPrevention; + +package me.ryanhamshire.GriefPrevention; //enumeration for golden shovel modes -public enum ShovelMode +public enum ShovelMode { - Basic, - Admin, - Subdivide, - RestoreNature, - RestoreNatureAggressive, - RestoreNatureFill + Basic, + Admin, + Subdivide, + RestoreNature, + RestoreNatureAggressive, + RestoreNatureFill } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/SiegeCheckupTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/SiegeCheckupTask.java index 44ec1de..346e6ce 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/SiegeCheckupTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/SiegeCheckupTask.java @@ -22,89 +22,89 @@ import org.bukkit.entity.Player; //checks to see whether or not a siege should end based on the locations of the players //for example, defender escaped or attacker gave up and left -class SiegeCheckupTask implements Runnable +class SiegeCheckupTask implements Runnable { - private SiegeData siegeData; - - public SiegeCheckupTask(SiegeData siegeData) - { - this.siegeData = siegeData; - } - - @Override - public void run() - { - DataStore dataStore = GriefPrevention.instance.dataStore; - Player defender = this.siegeData.defender; - Player attacker = this.siegeData.attacker; - - //where is the defender? - Claim defenderClaim = dataStore.getClaimAt(defender.getLocation(), false, null); - - //if this is a new claim and he has some permission there, extend the siege to include it - if(defenderClaim != null) - { - String noAccessReason = defenderClaim.allowAccess(defender); - if(defenderClaim.canSiege(defender) && noAccessReason == null) - { - this.siegeData.claims.add(defenderClaim); - defenderClaim.siegeData = this.siegeData; - } - } - - //determine who's close enough to the siege area to be considered "still here" - boolean attackerRemains = this.playerRemains(attacker); - boolean defenderRemains = this.playerRemains(defender); - - //if they're both here, just plan to come check again later - if(attackerRemains && defenderRemains) - { - this.scheduleAnotherCheck(); - } - - //otherwise attacker wins if the defender runs away - else if(attackerRemains && !defenderRemains) - { - dataStore.endSiege(this.siegeData, attacker.getName(), defender.getName(), null); - } - - //or defender wins if the attacker leaves - else if(!attackerRemains && defenderRemains) - { - dataStore.endSiege(this.siegeData, defender.getName(), attacker.getName(), null); - } - - //if they both left, but are still close together, the battle continues (check again later) - else if(attacker.getWorld().equals(defender.getWorld()) && attacker.getLocation().distanceSquared(defender.getLocation()) < 2500) //50-block radius for chasing - { - this.scheduleAnotherCheck(); - } - - //otherwise they both left and aren't close to each other, so call the attacker the winner (defender escaped, possibly after a chase) - else - { - dataStore.endSiege(this.siegeData, attacker.getName(), defender.getName(), null); - } - } - - //a player has to be within 25 blocks of the edge of a besieged claim to be considered still in the fight - private boolean playerRemains(Player player) - { - for(int i = 0; i < this.siegeData.claims.size(); i++) - { - Claim claim = this.siegeData.claims.get(i); - if(claim.isNear(player.getLocation(), 25)) - { - return true; - } - } - - return false; - } - - //schedules another checkup later - private void scheduleAnotherCheck() - { - this.siegeData.checkupTaskID = GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, this, 20L * 30); - } + private SiegeData siegeData; + + public SiegeCheckupTask(SiegeData siegeData) + { + this.siegeData = siegeData; + } + + @Override + public void run() + { + DataStore dataStore = GriefPrevention.instance.dataStore; + Player defender = this.siegeData.defender; + Player attacker = this.siegeData.attacker; + + //where is the defender? + Claim defenderClaim = dataStore.getClaimAt(defender.getLocation(), false, null); + + //if this is a new claim and he has some permission there, extend the siege to include it + if (defenderClaim != null) + { + String noAccessReason = defenderClaim.allowAccess(defender); + if (defenderClaim.canSiege(defender) && noAccessReason == null) + { + this.siegeData.claims.add(defenderClaim); + defenderClaim.siegeData = this.siegeData; + } + } + + //determine who's close enough to the siege area to be considered "still here" + boolean attackerRemains = this.playerRemains(attacker); + boolean defenderRemains = this.playerRemains(defender); + + //if they're both here, just plan to come check again later + if (attackerRemains && defenderRemains) + { + this.scheduleAnotherCheck(); + } + + //otherwise attacker wins if the defender runs away + else if (attackerRemains && !defenderRemains) + { + dataStore.endSiege(this.siegeData, attacker.getName(), defender.getName(), null); + } + + //or defender wins if the attacker leaves + else if (!attackerRemains && defenderRemains) + { + dataStore.endSiege(this.siegeData, defender.getName(), attacker.getName(), null); + } + + //if they both left, but are still close together, the battle continues (check again later) + else if (attacker.getWorld().equals(defender.getWorld()) && attacker.getLocation().distanceSquared(defender.getLocation()) < 2500) //50-block radius for chasing + { + this.scheduleAnotherCheck(); + } + + //otherwise they both left and aren't close to each other, so call the attacker the winner (defender escaped, possibly after a chase) + else + { + dataStore.endSiege(this.siegeData, attacker.getName(), defender.getName(), null); + } + } + + //a player has to be within 25 blocks of the edge of a besieged claim to be considered still in the fight + private boolean playerRemains(Player player) + { + for (int i = 0; i < this.siegeData.claims.size(); i++) + { + Claim claim = this.siegeData.claims.get(i); + if (claim.isNear(player.getLocation(), 25)) + { + return true; + } + } + + return false; + } + + //schedules another checkup later + private void scheduleAnotherCheck() + { + this.siegeData.checkupTaskID = GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, this, 20L * 30); + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/SiegeData.java b/src/main/java/me/ryanhamshire/GriefPrevention/SiegeData.java index 0c7e420..2fdd7c5 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/SiegeData.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/SiegeData.java @@ -18,23 +18,23 @@ package me.ryanhamshire.GriefPrevention; -import java.util.ArrayList; - import org.bukkit.entity.Player; +import java.util.ArrayList; + //information about an ongoing siege public class SiegeData { - public Player defender; - public Player attacker; - public ArrayList claims; - public int checkupTaskID; - - public SiegeData(Player attacker, Player defender, Claim claim) - { - this.defender = defender; - this.attacker = attacker; - this.claims = new ArrayList(); - this.claims.add(claim); - } + public Player defender; + public Player attacker; + public ArrayList claims; + public int checkupTaskID; + + public SiegeData(Player attacker, Player defender, Claim claim) + { + this.defender = defender; + this.attacker = attacker; + this.claims = new ArrayList(); + this.claims.add(claim); + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/SpamDetector.java b/src/main/java/me/ryanhamshire/GriefPrevention/SpamDetector.java index 563bc86..0b8adbb 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/SpamDetector.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/SpamDetector.java @@ -9,206 +9,204 @@ class SpamDetector //last chat message shown and its timestamp, regardless of who sent it private String lastChatMessage = ""; private long lastChatMessageTimestamp = 0; - + //number of identical chat messages in a row private int duplicateMessageCount = 0; - + //data for individual chatters ConcurrentHashMap dataStore = new ConcurrentHashMap(); + private ChatterData getChatterData(UUID chatterID) { ChatterData data = this.dataStore.get(chatterID); - if(data == null) + if (data == null) { data = new ChatterData(); this.dataStore.put(chatterID, data); } - + return data; } - + SpamAnalysisResult AnalyzeMessage(UUID chatterID, String message, long timestamp) { SpamAnalysisResult result = new SpamAnalysisResult(); result.finalMessage = message; - + //remedy any CAPS SPAM, exception for very short messages which could be emoticons like =D or XD - if(message.length() > 4 && this.stringsAreSimilar(message.toUpperCase(), message)) + if (message.length() > 4 && this.stringsAreSimilar(message.toUpperCase(), message)) { message = message.toLowerCase(); result.finalMessage = message; } - + boolean spam = false; ChatterData chatterData = this.getChatterData(chatterID); //mute if total volume of text from this player is too high - if(message.length() > 50 && chatterData.getTotalRecentLength(timestamp) > 200) + if (message.length() > 50 && chatterData.getTotalRecentLength(timestamp) > 200) { spam = true; result.muteReason = "too much chat sent in 10 seconds"; chatterData.spamLevel++; } - + //always mute an exact match to the last chat message - if(result.finalMessage.equals(this.lastChatMessage) && timestamp - this.lastChatMessageTimestamp < 2000) + if (result.finalMessage.equals(this.lastChatMessage) && timestamp - this.lastChatMessageTimestamp < 2000) { chatterData.spamLevel += ++this.duplicateMessageCount; spam = true; result.muteReason = "repeat message"; - } - else + } else { this.lastChatMessage = message; this.lastChatMessageTimestamp = timestamp; this.duplicateMessageCount = 0; } - + //check message content and timing long millisecondsSinceLastMessage = timestamp - chatterData.lastMessageTimestamp; - + //if the message came too close to the last one - if(millisecondsSinceLastMessage < 1500) + if (millisecondsSinceLastMessage < 1500) { //increment the spam counter chatterData.spamLevel++; spam = true; } - + //if it's exactly the same as the last message from the same player and within 30 seconds - if(result.muteReason == null && millisecondsSinceLastMessage < 30000 && result.finalMessage.equalsIgnoreCase(chatterData.lastMessage)) + if (result.muteReason == null && millisecondsSinceLastMessage < 30000 && result.finalMessage.equalsIgnoreCase(chatterData.lastMessage)) { chatterData.spamLevel++; spam = true; result.muteReason = "repeat message"; } - + //if it's very similar to the last message from the same player and within 10 seconds of that message - if(result.muteReason == null && millisecondsSinceLastMessage < 10000 && this.stringsAreSimilar(message.toLowerCase(), chatterData.lastMessage.toLowerCase())) + if (result.muteReason == null && millisecondsSinceLastMessage < 10000 && this.stringsAreSimilar(message.toLowerCase(), chatterData.lastMessage.toLowerCase())) { chatterData.spamLevel++; spam = true; - if(chatterData.spamLevel > 2) + if (chatterData.spamLevel > 2) { result.muteReason = "similar message"; } } - + //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(result.muteReason == null && message.length() > 5) + if (result.muteReason == null && message.length() > 5) { int symbolsCount = 0; int whitespaceCount = 0; - for(int i = 0; i < message.length(); i++) + for (int i = 0; i < message.length(); i++) { char character = message.charAt(i); - if(!(Character.isLetterOrDigit(character))) + if (!(Character.isLetterOrDigit(character))) { symbolsCount++; } - - if(Character.isWhitespace(character)) + + if (Character.isWhitespace(character)) { whitespaceCount++; } } - - if(symbolsCount > message.length() / 2 || (message.length() > 15 && whitespaceCount < message.length() / 10)) + + if (symbolsCount > message.length() / 2 || (message.length() > 15 && whitespaceCount < message.length() / 10)) { spam = true; - if(chatterData.spamLevel > 0) result.muteReason = "gibberish"; + if (chatterData.spamLevel > 0) result.muteReason = "gibberish"; chatterData.spamLevel++; } } - + //very short messages close together are spam - if(result.muteReason == null && message.length() < 5 && millisecondsSinceLastMessage < 3000) + if (result.muteReason == null && message.length() < 5 && millisecondsSinceLastMessage < 3000) { spam = true; chatterData.spamLevel++; } - + //if the message was determined to be a spam, consider taking action - if(spam) - { + if (spam) + { //anything above level 8 for a player which has received a warning... kick or if enabled, ban - if(chatterData.spamLevel > 8 && chatterData.spamWarned) + if (chatterData.spamLevel > 8 && chatterData.spamWarned) { result.shouldBanChatter = true; - } - - else if(chatterData.spamLevel >= 4) + } else if (chatterData.spamLevel >= 4) { - if(!chatterData.spamWarned) + if (!chatterData.spamWarned) { chatterData.spamWarned = true; result.shouldWarnChatter = true; } - - if(result.muteReason == null) + + if (result.muteReason == null) { result.muteReason = "too-frequent text"; } } } - + //otherwise if not a spam, reduce the spam level for this player else { chatterData.spamLevel = 0; chatterData.spamWarned = false; } - + chatterData.AddMessage(message, timestamp); - + return result; } - + //if two strings are 75% identical, they're too close to follow each other in the chat private boolean stringsAreSimilar(String message, String lastMessage) { //ignore differences in only punctuation and whitespace message = message.replaceAll("[^\\p{Alpha}]", ""); lastMessage = lastMessage.replaceAll("[^\\p{Alpha}]", ""); - + //determine which is shorter String shorterString, longerString; - if(lastMessage.length() < message.length()) + if (lastMessage.length() < message.length()) { shorterString = lastMessage; longerString = message; - } - else + } else { shorterString = message; longerString = lastMessage; } - - if(shorterString.length() <= 5) return shorterString.equals(longerString); - + + if (shorterString.length() <= 5) return shorterString.equals(longerString); + //set similarity tolerance int maxIdenticalCharacters = longerString.length() - longerString.length() / 4; - + //trivial check on length - if(shorterString.length() < maxIdenticalCharacters) return false; - + if (shorterString.length() < maxIdenticalCharacters) return false; + //compare forward int identicalCount = 0; int i; - for(i = 0; i < shorterString.length(); i++) + for (i = 0; i < shorterString.length(); i++) { - if(shorterString.charAt(i) == longerString.charAt(i)) identicalCount++; - if(identicalCount > maxIdenticalCharacters) return true; + if (shorterString.charAt(i) == longerString.charAt(i)) identicalCount++; + if (identicalCount > maxIdenticalCharacters) return true; } - + //compare backward int j; - for(j = 0; j < shorterString.length() - i; j++) + for (j = 0; j < shorterString.length() - i; j++) { - if(shorterString.charAt(shorterString.length() - j - 1) == longerString.charAt(longerString.length() - j - 1)) identicalCount++; - if(identicalCount > maxIdenticalCharacters) return true; + if (shorterString.charAt(shorterString.length() - j - 1) == longerString.charAt(longerString.length() - j - 1)) + identicalCount++; + if (identicalCount > maxIdenticalCharacters) return true; } - + return false; } } @@ -227,17 +225,17 @@ class ChatterData public long lastMessageTimestamp; //last time the player sent a chat message or used a monitored slash command public int spamLevel = 0; //number of consecutive "spams" public boolean spamWarned = false; //whether the player has received a warning recently - + //all recent message lengths and their total private ConcurrentLinkedQueue recentMessageLengths = new ConcurrentLinkedQueue(); private int recentTotalLength = 0; - + public void AddMessage(String message, long timestamp) { int length = message.length(); this.recentMessageLengths.add(new LengthTimestampPair(length, timestamp)); this.recentTotalLength += length; - + this.lastMessage = message; this.lastMessageTimestamp = timestamp; } @@ -245,13 +243,13 @@ class ChatterData public int getTotalRecentLength(long timestamp) { LengthTimestampPair oldestPair = this.recentMessageLengths.peek(); - while(oldestPair != null && timestamp - oldestPair.timestamp > 10000) + while (oldestPair != null && timestamp - oldestPair.timestamp > 10000) { this.recentMessageLengths.poll(); this.recentTotalLength -= oldestPair.length; oldestPair = this.recentMessageLengths.peek(); } - + return this.recentTotalLength; } } @@ -260,7 +258,7 @@ class LengthTimestampPair { public long timestamp; public int length; - + public LengthTimestampPair(int length, long timestamp) { this.length = length; diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/TextMode.java b/src/main/java/me/ryanhamshire/GriefPrevention/TextMode.java index 1fd288f..53318b9 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/TextMode.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/TextMode.java @@ -21,11 +21,11 @@ package me.ryanhamshire.GriefPrevention; import org.bukkit.ChatColor; //just a few constants for chat color codes -public class TextMode -{ - final static ChatColor Info = ChatColor.AQUA; - final static ChatColor Instr = ChatColor.YELLOW; - final static ChatColor Warn = ChatColor.GOLD; - final static ChatColor Err = ChatColor.RED; - final static ChatColor Success = ChatColor.GREEN; +public class TextMode +{ + final static ChatColor Info = ChatColor.AQUA; + final static ChatColor Instr = ChatColor.YELLOW; + final static ChatColor Warn = ChatColor.GOLD; + final static ChatColor Err = ChatColor.RED; + final static ChatColor Success = ChatColor.GREEN; } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/UUIDFetcher.java b/src/main/java/me/ryanhamshire/GriefPrevention/UUIDFetcher.java index 6e064e6..882a612 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/UUIDFetcher.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/UUIDFetcher.java @@ -7,93 +7,98 @@ import org.bukkit.OfflinePlayer; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; - + import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.nio.ByteBuffer; -import java.util.*; - -class UUIDFetcher { +import java.util.HashMap; +import java.util.List; +import java.util.UUID; + +class UUIDFetcher +{ private static int PROFILES_PER_REQUEST = 100; private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft"; private final JSONParser jsonParser = new JSONParser(); private final List names; private final boolean rateLimiting; - + //cache for username -> uuid lookups static HashMap lookupCache; - + //record of username -> proper casing updates static HashMap correctedNames; - - public UUIDFetcher(List names, boolean rateLimiting) { + + public UUIDFetcher(List names, boolean rateLimiting) + { this.names = names; this.rateLimiting = rateLimiting; } - - public UUIDFetcher(List names) { + + public UUIDFetcher(List names) + { this(names, true); } - + public void call() throws Exception { - if(lookupCache == null) + if (lookupCache == null) { lookupCache = new HashMap(); } - - if(correctedNames == null) + + if (correctedNames == null) { correctedNames = new HashMap(); } - + GriefPrevention.AddLogEntry("UUID conversion process started. Please be patient - this may take a while."); - + GriefPrevention.AddLogEntry("Mining your local world data to save calls to Mojang..."); - OfflinePlayer [] players = GriefPrevention.instance.getServer().getOfflinePlayers(); - for(OfflinePlayer player : players) + OfflinePlayer[] players = GriefPrevention.instance.getServer().getOfflinePlayers(); + for (OfflinePlayer player : players) { - if(player.getName() != null && player.getUniqueId() != null) + if (player.getName() != null && player.getUniqueId() != null) { lookupCache.put(player.getName(), player.getUniqueId()); lookupCache.put(player.getName().toLowerCase(), player.getUniqueId()); correctedNames.put(player.getName().toLowerCase(), player.getName()); } } - + //try to get correct casing from local data GriefPrevention.AddLogEntry("Checking local server data to get correct casing for player names..."); - for(int i = 0; i < names.size(); i++) + for (int i = 0; i < names.size(); i++) { String name = names.get(i); String correctCasingName = correctedNames.get(name); - if(correctCasingName != null && !name.equals(correctCasingName)) + if (correctCasingName != null && !name.equals(correctCasingName)) { GriefPrevention.AddLogEntry(name + " --> " + correctCasingName); - names.set(i, correctCasingName); + names.set(i, correctCasingName); } } - + //look for local uuid's first GriefPrevention.AddLogEntry("Checking local server data for UUIDs already seen..."); - for(int i = 0; i < names.size(); i++) + for (int i = 0; i < names.size(); i++) { String name = names.get(i); UUID uuid = lookupCache.get(name); - if(uuid != null) + if (uuid != null) { GriefPrevention.AddLogEntry(name + " --> " + uuid.toString()); names.remove(i--); } } - + //for online mode, call Mojang to resolve the rest - if(GriefPrevention.instance.getServer().getOnlineMode()) + if (GriefPrevention.instance.getServer().getOnlineMode()) { GriefPrevention.AddLogEntry("Calling Mojang to get UUIDs for remaining unresolved players (this is the slowest step)..."); - + for (int i = 0; i * PROFILES_PER_REQUEST < names.size(); i++) { boolean retry = false; @@ -109,21 +114,21 @@ class UUIDFetcher { { array = (JSONArray) jsonParser.parse(new InputStreamReader(connection.getInputStream())); } - catch(Exception e) + catch (Exception e) { //in case of error 429 too many requests, pause and then retry later - if(e.getMessage().contains("429")) + if (e.getMessage().contains("429")) { retry = true; - + //if this is the first time we're sending anything, the batch size must be too big //try reducing it - if(i == 0 && PROFILES_PER_REQUEST > 1) + if (i == 0 && PROFILES_PER_REQUEST > 1) { GriefPrevention.AddLogEntry("Batch size " + PROFILES_PER_REQUEST + " seems too large. Looking for a workable batch size..."); PROFILES_PER_REQUEST = Math.max(PROFILES_PER_REQUEST - 5, 1); } - + //otherwise, keep the batch size which has worked for previous iterations //but wait a little while before trying again. else @@ -131,15 +136,15 @@ class UUIDFetcher { GriefPrevention.AddLogEntry("Mojang says we're sending requests too fast. Will retry every 30 seconds until we succeed..."); Thread.sleep(30000); } - } - else + } else { throw e; } } - }while(retry); - - for (Object profile : array) { + } while (retry); + + for (Object profile : array) + { JSONObject jsonProfile = (JSONObject) profile; String id = (String) jsonProfile.get("id"); String name = (String) jsonProfile.get("name"); @@ -148,18 +153,19 @@ class UUIDFetcher { lookupCache.put(name, uuid); lookupCache.put(name.toLowerCase(), uuid); } - if (rateLimiting) { + if (rateLimiting) + { Thread.sleep(200L); } } } - + //for offline mode, generate UUIDs for the rest else { GriefPrevention.AddLogEntry("Generating offline mode UUIDs for remaining unresolved players..."); - - for(int i = 0; i < names.size(); i++) + + for (int i = 0; i < names.size(); i++) { String name = names.get(i); UUID uuid = java.util.UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)); @@ -169,15 +175,17 @@ class UUIDFetcher { } } } - - private static void writeBody(HttpURLConnection connection, String body) throws Exception { + + private static void writeBody(HttpURLConnection connection, String body) throws Exception + { OutputStream stream = connection.getOutputStream(); stream.write(body.getBytes()); stream.flush(); stream.close(); } - - private static HttpURLConnection createConnection() throws Exception { + + private static HttpURLConnection createConnection() throws Exception + { URL url = new URL(PROFILE_URL); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST"); @@ -187,20 +195,24 @@ class UUIDFetcher { connection.setDoOutput(true); return connection; } - - private static UUID getUUID(String id) { - return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" +id.substring(20, 32)); + + private static UUID getUUID(String id) + { + return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" + id.substring(20, 32)); } - - public static byte[] toBytes(UUID uuid) { + + public static byte[] toBytes(UUID uuid) + { ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]); byteBuffer.putLong(uuid.getMostSignificantBits()); byteBuffer.putLong(uuid.getLeastSignificantBits()); return byteBuffer.array(); } - - public static UUID fromBytes(byte[] array) { - if (array.length != 16) { + + public static UUID fromBytes(byte[] array) + { + if (array.length != 16) + { throw new IllegalArgumentException("Illegal byte array length: " + array.length); } ByteBuffer byteBuffer = ByteBuffer.wrap(array); @@ -208,17 +220,17 @@ class UUIDFetcher { long leastSignificant = byteBuffer.getLong(); return new UUID(mostSignificant, leastSignificant); } - + public static UUID getUUIDOf(String name) throws Exception { UUID result = lookupCache.get(name); - if(result == null) + if (result == null) { //throw up our hands and report the problem in the logs //this player will lose his land claim blocks, but claims will stay in place as admin claims throw new IllegalArgumentException(name); } - + return result; } } \ No newline at end of file diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/Visualization.java b/src/main/java/me/ryanhamshire/GriefPrevention/Visualization.java index 0499a17..6263a91 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/Visualization.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/Visualization.java @@ -18,8 +18,6 @@ package me.ryanhamshire.GriefPrevention; -import java.util.ArrayList; - import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Tag; @@ -30,306 +28,300 @@ import org.bukkit.block.data.BlockData; import org.bukkit.block.data.Lightable; import org.bukkit.entity.Player; +import java.util.ArrayList; + //represents a visualization sent to a player //FEATURE: to show players visually where claim boundaries are, we send them fake block change packets //the result is that those players see new blocks, but the world hasn't been changed. other players can't see the new blocks, either. -public class Visualization +public class Visualization { - public ArrayList elements = new ArrayList(); - - //sends a visualization to a player - public static void Apply(Player player, Visualization visualization) - { - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); - - //if he has any current visualization, clear it first - if(playerData.currentVisualization != null) - { - Visualization.Revert(player); - } - - //if he's online, create a task to send him the visualization - if(player.isOnline() && visualization.elements.size() > 0 && visualization.elements.get(0).location.getWorld().equals(player.getWorld())) - { - GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, new VisualizationApplicationTask(player, playerData, visualization), 1L); - } - } - - //reverts a visualization by sending another block change list, this time with the real world block values - @SuppressWarnings("deprecation") - public static void Revert(Player player) - { - if(!player.isOnline()) return; - - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); - - Visualization visualization = playerData.currentVisualization; - - if(playerData.currentVisualization != null) - { - //locality - int minx = player.getLocation().getBlockX() - 100; - int minz = player.getLocation().getBlockZ() - 100; - int maxx = player.getLocation().getBlockX() + 100; - int maxz = player.getLocation().getBlockZ() + 100; + public ArrayList elements = new ArrayList(); - //remove any elements which are too far away - visualization.removeElementsOutOfRange(visualization.elements, minx, minz, maxx, maxz); - - //send real block information for any remaining elements - for(int i = 0; i < visualization.elements.size(); i++) - { - VisualizationElement element = visualization.elements.get(i); + //sends a visualization to a player + public static void Apply(Player player, Visualization visualization) + { + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); - //check player still in world where visualization exists - if(i == 0) - { - if(!player.getWorld().equals(element.location.getWorld())) return; - } + //if he has any current visualization, clear it first + if (playerData.currentVisualization != null) + { + Visualization.Revert(player); + } - player.sendBlockChange(element.location, element.realBlock); - } + //if he's online, create a task to send him the visualization + if (player.isOnline() && visualization.elements.size() > 0 && visualization.elements.get(0).location.getWorld().equals(player.getWorld())) + { + GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, new VisualizationApplicationTask(player, playerData, visualization), 1L); + } + } - playerData.currentVisualization = null; - } - } - - //convenience method to build a visualization from a claim - //visualizationType determines the style (gold blocks, silver, red, diamond, etc) - public static Visualization FromClaim(Claim claim, int height, VisualizationType visualizationType, Location locality) - { - //visualize only top level claims - if(claim.parent != null) - { - return FromClaim(claim.parent, height, visualizationType, locality); - } - - Visualization visualization = new Visualization(); - - //add subdivisions first - for(int i = 0; i < claim.children.size(); i++) - { - Claim child = claim.children.get(i); - if(!child.inDataStore) continue; - visualization.addClaimElements(child, height, VisualizationType.Subdivision, locality); - } - - //special visualization for administrative land claims - if(claim.isAdminClaim() && visualizationType == VisualizationType.Claim) + //reverts a visualization by sending another block change list, this time with the real world block values + @SuppressWarnings("deprecation") + public static void Revert(Player player) + { + if (!player.isOnline()) return; + + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); + + Visualization visualization = playerData.currentVisualization; + + if (playerData.currentVisualization != null) + { + //locality + int minx = player.getLocation().getBlockX() - 100; + int minz = player.getLocation().getBlockZ() - 100; + int maxx = player.getLocation().getBlockX() + 100; + int maxz = player.getLocation().getBlockZ() + 100; + + //remove any elements which are too far away + visualization.removeElementsOutOfRange(visualization.elements, minx, minz, maxx, maxz); + + //send real block information for any remaining elements + for (int i = 0; i < visualization.elements.size(); i++) + { + VisualizationElement element = visualization.elements.get(i); + + //check player still in world where visualization exists + if (i == 0) + { + if (!player.getWorld().equals(element.location.getWorld())) return; + } + + player.sendBlockChange(element.location, element.realBlock); + } + + playerData.currentVisualization = null; + } + } + + //convenience method to build a visualization from a claim + //visualizationType determines the style (gold blocks, silver, red, diamond, etc) + public static Visualization FromClaim(Claim claim, int height, VisualizationType visualizationType, Location locality) + { + //visualize only top level claims + if (claim.parent != null) + { + return FromClaim(claim.parent, height, visualizationType, locality); + } + + Visualization visualization = new Visualization(); + + //add subdivisions first + for (int i = 0; i < claim.children.size(); i++) + { + Claim child = claim.children.get(i); + if (!child.inDataStore) continue; + visualization.addClaimElements(child, height, VisualizationType.Subdivision, locality); + } + + //special visualization for administrative land claims + if (claim.isAdminClaim() && visualizationType == VisualizationType.Claim) { visualizationType = VisualizationType.AdminClaim; } - - //add top level last so that it takes precedence (it shows on top when the child claim boundaries overlap with its boundaries) - visualization.addClaimElements(claim, height, visualizationType, locality); - - return visualization; - } - - //adds a claim's visualization to the current visualization - //handy for combining several visualizations together, as when visualization a top level claim with several subdivisions inside - //locality is a performance consideration. only create visualization blocks for around 100 blocks of the locality - @SuppressWarnings("deprecation") - private void addClaimElements(Claim claim, int height, VisualizationType visualizationType, Location locality) - { - Location smallXsmallZ = claim.getLesserBoundaryCorner(); - Location bigXbigZ = claim.getGreaterBoundaryCorner(); - World world = smallXsmallZ.getWorld(); - boolean waterIsTransparent = locality.getBlock().getType() == Material.WATER; - - int smallx = smallXsmallZ.getBlockX(); - int smallz = smallXsmallZ.getBlockZ(); - int bigx = bigXbigZ.getBlockX(); - int bigz = bigXbigZ.getBlockZ(); - - BlockData cornerBlockData; - BlockData accentBlockData; - - ArrayList newElements = new ArrayList(); - - if(visualizationType == VisualizationType.Claim) - { - cornerBlockData = Material.GLOWSTONE.createBlockData(); - accentBlockData = Material.GOLD_BLOCK.createBlockData(); - } - - else if(visualizationType == VisualizationType.AdminClaim) - { - cornerBlockData = Material.GLOWSTONE.createBlockData(); - accentBlockData = Material.PUMPKIN.createBlockData(); -} - - else if(visualizationType == VisualizationType.Subdivision) - { - cornerBlockData = Material.IRON_BLOCK.createBlockData(); - accentBlockData = Material.WHITE_WOOL.createBlockData(); - } - - else if(visualizationType == VisualizationType.RestoreNature) - { - cornerBlockData = Material.DIAMOND_BLOCK.createBlockData(); - accentBlockData = Material.DIAMOND_BLOCK.createBlockData(); - } - - else - { - cornerBlockData = Material.REDSTONE_ORE.createBlockData(); - ((Lightable) cornerBlockData).setLit(true); - accentBlockData = Material.NETHERRACK.createBlockData(); - } - - //initialize visualization elements without Y values and real data - //that will be added later for only the visualization elements within visualization range - - //locality - int minx = locality.getBlockX() - 75; - int minz = locality.getBlockZ() - 75; - int maxx = locality.getBlockX() + 75; - int maxz = locality.getBlockZ() + 75; - - final int STEP = 10; - - //top line - newElements.add(new VisualizationElement(new Location(world, smallx, 0, bigz), cornerBlockData, Material.AIR.createBlockData())); - newElements.add(new VisualizationElement(new Location(world, smallx + 1, 0, bigz), accentBlockData, Material.AIR.createBlockData())); - for(int x = smallx + STEP; x < bigx - STEP / 2; x += STEP) - { - if(x > minx && x < maxx) - newElements.add(new VisualizationElement(new Location(world, x, 0, bigz), accentBlockData, Material.AIR.createBlockData())); - } - newElements.add(new VisualizationElement(new Location(world, bigx - 1, 0, bigz), accentBlockData, Material.AIR.createBlockData())); - - //bottom line - newElements.add(new VisualizationElement(new Location(world, smallx + 1, 0, smallz), accentBlockData, Material.AIR.createBlockData())); - for(int x = smallx + STEP; x < bigx - STEP / 2; x += STEP) - { - if(x > minx && x < maxx) - newElements.add(new VisualizationElement(new Location(world, x, 0, smallz), accentBlockData, Material.AIR.createBlockData())); - } - newElements.add(new VisualizationElement(new Location(world, bigx - 1, 0, smallz), accentBlockData, Material.AIR.createBlockData())); - - //left line - newElements.add(new VisualizationElement(new Location(world, smallx, 0, smallz), cornerBlockData, Material.AIR.createBlockData())); - newElements.add(new VisualizationElement(new Location(world, smallx, 0, smallz + 1), accentBlockData, Material.AIR.createBlockData())); - for(int z = smallz + STEP; z < bigz - STEP / 2; z += STEP) - { - if(z > minz && z < maxz) - newElements.add(new VisualizationElement(new Location(world, smallx, 0, z), accentBlockData, Material.AIR.createBlockData())); - } - newElements.add(new VisualizationElement(new Location(world, smallx, 0, bigz - 1), accentBlockData, Material.AIR.createBlockData())); - - //right line - newElements.add(new VisualizationElement(new Location(world, bigx, 0, smallz), cornerBlockData, Material.AIR.createBlockData())); - newElements.add(new VisualizationElement(new Location(world, bigx, 0, smallz + 1), accentBlockData, Material.AIR.createBlockData())); - for(int z = smallz + STEP; z < bigz - STEP / 2; z += STEP) - { - if(z > minz && z < maxz) - newElements.add(new VisualizationElement(new Location(world, bigx, 0, z), accentBlockData, Material.AIR.createBlockData())); - } - newElements.add(new VisualizationElement(new Location(world, bigx, 0, bigz - 1), accentBlockData, Material.AIR.createBlockData())); - newElements.add(new VisualizationElement(new Location(world, bigx, 0, bigz), cornerBlockData, Material.AIR.createBlockData())); - - //remove any out of range elements - this.removeElementsOutOfRange(newElements, minx, minz, maxx, maxz); - - //remove any elements outside the claim - for(int i = 0; i < newElements.size(); i++) - { - VisualizationElement element = newElements.get(i); - if(!claim.contains(element.location, true, false)) - { - newElements.remove(i--); - } - } - - //set Y values and real block information for any remaining visualization blocks - for(VisualizationElement element : newElements) - { - Location tempLocation = element.location; - element.location = getVisibleLocation(tempLocation.getWorld(), tempLocation.getBlockX(), height, tempLocation.getBlockZ(), waterIsTransparent); - height = element.location.getBlockY(); - element.realBlock = element.location.getBlock().getBlockData(); - } - - this.elements.addAll(newElements); - } - - //removes any elements which are out of visualization range - private void removeElementsOutOfRange(ArrayList elements, int minx, int minz, int maxx, int maxz) - { - for(int i = 0; i < elements.size(); i++) - { - Location location = elements.get(i).location; - if(location.getX() < minx || location.getX() > maxx || location.getZ() < minz || location.getZ() > maxz) - { - elements.remove(i--); - } - } + + //add top level last so that it takes precedence (it shows on top when the child claim boundaries overlap with its boundaries) + visualization.addClaimElements(claim, height, visualizationType, locality); + + return visualization; } - //finds a block the player can probably see. this is how visualizations "cling" to the ground or ceiling - private static Location getVisibleLocation(World world, int x, int y, int z, boolean waterIsTransparent) - { - Block block = world.getBlockAt(x, y, z); - BlockFace direction = (isTransparent(block, waterIsTransparent)) ? BlockFace.DOWN : BlockFace.UP; + //adds a claim's visualization to the current visualization + //handy for combining several visualizations together, as when visualization a top level claim with several subdivisions inside + //locality is a performance consideration. only create visualization blocks for around 100 blocks of the locality + @SuppressWarnings("deprecation") + private void addClaimElements(Claim claim, int height, VisualizationType visualizationType, Location locality) + { + Location smallXsmallZ = claim.getLesserBoundaryCorner(); + Location bigXbigZ = claim.getGreaterBoundaryCorner(); + World world = smallXsmallZ.getWorld(); + boolean waterIsTransparent = locality.getBlock().getType() == Material.WATER; - while( block.getY() >= 1 && - block.getY() < world.getMaxHeight() - 1 && - (!isTransparent(block.getRelative(BlockFace.UP), waterIsTransparent) || isTransparent(block, waterIsTransparent))) - { - block = block.getRelative(direction); - } - - return block.getLocation(); - } - - //helper method for above. allows visualization blocks to sit underneath partly transparent blocks like grass and fence - private static boolean isTransparent(Block block, boolean waterIsTransparent) - { - Material blockMaterial = block.getType(); - //Blacklist - switch (blockMaterial) - { - case SNOW: - return false; - } + int smallx = smallXsmallZ.getBlockX(); + int smallz = smallXsmallZ.getBlockZ(); + int bigx = bigXbigZ.getBlockX(); + int bigz = bigXbigZ.getBlockZ(); - //Whitelist TODO: some of this might already be included in isTransparent() - switch (blockMaterial) - { - case AIR: - case OAK_FENCE: - case ACACIA_FENCE: - case BIRCH_FENCE: - case DARK_OAK_FENCE: - case JUNGLE_FENCE: - case NETHER_BRICK_FENCE: - case SPRUCE_FENCE: - case OAK_FENCE_GATE: - case ACACIA_FENCE_GATE: - case BIRCH_FENCE_GATE: - case DARK_OAK_FENCE_GATE: - case SPRUCE_FENCE_GATE: - case JUNGLE_FENCE_GATE: - return true; - } + BlockData cornerBlockData; + BlockData accentBlockData; - if (Tag.SIGNS.isTagged(blockMaterial) || Tag.WALL_SIGNS.isTagged(blockMaterial)) - return true; + ArrayList newElements = new ArrayList(); - return (waterIsTransparent && block.getType() == Material.WATER) || - block.getType().isTransparent(); - } + if (visualizationType == VisualizationType.Claim) + { + cornerBlockData = Material.GLOWSTONE.createBlockData(); + accentBlockData = Material.GOLD_BLOCK.createBlockData(); + } else if (visualizationType == VisualizationType.AdminClaim) + { + cornerBlockData = Material.GLOWSTONE.createBlockData(); + accentBlockData = Material.PUMPKIN.createBlockData(); + } else if (visualizationType == VisualizationType.Subdivision) + { + cornerBlockData = Material.IRON_BLOCK.createBlockData(); + accentBlockData = Material.WHITE_WOOL.createBlockData(); + } else if (visualizationType == VisualizationType.RestoreNature) + { + cornerBlockData = Material.DIAMOND_BLOCK.createBlockData(); + accentBlockData = Material.DIAMOND_BLOCK.createBlockData(); + } else + { + cornerBlockData = Material.REDSTONE_ORE.createBlockData(); + ((Lightable) cornerBlockData).setLit(true); + accentBlockData = Material.NETHERRACK.createBlockData(); + } + + //initialize visualization elements without Y values and real data + //that will be added later for only the visualization elements within visualization range + + //locality + int minx = locality.getBlockX() - 75; + int minz = locality.getBlockZ() - 75; + int maxx = locality.getBlockX() + 75; + int maxz = locality.getBlockZ() + 75; + + final int STEP = 10; + + //top line + newElements.add(new VisualizationElement(new Location(world, smallx, 0, bigz), cornerBlockData, Material.AIR.createBlockData())); + newElements.add(new VisualizationElement(new Location(world, smallx + 1, 0, bigz), accentBlockData, Material.AIR.createBlockData())); + for (int x = smallx + STEP; x < bigx - STEP / 2; x += STEP) + { + if (x > minx && x < maxx) + newElements.add(new VisualizationElement(new Location(world, x, 0, bigz), accentBlockData, Material.AIR.createBlockData())); + } + newElements.add(new VisualizationElement(new Location(world, bigx - 1, 0, bigz), accentBlockData, Material.AIR.createBlockData())); + + //bottom line + newElements.add(new VisualizationElement(new Location(world, smallx + 1, 0, smallz), accentBlockData, Material.AIR.createBlockData())); + for (int x = smallx + STEP; x < bigx - STEP / 2; x += STEP) + { + if (x > minx && x < maxx) + newElements.add(new VisualizationElement(new Location(world, x, 0, smallz), accentBlockData, Material.AIR.createBlockData())); + } + newElements.add(new VisualizationElement(new Location(world, bigx - 1, 0, smallz), accentBlockData, Material.AIR.createBlockData())); + + //left line + newElements.add(new VisualizationElement(new Location(world, smallx, 0, smallz), cornerBlockData, Material.AIR.createBlockData())); + newElements.add(new VisualizationElement(new Location(world, smallx, 0, smallz + 1), accentBlockData, Material.AIR.createBlockData())); + for (int z = smallz + STEP; z < bigz - STEP / 2; z += STEP) + { + if (z > minz && z < maxz) + newElements.add(new VisualizationElement(new Location(world, smallx, 0, z), accentBlockData, Material.AIR.createBlockData())); + } + newElements.add(new VisualizationElement(new Location(world, smallx, 0, bigz - 1), accentBlockData, Material.AIR.createBlockData())); + + //right line + newElements.add(new VisualizationElement(new Location(world, bigx, 0, smallz), cornerBlockData, Material.AIR.createBlockData())); + newElements.add(new VisualizationElement(new Location(world, bigx, 0, smallz + 1), accentBlockData, Material.AIR.createBlockData())); + for (int z = smallz + STEP; z < bigz - STEP / 2; z += STEP) + { + if (z > minz && z < maxz) + newElements.add(new VisualizationElement(new Location(world, bigx, 0, z), accentBlockData, Material.AIR.createBlockData())); + } + newElements.add(new VisualizationElement(new Location(world, bigx, 0, bigz - 1), accentBlockData, Material.AIR.createBlockData())); + newElements.add(new VisualizationElement(new Location(world, bigx, 0, bigz), cornerBlockData, Material.AIR.createBlockData())); + + //remove any out of range elements + this.removeElementsOutOfRange(newElements, minx, minz, maxx, maxz); + + //remove any elements outside the claim + for (int i = 0; i < newElements.size(); i++) + { + VisualizationElement element = newElements.get(i); + if (!claim.contains(element.location, true, false)) + { + newElements.remove(i--); + } + } + + //set Y values and real block information for any remaining visualization blocks + for (VisualizationElement element : newElements) + { + Location tempLocation = element.location; + element.location = getVisibleLocation(tempLocation.getWorld(), tempLocation.getBlockX(), height, tempLocation.getBlockZ(), waterIsTransparent); + height = element.location.getBlockY(); + element.realBlock = element.location.getBlock().getBlockData(); + } + + this.elements.addAll(newElements); + } + + //removes any elements which are out of visualization range + private void removeElementsOutOfRange(ArrayList elements, int minx, int minz, int maxx, int maxz) + { + for (int i = 0; i < elements.size(); i++) + { + Location location = elements.get(i).location; + if (location.getX() < minx || location.getX() > maxx || location.getZ() < minz || location.getZ() > maxz) + { + elements.remove(i--); + } + } + } + + //finds a block the player can probably see. this is how visualizations "cling" to the ground or ceiling + private static Location getVisibleLocation(World world, int x, int y, int z, boolean waterIsTransparent) + { + Block block = world.getBlockAt(x, y, z); + BlockFace direction = (isTransparent(block, waterIsTransparent)) ? BlockFace.DOWN : BlockFace.UP; + + while (block.getY() >= 1 && + block.getY() < world.getMaxHeight() - 1 && + (!isTransparent(block.getRelative(BlockFace.UP), waterIsTransparent) || isTransparent(block, waterIsTransparent))) + { + block = block.getRelative(direction); + } + + return block.getLocation(); + } + + //helper method for above. allows visualization blocks to sit underneath partly transparent blocks like grass and fence + private static boolean isTransparent(Block block, boolean waterIsTransparent) + { + Material blockMaterial = block.getType(); + //Blacklist + switch (blockMaterial) + { + case SNOW: + return false; + } + + //Whitelist TODO: some of this might already be included in isTransparent() + switch (blockMaterial) + { + case AIR: + case OAK_FENCE: + case ACACIA_FENCE: + case BIRCH_FENCE: + case DARK_OAK_FENCE: + case JUNGLE_FENCE: + case NETHER_BRICK_FENCE: + case SPRUCE_FENCE: + case OAK_FENCE_GATE: + case ACACIA_FENCE_GATE: + case BIRCH_FENCE_GATE: + case DARK_OAK_FENCE_GATE: + case SPRUCE_FENCE_GATE: + case JUNGLE_FENCE_GATE: + return true; + } + + if (Tag.SIGNS.isTagged(blockMaterial) || Tag.WALL_SIGNS.isTagged(blockMaterial)) + return true; + + return (waterIsTransparent && block.getType() == Material.WATER) || + block.getType().isTransparent(); + } public static Visualization fromClaims(Iterable claims, int height, VisualizationType type, Location locality) { Visualization visualization = new Visualization(); - - for(Claim claim : claims) + + for (Claim claim : claims) { visualization.addClaimElements(claim, height, type, locality); } - + return visualization; } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/VisualizationApplicationTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/VisualizationApplicationTask.java index e9380c1..000ba9f 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/VisualizationApplicationTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/VisualizationApplicationTask.java @@ -15,46 +15,46 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - - package me.ryanhamshire.GriefPrevention; + +package me.ryanhamshire.GriefPrevention; import org.bukkit.entity.Player; //applies a visualization for a player by sending him block change packets -class VisualizationApplicationTask implements Runnable +class VisualizationApplicationTask implements Runnable { - private Visualization visualization; - private Player player; - private PlayerData playerData; + private Visualization visualization; + private Player player; + private PlayerData playerData; - public VisualizationApplicationTask(Player player, PlayerData playerData, Visualization visualization) - { - this.visualization = visualization; - this.playerData = playerData; - this.player = player; - } - - @SuppressWarnings("deprecation") + public VisualizationApplicationTask(Player player, PlayerData playerData, Visualization visualization) + { + this.visualization = visualization; + this.playerData = playerData; + this.player = player; + } + + @SuppressWarnings("deprecation") @Override - public void run() - { - //for each element (=block) of the visualization - for(int i = 0; i < visualization.elements.size(); i++) - { - VisualizationElement element = visualization.elements.get(i); - - //send the player a fake block change event - if(!element.location.getChunk().isLoaded()) continue; //cheap distance check - player.sendBlockChange(element.location, element.visualizedBlock); - } - - //remember the visualization applied to this player for later (so it can be inexpensively reverted) - playerData.currentVisualization = visualization; - - //schedule automatic visualization reversion in 60 seconds. - GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask( - GriefPrevention.instance, - new VisualizationReversionTask(player, playerData, visualization), - 20L * 60); //60 seconds - } + public void run() + { + //for each element (=block) of the visualization + for (int i = 0; i < visualization.elements.size(); i++) + { + VisualizationElement element = visualization.elements.get(i); + + //send the player a fake block change event + if (!element.location.getChunk().isLoaded()) continue; //cheap distance check + player.sendBlockChange(element.location, element.visualizedBlock); + } + + //remember the visualization applied to this player for later (so it can be inexpensively reverted) + playerData.currentVisualization = visualization; + + //schedule automatic visualization reversion in 60 seconds. + GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask( + GriefPrevention.instance, + new VisualizationReversionTask(player, playerData, visualization), + 20L * 60); //60 seconds + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/VisualizationElement.java b/src/main/java/me/ryanhamshire/GriefPrevention/VisualizationElement.java index 3786bb4..689515f 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/VisualizationElement.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/VisualizationElement.java @@ -17,21 +17,21 @@ */ package me.ryanhamshire.GriefPrevention; + import org.bukkit.Location; -import org.bukkit.Material; import org.bukkit.block.data.BlockData; //represents a "fake" block sent to a player as part of a visualization -public class VisualizationElement +public class VisualizationElement { - public Location location; - public BlockData visualizedBlock; - public BlockData realBlock; - - public VisualizationElement(Location location, BlockData visualizedBlock, BlockData realBlock) - { - this.location = location; - this.visualizedBlock = visualizedBlock; - this.realBlock = realBlock; - } + public Location location; + public BlockData visualizedBlock; + public BlockData realBlock; + + public VisualizationElement(Location location, BlockData visualizedBlock, BlockData realBlock) + { + this.location = location; + this.visualizedBlock = visualizedBlock; + this.realBlock = realBlock; + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/VisualizationReversionTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/VisualizationReversionTask.java index d3bc6d4..6820c36 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/VisualizationReversionTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/VisualizationReversionTask.java @@ -15,8 +15,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - - package me.ryanhamshire.GriefPrevention; + +package me.ryanhamshire.GriefPrevention; import me.ryanhamshire.GriefPrevention.events.VisualizationEvent; import org.bukkit.Bukkit; @@ -25,28 +25,28 @@ import org.bukkit.entity.Player; import java.util.Collections; //applies a visualization for a player by sending him block change packets -class VisualizationReversionTask implements Runnable +class VisualizationReversionTask implements Runnable { - private Visualization visualization; - private Player player; - private PlayerData playerData; + private Visualization visualization; + private Player player; + private PlayerData playerData; - public VisualizationReversionTask(Player player, PlayerData playerData, Visualization visualization) - { - this.visualization = visualization; - this.playerData = playerData; - this.player = player; - } - - @Override - public void run() - { - //don't do anything if the player's current visualization is different from the one scheduled to revert - if(playerData.currentVisualization != visualization) return; + public VisualizationReversionTask(Player player, PlayerData playerData, Visualization visualization) + { + this.visualization = visualization; + this.playerData = playerData; + this.player = player; + } - // alert plugins of a visualization - Bukkit.getPluginManager().callEvent(new VisualizationEvent(player, Collections.emptySet())); - - Visualization.Revert(player); - } + @Override + public void run() + { + //don't do anything if the player's current visualization is different from the one scheduled to revert + if (playerData.currentVisualization != visualization) return; + + // alert plugins of a visualization + Bukkit.getPluginManager().callEvent(new VisualizationEvent(player, Collections.emptySet())); + + Visualization.Revert(player); + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/VisualizationType.java b/src/main/java/me/ryanhamshire/GriefPrevention/VisualizationType.java index 4edea0f..f4516ef 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/VisualizationType.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/VisualizationType.java @@ -19,11 +19,11 @@ package me.ryanhamshire.GriefPrevention; //just an enumeration of the visualization types, which determine what materials will be for the fake blocks -public enum VisualizationType +public enum VisualizationType { - Claim, - Subdivision, - ErrorClaim, - RestoreNature, - AdminClaim + Claim, + Subdivision, + ErrorClaim, + RestoreNature, + AdminClaim } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/WelcomeTask.java b/src/main/java/me/ryanhamshire/GriefPrevention/WelcomeTask.java index aa91ff2..c528ba0 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/WelcomeTask.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/WelcomeTask.java @@ -10,24 +10,24 @@ import org.bukkit.inventory.meta.BookMeta; public class WelcomeTask implements Runnable { private Player player; - + public WelcomeTask(Player player) { this.player = player; } - + @Override public void run() { //abort if player has logged out since this task was scheduled - if(!this.player.isOnline()) return; - + if (!this.player.isOnline()) return; + //offer advice and a helpful link GriefPrevention.sendMessage(player, TextMode.Instr, Messages.AvoidGriefClaimLand); GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsVideo2, DataStore.SURVIVAL_VIDEO_URL); - + //give the player a reference book for later - if(GriefPrevention.instance.config_claims_supplyPlayerManual) + if (GriefPrevention.instance.config_claims_supplyPlayerManual) { ItemFactory factory = Bukkit.getItemFactory(); BookMeta meta = (BookMeta) factory.getItemMeta(Material.WRITTEN_BOOK); @@ -35,43 +35,43 @@ public class WelcomeTask implements Runnable DataStore datastore = GriefPrevention.instance.dataStore; meta.setAuthor(datastore.getMessage(Messages.BookAuthor)); meta.setTitle(datastore.getMessage(Messages.BookTitle)); - + StringBuilder page1 = new StringBuilder(); String URL = datastore.getMessage(Messages.BookLink, DataStore.SURVIVAL_VIDEO_URL); String intro = datastore.getMessage(Messages.BookIntro); - + page1.append(URL).append("\n\n"); page1.append(intro).append("\n\n"); String editToolName = GriefPrevention.instance.config_claims_modificationTool.name().replace('_', ' ').toLowerCase(); String infoToolName = GriefPrevention.instance.config_claims_investigationTool.name().replace('_', ' ').toLowerCase(); String configClaimTools = datastore.getMessage(Messages.BookTools, editToolName, infoToolName); page1.append(configClaimTools); - if(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius < 0) + if (GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius < 0) { page1.append(datastore.getMessage(Messages.BookDisabledChestClaims)); } - + StringBuilder page2 = new StringBuilder(datastore.getMessage(Messages.BookUsefulCommands)).append("\n\n"); page2.append("/Trust /UnTrust /TrustList\n"); page2.append("/ClaimsList\n"); page2.append("/AbandonClaim\n\n"); page2.append("/Claim /ExtendClaim\n"); - + page2.append("/IgnorePlayer\n\n"); - + page2.append("/SubdivideClaims\n"); page2.append("/AccessTrust\n"); page2.append("/ContainerTrust\n"); page2.append("/PermissionTrust"); - + meta.setPages(page1.toString(), page2.toString()); ItemStack item = new ItemStack(Material.WRITTEN_BOOK); item.setItemMeta(meta); player.getInventory().addItem(item); } - + } - + } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/WordFinder.java b/src/main/java/me/ryanhamshire/GriefPrevention/WordFinder.java index 44a0759..a1ad84b 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/WordFinder.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/WordFinder.java @@ -6,35 +6,35 @@ import java.util.regex.Pattern; class WordFinder { - private Pattern pattern; - + private Pattern pattern; + WordFinder(List wordsToFind) { - if(wordsToFind.size() == 0) return; - + if (wordsToFind.size() == 0) return; + StringBuilder patternBuilder = new StringBuilder(); - for(String word : wordsToFind) + for (String word : wordsToFind) { - if(!word.isEmpty() && !word.trim().isEmpty()) + if (!word.isEmpty() && !word.trim().isEmpty()) { patternBuilder.append("|(([^\\w]|^)" + Pattern.quote(word) + "([^\\w]|$))"); } } - + String patternString = patternBuilder.toString(); - if(patternString.length() > 1) + if (patternString.length() > 1) { //trim extraneous leading pipe (|) patternString = patternString.substring(1); } - + this.pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); } - + boolean hasMatch(String input) { - if(this.pattern == null) return false; - + if (this.pattern == null) return false; + Matcher matcher = this.pattern.matcher(input); return matcher.find(); } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/WorldGuardWrapper.java b/src/main/java/me/ryanhamshire/GriefPrevention/WorldGuardWrapper.java index 4f97f54..3d49344 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/WorldGuardWrapper.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/WorldGuardWrapper.java @@ -1,19 +1,18 @@ package me.ryanhamshire.GriefPrevention; -import org.bukkit.Location; -import org.bukkit.entity.Player; - import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.World; import com.sk89q.worldguard.WorldGuard; -import com.sk89q.worldguard.bukkit.WorldGuardPlugin; import com.sk89q.worldguard.bukkit.BukkitPlayer; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; import com.sk89q.worldguard.internal.permission.RegionPermissionModel; import com.sk89q.worldguard.protection.ApplicableRegionSet; import com.sk89q.worldguard.protection.flags.Flags; import com.sk89q.worldguard.protection.managers.RegionManager; import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.bukkit.Location; +import org.bukkit.entity.Player; class WorldGuardWrapper { @@ -21,7 +20,7 @@ class WorldGuardWrapper public WorldGuardWrapper() throws ClassNotFoundException { - this.worldGuard = (WorldGuardPlugin)GriefPrevention.instance.getServer().getPluginManager().getPlugin("WorldGuard"); + this.worldGuard = (WorldGuardPlugin) GriefPrevention.instance.getServer().getPluginManager().getPlugin("WorldGuard"); } public boolean canBuild(Location lesserCorner, Location greaterCorner, Player creatingPlayer) @@ -31,11 +30,11 @@ class WorldGuardWrapper BukkitPlayer localPlayer = new BukkitPlayer(this.worldGuard, creatingPlayer); World world = WorldGuard.getInstance().getPlatform().getMatcher().getWorldByName(lesserCorner.getWorld().getName()); - if(new RegionPermissionModel(localPlayer).mayIgnoreRegionProtection(world)) return true; + if (new RegionPermissionModel(localPlayer).mayIgnoreRegionProtection(world)) return true; - RegionManager manager = WorldGuard.getInstance().getPlatform().getRegionContainer().get(world); + RegionManager manager = WorldGuard.getInstance().getPlatform().getRegionContainer().get(world); - if(manager != null) + if (manager != null) { ProtectedCuboidRegion tempRegion = new ProtectedCuboidRegion( "GP_TEMP", @@ -43,8 +42,10 @@ class WorldGuardWrapper BlockVector3.at(greaterCorner.getX(), world.getMaxY(), greaterCorner.getZ())); ApplicableRegionSet overlaps = manager.getApplicableRegions(tempRegion); - for (ProtectedRegion r : overlaps.getRegions()) { - if (!manager.getApplicableRegions(r).testState(localPlayer, Flags.BUILD)) { + for (ProtectedRegion r : overlaps.getRegions()) + { + if (!manager.getApplicableRegions(r).testState(localPlayer, Flags.BUILD)) + { return false; } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/events/AccrueClaimBlocksEvent.java b/src/main/java/me/ryanhamshire/GriefPrevention/events/AccrueClaimBlocksEvent.java index c905aeb..94adbd0 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/events/AccrueClaimBlocksEvent.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/events/AccrueClaimBlocksEvent.java @@ -14,11 +14,15 @@ public class AccrueClaimBlocksEvent extends Event { // Custom Event Requirements private static final HandlerList handlers = new HandlerList(); - public static HandlerList getHandlerList() { + + public static HandlerList getHandlerList() + { return handlers; } + @Override - public HandlerList getHandlers() { + public HandlerList getHandlers() + { return handlers; } @@ -30,7 +34,6 @@ public class AccrueClaimBlocksEvent extends Event /** * @param player Player receiving accruals * @param blocksToAccrue Blocks to accrue - * * @deprecated Use {@link #AccrueClaimBlocksEvent(Player, int, boolean)} instead */ public AccrueClaimBlocksEvent(Player player, int blocksToAccrue) @@ -67,7 +70,8 @@ public class AccrueClaimBlocksEvent extends Event /** * @return whether the player was detected as idle (used for idle accrual percentage) */ - public boolean isIdle() { + public boolean isIdle() + { return this.isIdle; } @@ -78,6 +82,7 @@ public class AccrueClaimBlocksEvent extends Event /** * Modify the amount of claim blocks to deliver to the player for this 10 minute interval + * * @param blocksToAccrue blocks to deliver */ public void setBlocksToAccrue(int blocksToAccrue) @@ -87,6 +92,7 @@ public class AccrueClaimBlocksEvent extends Event /** * Similar to setBlocksToAccrue(int), but automatically converting from a per-hour rate value to a 10-minute rate value + * * @param blocksToAccruePerHour the per-hour rate of blocks to deliver */ diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/events/ClaimCreatedEvent.java b/src/main/java/me/ryanhamshire/GriefPrevention/events/ClaimCreatedEvent.java index 4af4fe8..72e02b0 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/events/ClaimCreatedEvent.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/events/ClaimCreatedEvent.java @@ -13,11 +13,13 @@ import org.bukkit.event.HandlerList; * Created by Narimm on 5/08/2018. */ -public class ClaimCreatedEvent extends Event implements Cancellable { +public class ClaimCreatedEvent extends Event implements Cancellable +{ private static final HandlerList handlers = new HandlerList(); - public static HandlerList getHandlerList() { + public static HandlerList getHandlerList() + { return handlers; } @@ -27,23 +29,27 @@ public class ClaimCreatedEvent extends Event implements Cancellable { private boolean cancelled = false; - public ClaimCreatedEvent(Claim claim, CommandSender creator) { + public ClaimCreatedEvent(Claim claim, CommandSender creator) + { this.claim = claim; this.creator = creator; } @Override - public HandlerList getHandlers() { + public HandlerList getHandlers() + { return handlers; } @Override - public boolean isCancelled() { + public boolean isCancelled() + { return cancelled; } @Override - public void setCancelled(boolean b) { + public void setCancelled(boolean b) + { this.cancelled = b; } @@ -52,7 +58,8 @@ public class ClaimCreatedEvent extends Event implements Cancellable { * * @return Claim */ - public Claim getClaim() { + public Claim getClaim() + { return claim; } @@ -61,7 +68,8 @@ public class ClaimCreatedEvent extends Event implements Cancellable { * * @return the CommandSender */ - public CommandSender getCreator() { + public CommandSender getCreator() + { return creator; } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/events/ClaimDeletedEvent.java b/src/main/java/me/ryanhamshire/GriefPrevention/events/ClaimDeletedEvent.java index b691e0a..76e5a82 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/events/ClaimDeletedEvent.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/events/ClaimDeletedEvent.java @@ -1,43 +1,46 @@ package me.ryanhamshire.GriefPrevention.events; import me.ryanhamshire.GriefPrevention.Claim; - import org.bukkit.event.Event; import org.bukkit.event.HandlerList; /** * This event gets called whenever a claim is going to be deleted. This event is * not called when a claim is resized. - * + * * @author Tux2 - * */ -public class ClaimDeletedEvent extends Event{ +public class ClaimDeletedEvent extends Event +{ // Custom Event Requirements private static final HandlerList handlers = new HandlerList(); - public static HandlerList getHandlerList() { + public static HandlerList getHandlerList() + { return handlers; } private Claim claim; - public ClaimDeletedEvent(Claim claim) { + public ClaimDeletedEvent(Claim claim) + { this.claim = claim; } /** * Gets the claim to be deleted. - * + * * @return */ - public Claim getClaim() { + public Claim getClaim() + { return claim; } @Override - public HandlerList getHandlers() { + public HandlerList getHandlers() + { return handlers; } } \ No newline at end of file diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/events/ClaimExpirationEvent.java b/src/main/java/me/ryanhamshire/GriefPrevention/events/ClaimExpirationEvent.java index 7c63336..8d1c4d7 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/events/ClaimExpirationEvent.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/events/ClaimExpirationEvent.java @@ -1,7 +1,6 @@ package me.ryanhamshire.GriefPrevention.events; import me.ryanhamshire.GriefPrevention.Claim; - import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; @@ -11,7 +10,7 @@ public class ClaimExpirationEvent extends Event implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled = false; - + public static HandlerList getHandlerList() { return handlers; @@ -34,13 +33,13 @@ public class ClaimExpirationEvent extends Event implements Cancellable { return handlers; } - + @Override public boolean isCancelled() { return this.cancelled; } - + @Override public void setCancelled(boolean cancelled) { diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/events/ClaimModifiedEvent.java b/src/main/java/me/ryanhamshire/GriefPrevention/events/ClaimModifiedEvent.java index c527dcb..f40ac75 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/events/ClaimModifiedEvent.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/events/ClaimModifiedEvent.java @@ -11,24 +11,28 @@ import org.bukkit.event.HandlerList; * a claim has changed. The CommandSender can be null in the event that the modification is called by the plugin itself. * Created by Narimm on 5/08/2018. */ -public class ClaimModifiedEvent extends Event { +public class ClaimModifiedEvent extends Event +{ private static final HandlerList handlers = new HandlerList(); - - public static HandlerList getHandlerList() { + + public static HandlerList getHandlerList() + { return handlers; } - + private final Claim claim; private CommandSender modifier; - public ClaimModifiedEvent(Claim claim, CommandSender modifier) { + public ClaimModifiedEvent(Claim claim, CommandSender modifier) + { this.claim = claim; this.modifier = modifier; } @Override - public HandlerList getHandlers() { + public HandlerList getHandlers() + { return handlers; } @@ -37,7 +41,8 @@ public class ClaimModifiedEvent extends Event { * * @return the claim */ - public Claim getClaim() { + public Claim getClaim() + { return claim; } @@ -46,7 +51,8 @@ public class ClaimModifiedEvent extends Event { * * @return the CommandSender or null */ - public CommandSender getModifier() { + public CommandSender getModifier() + { return modifier; } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/events/PlayerKickBanEvent.java b/src/main/java/me/ryanhamshire/GriefPrevention/events/PlayerKickBanEvent.java index 601dca0..d8ebcfc 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/events/PlayerKickBanEvent.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/events/PlayerKickBanEvent.java @@ -34,8 +34,8 @@ public class PlayerKickBanEvent extends Event /** * @param player Player getting kicked and/or banned * @param reason Reason message for kick/ban - * @param source What caused the kick/ban - * @param ban True if player is getting banned + * @param source What caused the kick/ban + * @param ban True if player is getting banned */ public PlayerKickBanEvent(Player player, String reason, String source, boolean ban) { diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/events/PreventBlockBreakEvent.java b/src/main/java/me/ryanhamshire/GriefPrevention/events/PreventBlockBreakEvent.java index bc14d1d..a88ace6 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/events/PreventBlockBreakEvent.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/events/PreventBlockBreakEvent.java @@ -10,7 +10,7 @@ public class PreventBlockBreakEvent extends Event implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled = false; - private BlockBreakEvent innerEvent; + private BlockBreakEvent innerEvent; public static HandlerList getHandlerList() { @@ -21,7 +21,7 @@ public class PreventBlockBreakEvent extends Event implements Cancellable { this.innerEvent = innerEvent; } - + public BlockBreakEvent getInnerEvent() { return this.innerEvent; @@ -32,13 +32,13 @@ public class PreventBlockBreakEvent extends Event implements Cancellable { return handlers; } - + @Override public boolean isCancelled() { return this.cancelled; } - + @Override public void setCancelled(boolean cancelled) { diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/events/PreventPvPEvent.java b/src/main/java/me/ryanhamshire/GriefPrevention/events/PreventPvPEvent.java index 1aeecf5..a07d657 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/events/PreventPvPEvent.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/events/PreventPvPEvent.java @@ -1,7 +1,6 @@ package me.ryanhamshire.GriefPrevention.events; import me.ryanhamshire.GriefPrevention.Claim; - import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; @@ -34,13 +33,13 @@ public class PreventPvPEvent extends Event implements Cancellable { return handlers; } - + @Override public boolean isCancelled() { return this.cancelled; } - + @Override public void setCancelled(boolean cancelled) { diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/events/ProtectDeathDropsEvent.java b/src/main/java/me/ryanhamshire/GriefPrevention/events/ProtectDeathDropsEvent.java index cfba989..5f20789 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/events/ProtectDeathDropsEvent.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/events/ProtectDeathDropsEvent.java @@ -1,7 +1,6 @@ package me.ryanhamshire.GriefPrevention.events; import me.ryanhamshire.GriefPrevention.Claim; - import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; @@ -34,13 +33,13 @@ public class ProtectDeathDropsEvent extends Event implements Cancellable { return handlers; } - + @Override public boolean isCancelled() { return this.cancelled; } - + @Override public void setCancelled(boolean cancelled) { diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/events/SaveTrappedPlayerEvent.java b/src/main/java/me/ryanhamshire/GriefPrevention/events/SaveTrappedPlayerEvent.java index 46305f5..bd776b2 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/events/SaveTrappedPlayerEvent.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/events/SaveTrappedPlayerEvent.java @@ -1,7 +1,6 @@ package me.ryanhamshire.GriefPrevention.events; import me.ryanhamshire.GriefPrevention.Claim; - import org.bukkit.Location; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; @@ -46,13 +45,13 @@ public class SaveTrappedPlayerEvent extends Event implements Cancellable { return handlers; } - + @Override public boolean isCancelled() { return this.cancelled; } - + @Override public void setCancelled(boolean cancelled) { diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/events/TrustChangedEvent.java b/src/main/java/me/ryanhamshire/GriefPrevention/events/TrustChangedEvent.java index 567fc20..eac93b0 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/events/TrustChangedEvent.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/events/TrustChangedEvent.java @@ -1,110 +1,119 @@ package me.ryanhamshire.GriefPrevention.events; -import java.util.ArrayList; -import java.util.List; - +import me.ryanhamshire.GriefPrevention.Claim; +import me.ryanhamshire.GriefPrevention.ClaimPermission; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; -import me.ryanhamshire.GriefPrevention.Claim; -import me.ryanhamshire.GriefPrevention.ClaimPermission; +import java.util.ArrayList; +import java.util.List; /** * This event is thrown when the trust is changed in one or more claims - * - * @author roinujnosde * + * @author roinujnosde */ -public class TrustChangedEvent extends Event implements Cancellable { - +public class TrustChangedEvent extends Event implements Cancellable +{ + private static final HandlerList handlers = new HandlerList(); - + private final Player changer; private final List claims; private final ClaimPermission claimPermission; private final boolean given; private final String identifier; private boolean cancelled; - - public TrustChangedEvent(Player changer, List claims, ClaimPermission claimPermission, boolean given, - String identifier) { - super(); - this.changer = changer; - this.claims = claims; - this.claimPermission = claimPermission; - this.given = given; - this.identifier = identifier; - } - public TrustChangedEvent(Player changer, Claim claim, ClaimPermission claimPermission, boolean given, String identifier) { - this.changer = changer; - claims = new ArrayList<>(); - claims.add(claim); - this.claimPermission = claimPermission; - this.given = given; - this.identifier = identifier; - } + public TrustChangedEvent(Player changer, List claims, ClaimPermission claimPermission, boolean given, + String identifier) + { + super(); + this.changer = changer; + this.claims = claims; + this.claimPermission = claimPermission; + this.given = given; + this.identifier = identifier; + } - @Override - public HandlerList getHandlers() { - return handlers; - } - - /** - * Gets who made the change - * - * @return the changer - */ - public Player getChanger() { - return changer; - } + public TrustChangedEvent(Player changer, Claim claim, ClaimPermission claimPermission, boolean given, String identifier) + { + this.changer = changer; + claims = new ArrayList<>(); + claims.add(claim); + this.claimPermission = claimPermission; + this.given = given; + this.identifier = identifier; + } - /** - * Gets the changed claims - * - * @return the changed claims - */ - public List getClaims() { - return claims; - } + @Override + public HandlerList getHandlers() + { + return handlers; + } - /** - * Gets the claim permission (null if the permission is being taken) - * - * @return the claim permission - */ - public ClaimPermission getClaimPermission() { - return claimPermission; - } + /** + * Gets who made the change + * + * @return the changer + */ + public Player getChanger() + { + return changer; + } - /** - * Checks if the trust is being given - * - * @return true if given, false if taken - */ - public boolean isGiven() { - return given; - } + /** + * Gets the changed claims + * + * @return the changed claims + */ + public List getClaims() + { + return claims; + } - /** - * Gets the identifier of the receiver of this action - * Can be: "public", "all", a UUID, a permission - * - * @return the identifier - */ - public String getIdentifier() { - return identifier; - } + /** + * Gets the claim permission (null if the permission is being taken) + * + * @return the claim permission + */ + public ClaimPermission getClaimPermission() + { + return claimPermission; + } - @Override - public boolean isCancelled() { - return cancelled; - } + /** + * Checks if the trust is being given + * + * @return true if given, false if taken + */ + public boolean isGiven() + { + return given; + } - @Override - public void setCancelled(boolean cancelled) { - this.cancelled = cancelled; - } + /** + * Gets the identifier of the receiver of this action + * Can be: "public", "all", a UUID, a permission + * + * @return the identifier + */ + public String getIdentifier() + { + return identifier; + } + + @Override + public boolean isCancelled() + { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) + { + this.cancelled = cancelled; + } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/events/VisualizationEvent.java b/src/main/java/me/ryanhamshire/GriefPrevention/events/VisualizationEvent.java index 2b2a19a..f5d953d 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/events/VisualizationEvent.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/events/VisualizationEvent.java @@ -11,7 +11,8 @@ import java.util.Collections; /** * Called when GriefPrevention is sending claim visuals to a player */ -public class VisualizationEvent extends PlayerEvent { +public class VisualizationEvent extends PlayerEvent +{ private static final HandlerList handlers = new HandlerList(); private final Collection claims; private final boolean showSubdivides; @@ -22,7 +23,8 @@ public class VisualizationEvent extends PlayerEvent { * @param player Player receiving visuals * @param claim The claim being visualized (with subdivides), or null if visuals being removed */ - public VisualizationEvent(Player player, Claim claim) { + public VisualizationEvent(Player player, Claim claim) + { super(player); this.claims = Collections.singleton(claim); this.showSubdivides = true; @@ -34,7 +36,8 @@ public class VisualizationEvent extends PlayerEvent { * @param player Player receiving visuals * @param claims Claims being visualized (without subdivides) */ - public VisualizationEvent(Player player, Collection claims) { + public VisualizationEvent(Player player, Collection claims) + { super(player); this.claims = claims; this.showSubdivides = false; @@ -45,7 +48,8 @@ public class VisualizationEvent extends PlayerEvent { * * @return Claims being visualized */ - public Collection getClaims() { + public Collection getClaims() + { return claims; } @@ -54,16 +58,19 @@ public class VisualizationEvent extends PlayerEvent { * * @return True if subdivide claims are being shown */ - public boolean showSubdivides() { + public boolean showSubdivides() + { return showSubdivides; } @Override - public HandlerList getHandlers() { + public HandlerList getHandlers() + { return handlers; } - public static HandlerList getHandlerList() { + public static HandlerList getHandlerList() + { return handlers; } } diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/events/package-info.java b/src/main/java/me/ryanhamshire/GriefPrevention/events/package-info.java index 5419224..660665a 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/events/package-info.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/events/package-info.java @@ -1,5 +1,5 @@ /** - * + * @author Ryan */ /** * @author Ryan diff --git a/src/main/java/me/ryanhamshire/GriefPrevention/metrics/MetricsHandler.java b/src/main/java/me/ryanhamshire/GriefPrevention/metrics/MetricsHandler.java index 5bbf2b5..d28d159 100644 --- a/src/main/java/me/ryanhamshire/GriefPrevention/metrics/MetricsHandler.java +++ b/src/main/java/me/ryanhamshire/GriefPrevention/metrics/MetricsHandler.java @@ -14,6 +14,7 @@ import java.util.concurrent.Callable; public class MetricsHandler { private Metrics metrics; + public MetricsHandler(GriefPrevention plugin, String dataMode) { metrics = new Metrics(plugin); @@ -23,7 +24,7 @@ public class MetricsHandler addSimplePie("custom_build", plugin.getDescription().getVersion().equals("15.2.2")); addSimplePie("bukkit_impl", plugin.getServer().getVersion().split("-")[1]); } - catch (Throwable ignored){} + catch (Throwable ignored) {} //enums and etc. would be amazing.