From 1f6223526b74cc9e1735e5fc2557a4cc8c7c044e Mon Sep 17 00:00:00 2001 From: Ryan Hamshire Date: Mon, 10 Sep 2012 16:38:48 -0700 Subject: [PATCH] 6.4 --- plugin.yml | 2 +- .../ryanhamshire/GriefPrevention/Claim.java | 70 +++++- .../CleanupUnusedClaimsTask.java | 186 ++++++++++++++ .../GriefPrevention/DataStore.java | 58 +---- .../EquipShovelProcessingTask.java | 35 ++- .../GriefPrevention/GriefPrevention.java | 20 +- .../GriefPrevention/Messages.java | 2 +- .../GriefPrevention/PlayerEventHandler.java | 28 ++- .../RestoreNatureProcessingTask.java | 231 ++++++++++-------- 9 files changed, 435 insertions(+), 197 deletions(-) create mode 100644 src/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimsTask.java diff --git a/plugin.yml b/plugin.yml index efdeba7..8bc20dd 100644 --- a/plugin.yml +++ b/plugin.yml @@ -2,7 +2,7 @@ name: GriefPrevention main: me.ryanhamshire.GriefPrevention.GriefPrevention softdepend: [Vault, Multiverse-Core, My Worlds] dev-url: http://dev.bukkit.org/server-mods/grief-prevention -version: 6.3 +version: 6.4 commands: abandonclaim: description: Deletes a claim. diff --git a/src/me/ryanhamshire/GriefPrevention/Claim.java b/src/me/ryanhamshire/GriefPrevention/Claim.java index 8468266..97baf4d 100644 --- a/src/me/ryanhamshire/GriefPrevention/Claim.java +++ b/src/me/ryanhamshire/GriefPrevention/Claim.java @@ -39,7 +39,6 @@ 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 - //IF MODIFIED, THE CLAIM DATA FILE'S NAME WILL CHANGE. ANY MODIFICATIONS MUST BE HANDLED VERY CAREFULLY Location lesserBoundaryCorner; Location greaterBoundaryCorner; @@ -436,6 +435,12 @@ public class Claim //following a siege where the defender lost, the claim will allow everyone access for a time if(this.doorsOpen) return null; + //admin claims need adminclaims permission only. + if(this.isAdminClaim()) + { + if(player.hasPermission("griefprevention.adminclaims")) return null; + } + //claim owner and admins in ignoreclaims mode have access if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null; @@ -474,6 +479,12 @@ public class Claim //owner and administrators in ignoreclaims mode have access if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null; + //admin claims need adminclaims permission only. + if(this.isAdminClaim()) + { + if(player.hasPermission("griefprevention.adminclaims")) return null; + } + //check for explicit individual container or build permission if(this.hasExplicitPermission(player, ClaimPermission.Inventory)) return null; if(this.hasExplicitPermission(player, ClaimPermission.Build)) return null; @@ -750,4 +761,61 @@ public class Claim return thisCorner.getWorld().getName().compareTo(otherCorner.getWorld().getName()) < 0; } + + long getPlayerInvestmentScore() + { + //decide which blocks will be considered player placed + Location lesserBoundaryCorner = this.getLesserBoundaryCorner(); + ArrayList playerBlocks = RestoreNatureProcessingTask.getPlayerBlocks(lesserBoundaryCorner.getWorld().getEnvironment(), lesserBoundaryCorner.getBlock().getBiome()); + + //scan the claim for player placed blocks + double score = 0; + + boolean creativeMode = GriefPrevention.instance.creativeRulesApply(lesserBoundaryCorner); + + for(int x = this.lesserBoundaryCorner.getBlockX(); x <= this.greaterBoundaryCorner.getBlockX(); x++) + { + for(int z = this.lesserBoundaryCorner.getBlockZ(); z <= this.greaterBoundaryCorner.getBlockZ(); z++) + { + int y = this.lesserBoundaryCorner.getBlockY(); + for(; y < this.lesserBoundaryCorner.getWorld().getSeaLevel(); y++) + { + Block block = this.lesserBoundaryCorner.getWorld().getBlockAt(x, y, z); + if(playerBlocks.contains(block.getTypeId())) + { + if(block.getType() == Material.CHEST && !creativeMode) + { + score += 10; + } + else + { + score += .2; + } + } + } + + for(; y < this.lesserBoundaryCorner.getWorld().getMaxHeight(); y++) + { + Block block = this.lesserBoundaryCorner.getWorld().getBlockAt(x, y, z); + if(playerBlocks.contains(block.getTypeId())) + { + if(block.getType() == Material.CHEST && !creativeMode) + { + score += 10; + } + else if(creativeMode && (block.getType() == Material.LAVA || block.getType() == Material.STATIONARY_LAVA)) + { + score -= 10; + } + else + { + score += 1; + } + } + } + } + } + + return (long)score; + } } diff --git a/src/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimsTask.java b/src/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimsTask.java new file mode 100644 index 0000000..a5f925a --- /dev/null +++ b/src/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimsTask.java @@ -0,0 +1,186 @@ +/* + GriefPrevention Server Plugin for Minecraft + Copyright (C) 2011 Ryan Hamshire + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + + package me.ryanhamshire.GriefPrevention; + +import java.util.Calendar; +import java.util.Random; + +import org.bukkit.Chunk; +import org.bukkit.World; + +//FEATURE: automatically remove claims owned by inactive players which: +//...aren't protecting much OR +//...are a free new player claim (and the player has no other claims) OR +//...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 CleanupUnusedClaimsTask implements Runnable +{ + int nextClaimIndex; + + CleanupUnusedClaimsTask() + { + //start scanning in a random spot + if(GriefPrevention.instance.dataStore.claims.size() == 0) + { + this.nextClaimIndex = 0; + } + else + { + Random randomNumberGenerator = new Random(); + this.nextClaimIndex = randomNumberGenerator.nextInt(GriefPrevention.instance.dataStore.claims.size()); + } + } + + @Override + public void run() + { + //don't do anything when there are no claims + if(GriefPrevention.instance.dataStore.claims.size() == 0) return; + + //wrap search around to beginning + if(this.nextClaimIndex >= GriefPrevention.instance.dataStore.claims.size()) this.nextClaimIndex = 0; + + //decide which claim to check next + Claim claim = GriefPrevention.instance.dataStore.claims.get(this.nextClaimIndex++); + + //skip administrative claims + if(claim.isAdminClaim()) return; + + //get data for the player, especially last login timestamp + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(claim.ownerName); + + //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 he's been gone at least a week, if he has ONLY the new player claim, it will be removed + Calendar sevenDaysAgo = Calendar.getInstance(); + sevenDaysAgo.add(Calendar.DATE, -7); + boolean newPlayerClaimsExpired = sevenDaysAgo.getTime().after(playerData.lastLogin); + + //if only one claim, and the player hasn't played in a week + if(newPlayerClaimsExpired && playerData.claims.size() == 1) + { + //if that's a chest claim, delete it + if(claim.getArea() <= areaOfDefaultClaim) + { + claim.removeSurfaceFluids(null); + GriefPrevention.instance.dataStore.deleteClaim(claim); + + //if in a creative mode world, delete the claim + if(GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner())) + { + GriefPrevention.instance.restoreClaim(claim, 0); + } + + GriefPrevention.AddLogEntry(" " + claim.getOwnerName() + "'s new player claim expired."); + } + } + + //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(playerData.lastLogin)) + { + GriefPrevention.instance.dataStore.deleteClaimsForPlayer(claim.getOwnerName(), true); + GriefPrevention.AddLogEntry(" All of " + claim.getOwnerName() + "'s claims have expired."); + } + } + + else + { + + //if the player has been gone two weeks, scan claim content to assess player investment + Calendar fourteenDaysAgo = Calendar.getInstance(); + fourteenDaysAgo.add(Calendar.DATE, -14); + boolean needsInvestmentScan = fourteenDaysAgo.getTime().after(playerData.lastLogin); + + //avoid scanning large claims and administrative claims + if(claim.isAdminClaim() || claim.getWidth() > 25 || claim.getHeight() > 25) return; + + //if creative mode or the claim owner has been away a long enough time, scan the claim content + if(needsInvestmentScan || GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner())) + { + int minInvestment; + if(GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner())) + { + minInvestment = 400; + } + else + { + minInvestment = 200; + } + + long investmentScore = claim.getPlayerInvestmentScore(); + boolean removeClaim = false; + + //in creative mode, a build which is almost entirely lava above sea level will be automatically removed, even if the owner is an active player + //lava above the surface deducts 10 points per block from the investment score + //so 500 blocks of lava without anything built to offset all that potential mess would be cleaned up automatically + if(GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner()) && investmentScore < -5000) + { + removeClaim = true; + } + + //otherwise, the only way to get a claim automatically removed based on build investment is to be away for two weeks AND not build much of anything + else if(needsInvestmentScan && investmentScore < minInvestment) + { + removeClaim = true; + } + + if(removeClaim) + { + GriefPrevention.instance.dataStore.deleteClaim(claim); + GriefPrevention.AddLogEntry("Removed " + claim.getOwnerName() + "'s unused claim @ " + GriefPrevention.getfriendlyLocationString(claim.getLesserBoundaryCorner())); + + //if in a creative mode world, restore the claim area + if(GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner())) + { + GriefPrevention.instance.restoreClaim(claim, 0); + } + } + } + } + + //toss that player data out of the cache, it's probably not needed in memory right now + GriefPrevention.instance.dataStore.clearCachedPlayerData(claim.ownerName); + + //since we're potentially loading a lot of chunks to scan parts of the world where there are no players currently playing, be mindful of memory usage + //unfortunately, java/minecraft don't do a good job of clearing unused memory, leading to out of memory errors from this type of world scanning + if(this.nextClaimIndex % 20 == 0) + { + World world = claim.getLesserBoundaryCorner().getWorld(); + Chunk [] chunks = world.getLoadedChunks(); + for(int i = 0; i < chunks.length; i++) + { + Chunk chunk = chunks[i]; + chunk.unload(true, true); + } + + System.gc(); + } + } +} diff --git a/src/me/ryanhamshire/GriefPrevention/DataStore.java b/src/me/ryanhamshire/GriefPrevention/DataStore.java index 415ff30..0471e5f 100644 --- a/src/me/ryanhamshire/GriefPrevention/DataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DataStore.java @@ -70,63 +70,6 @@ public abstract class DataStore GriefPrevention.AddLogEntry(playerNames.size() + " players have staked claims."); - //load each of these players and determine whether his claims should be cleaned up - for(int i = 0; i < playerNames.size(); i++) - { - String playerName = playerNames.get(i); - - PlayerData playerData = this.getPlayerData(playerName); - - int areaOfDefaultClaim = 0; - - //determine area of the default chest claim - if(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius >= 0) - { - areaOfDefaultClaim = (int)Math.pow(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius * 2 + 1, 2); - } - - //figure out how long the player has been away - Calendar sevenDaysAgo = Calendar.getInstance(); - sevenDaysAgo.add(Calendar.DATE, -7); - boolean claimsExpired = sevenDaysAgo.getTime().after(playerData.lastLogin); - - //if only one claim, and the player hasn't played in a week - if(claimsExpired && playerData.claims.size() == 1) - { - Claim claim = playerData.claims.get(0); - - //if that's a chest claim, delete it - if(claim.getArea() <= areaOfDefaultClaim) - { - claim.removeSurfaceFluids(null); - this.deleteClaim(claim); - - //if in a creative mode world, delete the claim - if(GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner())) - { - GriefPrevention.instance.restoreClaim(claim, 0); - } - - GriefPrevention.AddLogEntry(" " + playerName + "'s new player claim expired."); - } - } - - if(GriefPrevention.instance.config_claims_expirationDays > 0) - { - Calendar earliestPermissibleLastLogin = Calendar.getInstance(); - earliestPermissibleLastLogin.add(Calendar.DATE, -GriefPrevention.instance.config_claims_expirationDays); - - if(earliestPermissibleLastLogin.getTime().after(playerData.lastLogin)) - { - this.deleteClaimsForPlayer(playerName, true); - GriefPrevention.AddLogEntry(" All of " + playerName + "'s claims have expired."); - } - } - - //toss that player data out of the cache, it's not needed in memory right now - this.clearCachedPlayerData(playerName); - } - //load up all the messages from messages.yml this.loadMessages(); @@ -1013,6 +956,7 @@ public abstract class DataStore this.addDefault(defaults, Messages.TrappedWontWorkHere, "Sorry, unable to find a safe location to teleport you to. Contact an admin, or consider /kill if you don't want to wait.", 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 anhd selling claim blocks is disabled.", null); //load the config file FileConfiguration config = YamlConfiguration.loadConfiguration(new File(messagesFilePath)); diff --git a/src/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java b/src/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java index 094708e..78ab7aa 100644 --- a/src/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java +++ b/src/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java @@ -44,30 +44,23 @@ class EquipShovelProcessingTask implements Runnable PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getName()); - //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) - { - playerData.shovelMode = ShovelMode.Basic; - GriefPrevention.sendMessage(player, TextMode.Info, Messages.ShovelBasicClaimMode); - } - int remainingBlocks = playerData.getRemainingClaimBlocks(); - //instruct him in the steps to create a claim - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.RemainingBlocks, String.valueOf(remainingBlocks)); - - //demo link changes based on game mode - if(GriefPrevention.instance.creativeRulesApply(player.getLocation())) + //if in basic claims mode... + if(playerData.shovelMode == ShovelMode.Basic) { - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.CreativeBasicsDemoAdvertisement); - } - else - { - GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsDemoAdvertisement); + //tell him how many claim blocks he has available + 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.CreativeBasicsDemoAdvertisement); + } + else + { + GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SurvivalBasicsDemoAdvertisement); + } } } } diff --git a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java index ae01161..897cace 100644 --- a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java +++ b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java @@ -565,6 +565,10 @@ public class GriefPrevention extends JavaPlugin EntityCleanupTask task = new EntityCleanupTask(0); this.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 20L); + //start recurring cleanup scan for unused claims belonging to inactive players + CleanupUnusedClaimsTask task2 = new CleanupUnusedClaimsTask(); + this.getServer().getScheduler().scheduleSyncRepeatingTask(this, task2, 20L * 60 * 2, 20L * 60 * 5); + //register for events PluginManager pluginManager = this.getServer().getPluginManager(); @@ -614,7 +618,7 @@ public class GriefPrevention extends JavaPlugin { 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."); } - } + } } //handles slash commands @@ -1041,7 +1045,11 @@ public class GriefPrevention extends JavaPlugin else if(cmd.getName().equalsIgnoreCase("buyclaimblocks") && player != null) { //if economy is disabled, don't do anything - if(GriefPrevention.economy == null) return true; + if(GriefPrevention.economy == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.BuySellNotConfigured); + return true; + } //if purchase disabled, send error message if(GriefPrevention.instance.config_economy_claimBlocksPurchaseCost == 0) @@ -1122,7 +1130,11 @@ public class GriefPrevention extends JavaPlugin else if(cmd.getName().equalsIgnoreCase("sellclaimblocks") && player != null) { //if economy is disabled, don't do anything - if(GriefPrevention.economy == null) return true; + if(GriefPrevention.economy == null) + { + GriefPrevention.sendMessage(player, TextMode.Err, Messages.BuySellNotConfigured); + return true; + } //if disabled, error message if(GriefPrevention.instance.config_economy_claimBlocksSellValue == 0) @@ -2337,7 +2349,7 @@ public class GriefPrevention extends JavaPlugin //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(), chunk.getWorld().getBiome(lesserBoundaryCorner.getBlockX(), lesserBoundaryCorner.getBlockZ()), lesserBoundaryCorner, greaterBoundaryCorner, chunk.getWorld().getSeaLevel() + 1, aggressiveMode, GriefPrevention.instance.creativeRulesApply(lesserBoundaryCorner), playerReceivingVisualization); + RestoreNatureProcessingTask task = new RestoreNatureProcessingTask(snapshots, miny, chunk.getWorld().getEnvironment(), lesserBoundaryCorner.getBlock().getBiome(), lesserBoundaryCorner, greaterBoundaryCorner, chunk.getWorld().getSeaLevel() + 1, aggressiveMode, GriefPrevention.instance.creativeRulesApply(lesserBoundaryCorner), playerReceivingVisualization); GriefPrevention.instance.getServer().getScheduler().scheduleAsyncDelayedTask(GriefPrevention.instance, task, delayInTicks); } } \ No newline at end of file diff --git a/src/me/ryanhamshire/GriefPrevention/Messages.java b/src/me/ryanhamshire/GriefPrevention/Messages.java index 6dfcadc..6343adb 100644 --- a/src/me/ryanhamshire/GriefPrevention/Messages.java +++ b/src/me/ryanhamshire/GriefPrevention/Messages.java @@ -2,5 +2,5 @@ package me.ryanhamshire.GriefPrevention; public enum Messages { - RespectingClaims, IgnoringClaims, SuccessfulAbandon, RestoreNatureActivate, RestoreNatureAggressiveActivate, FillModeActive, TransferClaimPermission, TransferClaimMissing, TransferClaimAdminOnly, PlayerNotFound, TransferTopLevel, TransferSuccess, TrustListNoClaim, ClearPermsOwnerOnly, UntrustIndividualAllClaims, UntrustEveryoneAllClaims, NoPermissionTrust, ClearPermissionsOneClaim, UntrustIndividualSingleClaim, OnlySellBlocks, BlockPurchaseCost, ClaimBlockLimit, InsufficientFunds, PurchaseConfirmation, OnlyPurchaseBlocks, BlockSaleValue, NotEnoughBlocksForSale, BlockSaleConfirmation, AdminClaimsMode, BasicClaimsMode, SubdivisionMode, SubdivisionDemo, DeleteClaimMissing, DeletionSubdivisionWarning, DeleteSuccess, CantDeleteAdminClaim, DeleteAllSuccess, NoDeletePermission, AllAdminDeleted, AdjustBlocksSuccess, NotTrappedHere, TrappedOnCooldown, RescuePending, NonSiegeWorld, AlreadySieging, NotSiegableThere, SiegeTooFarAway, NoSiegeDefenseless, AlreadyUnderSiegePlayer, AlreadyUnderSiegeArea, NoSiegeAdminClaim, SiegeOnCooldown, SiegeAlert, SiegeConfirmed, AbandonClaimMissing, NotYourClaim, DeleteTopLevelClaim, AbandonSuccess, CantGrantThatPermission, GrantPermissionNoClaim, GrantPermissionConfirmation, ManageUniversalPermissionsInstruction, ManageOneClaimPermissionsInstruction, CollectivePublic, BuildPermission, ContainersPermission, AccessPermission, PermissionsPermission, LocationCurrentClaim, LocationAllClaims, PvPImmunityStart, SiegeNoDrop, DonateItemsInstruction, ChestFull, DonationSuccess, PlayerTooCloseForFire, TooDeepToClaim, ChestClaimConfirmation, AutomaticClaimNotification, TrustCommandAdvertisement, GoldenShovelAdvertisement, UnprotectedChestWarning, ThatPlayerPvPImmune, CantFightWhileImmune, NoDamageClaimedEntity, ShovelBasicClaimMode, RemainingBlocks, CreativeBasicsDemoAdvertisement, SurvivalBasicsDemoAdvertisement, TrappedChatKeyword, TrappedInstructions, PvPNoDrop, SiegeNoTeleport, BesiegedNoTeleport, SiegeNoContainers, PvPNoContainers, PvPImmunityEnd, NoBedPermission, NoWildernessBuckets, NoLavaNearOtherPlayer, TooFarAway, BlockNotClaimed, BlockClaimed, SiegeNoShovel, RestoreNaturePlayerInChunk, NoCreateClaimPermission, ResizeClaimTooSmall, ResizeNeedMoreBlocks, NoCreativeUnClaim, ClaimResizeSuccess, ResizeFailOverlap, ResizeStart, ResizeFailOverlapSubdivision, SubdivisionStart, CreateSubdivisionOverlap, SubdivisionSuccess, CreateClaimFailOverlap, CreateClaimFailOverlapOtherPlayer, ClaimsDisabledWorld, ClaimStart, NewClaimTooSmall, CreateClaimInsufficientBlocks, AbandonClaimAdvertisement, CreateClaimFailOverlapShort, CreateClaimSuccess, SiegeWinDoorsOpen, RescueAbortedMoved, SiegeDoorsLockedEjection, NoModifyDuringSiege, OnlyOwnersModifyClaims, NoBuildUnderSiege, NoBuildPvP, NoBuildPermission, NonSiegeMaterial, NoOwnerBuildUnderSiege, NoAccessPermission, NoContainersSiege, NoContainersPermission, OwnerNameForAdminClaims, ClaimTooSmallForEntities, TooManyEntitiesInClaim, YouHaveNoClaims, ConfirmFluidRemoval, AutoBanNotify, AdjustGroupBlocksSuccess, InvalidPermissionID, UntrustOwnerOnly, HowToClaimRegex, NoBuildOutsideClaims, PlayerOfflineTime, BuildingOutsideClaims, TrappedWontWorkHere, CommandBannedInPvP, UnclaimCleanupWarning + RespectingClaims, IgnoringClaims, SuccessfulAbandon, RestoreNatureActivate, RestoreNatureAggressiveActivate, FillModeActive, TransferClaimPermission, TransferClaimMissing, TransferClaimAdminOnly, PlayerNotFound, TransferTopLevel, TransferSuccess, TrustListNoClaim, ClearPermsOwnerOnly, UntrustIndividualAllClaims, UntrustEveryoneAllClaims, NoPermissionTrust, ClearPermissionsOneClaim, UntrustIndividualSingleClaim, OnlySellBlocks, BlockPurchaseCost, ClaimBlockLimit, InsufficientFunds, PurchaseConfirmation, OnlyPurchaseBlocks, BlockSaleValue, NotEnoughBlocksForSale, BlockSaleConfirmation, AdminClaimsMode, BasicClaimsMode, SubdivisionMode, SubdivisionDemo, DeleteClaimMissing, DeletionSubdivisionWarning, DeleteSuccess, CantDeleteAdminClaim, DeleteAllSuccess, NoDeletePermission, AllAdminDeleted, AdjustBlocksSuccess, NotTrappedHere, TrappedOnCooldown, RescuePending, NonSiegeWorld, AlreadySieging, NotSiegableThere, SiegeTooFarAway, NoSiegeDefenseless, AlreadyUnderSiegePlayer, AlreadyUnderSiegeArea, NoSiegeAdminClaim, SiegeOnCooldown, SiegeAlert, SiegeConfirmed, AbandonClaimMissing, NotYourClaim, DeleteTopLevelClaim, AbandonSuccess, CantGrantThatPermission, GrantPermissionNoClaim, GrantPermissionConfirmation, ManageUniversalPermissionsInstruction, ManageOneClaimPermissionsInstruction, CollectivePublic, BuildPermission, ContainersPermission, AccessPermission, PermissionsPermission, LocationCurrentClaim, LocationAllClaims, PvPImmunityStart, SiegeNoDrop, DonateItemsInstruction, ChestFull, DonationSuccess, PlayerTooCloseForFire, TooDeepToClaim, ChestClaimConfirmation, AutomaticClaimNotification, TrustCommandAdvertisement, GoldenShovelAdvertisement, UnprotectedChestWarning, ThatPlayerPvPImmune, CantFightWhileImmune, NoDamageClaimedEntity, ShovelBasicClaimMode, RemainingBlocks, CreativeBasicsDemoAdvertisement, SurvivalBasicsDemoAdvertisement, TrappedChatKeyword, TrappedInstructions, PvPNoDrop, SiegeNoTeleport, BesiegedNoTeleport, SiegeNoContainers, PvPNoContainers, PvPImmunityEnd, NoBedPermission, NoWildernessBuckets, NoLavaNearOtherPlayer, TooFarAway, BlockNotClaimed, BlockClaimed, SiegeNoShovel, RestoreNaturePlayerInChunk, NoCreateClaimPermission, ResizeClaimTooSmall, ResizeNeedMoreBlocks, NoCreativeUnClaim, ClaimResizeSuccess, ResizeFailOverlap, ResizeStart, ResizeFailOverlapSubdivision, SubdivisionStart, CreateSubdivisionOverlap, SubdivisionSuccess, CreateClaimFailOverlap, CreateClaimFailOverlapOtherPlayer, ClaimsDisabledWorld, ClaimStart, NewClaimTooSmall, CreateClaimInsufficientBlocks, AbandonClaimAdvertisement, CreateClaimFailOverlapShort, CreateClaimSuccess, SiegeWinDoorsOpen, RescueAbortedMoved, SiegeDoorsLockedEjection, NoModifyDuringSiege, OnlyOwnersModifyClaims, NoBuildUnderSiege, NoBuildPvP, NoBuildPermission, NonSiegeMaterial, NoOwnerBuildUnderSiege, NoAccessPermission, NoContainersSiege, NoContainersPermission, OwnerNameForAdminClaims, ClaimTooSmallForEntities, TooManyEntitiesInClaim, YouHaveNoClaims, ConfirmFluidRemoval, AutoBanNotify, AdjustGroupBlocksSuccess, InvalidPermissionID, UntrustOwnerOnly, HowToClaimRegex, NoBuildOutsideClaims, PlayerOfflineTime, BuildingOutsideClaims, TrappedWontWorkHere, CommandBannedInPvP, UnclaimCleanupWarning, BuySellNotConfigured } diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java index b097b1d..d4d6337 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java @@ -234,6 +234,8 @@ class PlayerEventHandler implements Listener { player.kickPlayer(""); } + + return true; } //cancel any messages while at or above the third spam level and issue warnings @@ -521,7 +523,7 @@ class PlayerEventHandler implements Listener PlayerData playerData = this.dataStore.getPlayerData(player.getName()); //if banned, add IP to the temporary IP ban list - if(player.isBanned()) + if(player.isBanned() && playerData.ipAddress != null) { long now = Calendar.getInstance().getTimeInMillis(); this.tempBannedIps.add(new IpBanInfo(playerData.ipAddress, now + this.MILLISECONDS_IN_DAY, player.getName())); @@ -534,7 +536,7 @@ class PlayerEventHandler implements Listener } //make sure his data is all saved - he might have accrued some claim blocks while playing that were not saved immediately - this.dataStore.savePlayerData(player.getName(), playerData); + this.dataStore.savePlayerData(player.getName(), playerData); this.onPlayerDisconnect(event.getPlayer(), event.getQuitMessage()); } @@ -762,6 +764,20 @@ class PlayerEventHandler implements Listener ItemStack newItemStack = player.getInventory().getItem(event.getNewSlot()); if(newItemStack != null && newItemStack.getType() == GriefPrevention.instance.config_claims_modificationTool) { + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getName()); + + //always reset to basic claims mode + if(playerData.shovelMode != ShovelMode.Basic) + { + playerData.shovelMode = ShovelMode.Basic; + GriefPrevention.sendMessage(player, TextMode.Info, Messages.ShovelBasicClaimMode); + } + + //reset any work he might have been doing + playerData.lastShovelLocation = null; + playerData.claimResizing = null; + + //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 EquipShovelProcessingTask task = new EquipShovelProcessingTask(player); GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 15L); //15L is approx. 3/4 of a second } @@ -878,7 +894,7 @@ class PlayerEventHandler implements Listener try { clickedBlock = event.getClickedBlock(); //null returned here means interacting with air - if(clickedBlock == null) + if(clickedBlock == null || clickedBlock.getType() == Material.SNOW) { //try to find a far away non-air block along line of sight HashSet transparentMaterials = new HashSet(); @@ -1570,7 +1586,7 @@ class PlayerEventHandler implements Listener { GriefPrevention.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlapOtherPlayer, claim.getOwnerName()); Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.ErrorClaim, player.getLocation()); - Visualization.Apply(player, visualization); + Visualization.Apply(player, visualization); } return; @@ -1592,6 +1608,10 @@ class PlayerEventHandler implements Listener //remember it, and start him on the new claim playerData.lastShovelLocation = clickedBlock.getLocation(); GriefPrevention.sendMessage(player, TextMode.Instr, Messages.ClaimStart); + + //show him where he's working + Visualization visualization = Visualization.FromClaim(new Claim(clickedBlock.getLocation(), clickedBlock.getLocation(), "", new String[]{}, new String[]{}, new String[]{}, new String[]{}, null), clickedBlock.getY(), VisualizationType.RestoreNature, player.getLocation()); + Visualization.Apply(player, visualization); } //otherwise, he's trying to finish creating a claim by setting the other boundary corner diff --git a/src/me/ryanhamshire/GriefPrevention/RestoreNatureProcessingTask.java b/src/me/ryanhamshire/GriefPrevention/RestoreNatureProcessingTask.java index a2dee81..0457fee 100644 --- a/src/me/ryanhamshire/GriefPrevention/RestoreNatureProcessingTask.java +++ b/src/me/ryanhamshire/GriefPrevention/RestoreNatureProcessingTask.java @@ -75,110 +75,8 @@ class RestoreNatureProcessingTask implements Runnable this.notAllowedToHang.add(Material.STONE.getId()); } - //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 this.playerBlocks = new ArrayList(); - this.playerBlocks.add(Material.FIRE.getId()); - this.playerBlocks.add(Material.BED_BLOCK.getId()); - this.playerBlocks.add(Material.WOOD.getId()); - this.playerBlocks.add(Material.BOOKSHELF.getId()); - this.playerBlocks.add(Material.BREWING_STAND.getId()); - this.playerBlocks.add(Material.BRICK.getId()); - this.playerBlocks.add(Material.COBBLESTONE.getId()); - this.playerBlocks.add(Material.GLASS.getId()); - this.playerBlocks.add(Material.LAPIS_BLOCK.getId()); - this.playerBlocks.add(Material.DISPENSER.getId()); - this.playerBlocks.add(Material.NOTE_BLOCK.getId()); - this.playerBlocks.add(Material.POWERED_RAIL.getId()); - this.playerBlocks.add(Material.DETECTOR_RAIL.getId()); - this.playerBlocks.add(Material.PISTON_STICKY_BASE.getId()); - this.playerBlocks.add(Material.PISTON_BASE.getId()); - this.playerBlocks.add(Material.PISTON_EXTENSION.getId()); - this.playerBlocks.add(Material.WOOL.getId()); - this.playerBlocks.add(Material.PISTON_MOVING_PIECE.getId()); - this.playerBlocks.add(Material.GOLD_BLOCK.getId()); - this.playerBlocks.add(Material.IRON_BLOCK.getId()); - this.playerBlocks.add(Material.DOUBLE_STEP.getId()); - this.playerBlocks.add(Material.STEP.getId()); - this.playerBlocks.add(Material.CROPS.getId()); - this.playerBlocks.add(Material.TNT.getId()); - this.playerBlocks.add(Material.MOSSY_COBBLESTONE.getId()); - this.playerBlocks.add(Material.TORCH.getId()); - this.playerBlocks.add(Material.FIRE.getId()); - this.playerBlocks.add(Material.WOOD_STAIRS.getId()); - this.playerBlocks.add(Material.CHEST.getId()); - this.playerBlocks.add(Material.REDSTONE_WIRE.getId()); - this.playerBlocks.add(Material.DIAMOND_BLOCK.getId()); - this.playerBlocks.add(Material.WORKBENCH.getId()); - this.playerBlocks.add(Material.SOIL.getId()); - this.playerBlocks.add(Material.FURNACE.getId()); - this.playerBlocks.add(Material.BURNING_FURNACE.getId()); - this.playerBlocks.add(Material.WOODEN_DOOR.getId()); - this.playerBlocks.add(Material.SIGN_POST.getId()); - this.playerBlocks.add(Material.LADDER.getId()); - this.playerBlocks.add(Material.RAILS.getId()); - this.playerBlocks.add(Material.COBBLESTONE_STAIRS.getId()); - this.playerBlocks.add(Material.WALL_SIGN.getId()); - this.playerBlocks.add(Material.STONE_PLATE.getId()); - this.playerBlocks.add(Material.LEVER.getId()); - this.playerBlocks.add(Material.IRON_DOOR_BLOCK.getId()); - this.playerBlocks.add(Material.WOOD_PLATE.getId()); - this.playerBlocks.add(Material.REDSTONE_TORCH_ON.getId()); - this.playerBlocks.add(Material.REDSTONE_TORCH_OFF.getId()); - this.playerBlocks.add(Material.STONE_BUTTON.getId()); - this.playerBlocks.add(Material.SNOW_BLOCK.getId()); - this.playerBlocks.add(Material.JUKEBOX.getId()); - this.playerBlocks.add(Material.FENCE.getId()); - this.playerBlocks.add(Material.PORTAL.getId()); - this.playerBlocks.add(Material.JACK_O_LANTERN.getId()); - this.playerBlocks.add(Material.CAKE_BLOCK.getId()); - this.playerBlocks.add(Material.DIODE_BLOCK_ON.getId()); - this.playerBlocks.add(Material.DIODE_BLOCK_OFF.getId()); - this.playerBlocks.add(Material.TRAP_DOOR.getId()); - this.playerBlocks.add(Material.SMOOTH_BRICK.getId()); - this.playerBlocks.add(Material.HUGE_MUSHROOM_1.getId()); - this.playerBlocks.add(Material.HUGE_MUSHROOM_2.getId()); - this.playerBlocks.add(Material.IRON_FENCE.getId()); - this.playerBlocks.add(Material.THIN_GLASS.getId()); - this.playerBlocks.add(Material.MELON_STEM.getId()); - this.playerBlocks.add(Material.FENCE_GATE.getId()); - this.playerBlocks.add(Material.BRICK_STAIRS.getId()); - this.playerBlocks.add(Material.SMOOTH_STAIRS.getId()); - this.playerBlocks.add(Material.ENCHANTMENT_TABLE.getId()); - this.playerBlocks.add(Material.BREWING_STAND.getId()); - this.playerBlocks.add(Material.CAULDRON.getId()); - this.playerBlocks.add(Material.DIODE_BLOCK_ON.getId()); - this.playerBlocks.add(Material.DIODE_BLOCK_ON.getId()); - this.playerBlocks.add(Material.WEB.getId()); - this.playerBlocks.add(Material.SPONGE.getId()); - this.playerBlocks.add(Material.GRAVEL.getId()); - this.playerBlocks.add(Material.EMERALD_BLOCK.getId()); - this.playerBlocks.add(Material.SANDSTONE.getId()); - - //these are unnatural in the standard world, but not in the nether - if(this.environment != Environment.NETHER) - { - this.playerBlocks.add(Material.NETHERRACK.getId()); - this.playerBlocks.add(Material.SOUL_SAND.getId()); - this.playerBlocks.add(Material.GLOWSTONE.getId()); - this.playerBlocks.add(Material.NETHER_BRICK.getId()); - this.playerBlocks.add(Material.NETHER_FENCE.getId()); - this.playerBlocks.add(Material.NETHER_BRICK_STAIRS.getId()); - } - - //these are unnatural in the standard and nether worlds, but not in the end - if(this.environment != Environment.THE_END) - { - this.playerBlocks.add(Material.OBSIDIAN.getId()); - } - - //these are unnatural in sandy biomes, but not elsewhere - if(this.biome == Biome.DESERT || this.biome == Biome.DESERT_HILLS || this.biome == Biome.BEACH || this.environment != Environment.NORMAL || this.aggressiveMode) - { - this.playerBlocks.add(Material.LEAVES.getId()); - this.playerBlocks.add(Material.LOG.getId()); - } + 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, @@ -199,6 +97,8 @@ class RestoreNatureProcessingTask implements Runnable if(this.aggressiveMode) { + this.playerBlocks.add(Material.LEAVES.getId()); + this.playerBlocks.add(Material.LOG.getId()); this.playerBlocks.add(Material.LEAVES.getId()); this.playerBlocks.add(Material.VINE.getId()); } @@ -215,15 +115,15 @@ class RestoreNatureProcessingTask implements Runnable //remove any blocks which are definitely player placed this.removePlayerBlocks(); - //remove natural blocks which are unnaturally hanging in the air - this.removeHanging(); - //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(); @@ -257,13 +157,13 @@ class RestoreNatureProcessingTask implements Runnable for(int z = 1; z < snapshots[0][0].length - 1; z++) { //replace air, lava, or running water at sea level with stone - if(this.snapshots[x][this.seaLevel - 3][z].typeId == Material.AIR.getId() || this.snapshots[x][this.seaLevel][z].typeId == Material.LAVA.getId() || (this.snapshots[x][this.seaLevel][z].typeId == Material.WATER.getId() || this.snapshots[x][this.seaLevel][z].data != 0)) + if(this.snapshots[x][this.seaLevel - 3][z].typeId == Material.AIR.getId() || this.snapshots[x][this.seaLevel - 3][z].typeId == Material.LAVA.getId() || (this.snapshots[x][this.seaLevel - 3][z].typeId == Material.WATER.getId() || this.snapshots[x][this.seaLevel - 3][z].data != 0)) { this.snapshots[x][this.seaLevel - 3][z].typeId = Material.STONE.getId(); } //do the same for one layer beneath that (because a future restoration step may convert surface stone to sand, which falls down) - if(this.snapshots[x][this.seaLevel - 4][z].typeId == Material.AIR.getId() || this.snapshots[x][this.seaLevel][z].typeId == Material.LAVA.getId() || (this.snapshots[x][this.seaLevel][z].typeId == Material.WATER.getId() || this.snapshots[x][this.seaLevel][z].data != 0)) + if(this.snapshots[x][this.seaLevel - 4][z].typeId == Material.AIR.getId() || this.snapshots[x][this.seaLevel - 4][z].typeId == Material.LAVA.getId() || (this.snapshots[x][this.seaLevel - 4][z].typeId == Material.WATER.getId() || this.snapshots[x][this.seaLevel - 4][z].data != 0)) { this.snapshots[x][this.seaLevel - 4][z].typeId = Material.STONE.getId(); } @@ -696,4 +596,119 @@ class RestoreNatureProcessingTask implements Runnable return y; } + + 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.getId()); + playerBlocks.add(Material.BED_BLOCK.getId()); + playerBlocks.add(Material.WOOD.getId()); + playerBlocks.add(Material.BOOKSHELF.getId()); + playerBlocks.add(Material.BREWING_STAND.getId()); + playerBlocks.add(Material.BRICK.getId()); + playerBlocks.add(Material.COBBLESTONE.getId()); + playerBlocks.add(Material.GLASS.getId()); + playerBlocks.add(Material.LAPIS_BLOCK.getId()); + playerBlocks.add(Material.DISPENSER.getId()); + playerBlocks.add(Material.NOTE_BLOCK.getId()); + playerBlocks.add(Material.POWERED_RAIL.getId()); + playerBlocks.add(Material.DETECTOR_RAIL.getId()); + playerBlocks.add(Material.PISTON_STICKY_BASE.getId()); + playerBlocks.add(Material.PISTON_BASE.getId()); + playerBlocks.add(Material.PISTON_EXTENSION.getId()); + playerBlocks.add(Material.WOOL.getId()); + playerBlocks.add(Material.PISTON_MOVING_PIECE.getId()); + playerBlocks.add(Material.GOLD_BLOCK.getId()); + playerBlocks.add(Material.IRON_BLOCK.getId()); + playerBlocks.add(Material.DOUBLE_STEP.getId()); + playerBlocks.add(Material.STEP.getId()); + playerBlocks.add(Material.CROPS.getId()); + playerBlocks.add(Material.TNT.getId()); + playerBlocks.add(Material.MOSSY_COBBLESTONE.getId()); + playerBlocks.add(Material.TORCH.getId()); + playerBlocks.add(Material.FIRE.getId()); + playerBlocks.add(Material.WOOD_STAIRS.getId()); + playerBlocks.add(Material.CHEST.getId()); + playerBlocks.add(Material.REDSTONE_WIRE.getId()); + playerBlocks.add(Material.DIAMOND_BLOCK.getId()); + playerBlocks.add(Material.WORKBENCH.getId()); + playerBlocks.add(Material.SOIL.getId()); + playerBlocks.add(Material.FURNACE.getId()); + playerBlocks.add(Material.BURNING_FURNACE.getId()); + playerBlocks.add(Material.WOODEN_DOOR.getId()); + playerBlocks.add(Material.SIGN_POST.getId()); + playerBlocks.add(Material.LADDER.getId()); + playerBlocks.add(Material.RAILS.getId()); + playerBlocks.add(Material.COBBLESTONE_STAIRS.getId()); + playerBlocks.add(Material.WALL_SIGN.getId()); + playerBlocks.add(Material.STONE_PLATE.getId()); + playerBlocks.add(Material.LEVER.getId()); + playerBlocks.add(Material.IRON_DOOR_BLOCK.getId()); + playerBlocks.add(Material.WOOD_PLATE.getId()); + playerBlocks.add(Material.REDSTONE_TORCH_ON.getId()); + playerBlocks.add(Material.REDSTONE_TORCH_OFF.getId()); + playerBlocks.add(Material.STONE_BUTTON.getId()); + playerBlocks.add(Material.SNOW_BLOCK.getId()); + playerBlocks.add(Material.JUKEBOX.getId()); + playerBlocks.add(Material.FENCE.getId()); + playerBlocks.add(Material.PORTAL.getId()); + playerBlocks.add(Material.JACK_O_LANTERN.getId()); + playerBlocks.add(Material.CAKE_BLOCK.getId()); + playerBlocks.add(Material.DIODE_BLOCK_ON.getId()); + playerBlocks.add(Material.DIODE_BLOCK_OFF.getId()); + playerBlocks.add(Material.TRAP_DOOR.getId()); + playerBlocks.add(Material.SMOOTH_BRICK.getId()); + playerBlocks.add(Material.HUGE_MUSHROOM_1.getId()); + playerBlocks.add(Material.HUGE_MUSHROOM_2.getId()); + playerBlocks.add(Material.IRON_FENCE.getId()); + playerBlocks.add(Material.THIN_GLASS.getId()); + playerBlocks.add(Material.MELON_STEM.getId()); + playerBlocks.add(Material.FENCE_GATE.getId()); + playerBlocks.add(Material.BRICK_STAIRS.getId()); + playerBlocks.add(Material.SMOOTH_STAIRS.getId()); + playerBlocks.add(Material.ENCHANTMENT_TABLE.getId()); + playerBlocks.add(Material.BREWING_STAND.getId()); + playerBlocks.add(Material.CAULDRON.getId()); + playerBlocks.add(Material.DIODE_BLOCK_ON.getId()); + playerBlocks.add(Material.DIODE_BLOCK_ON.getId()); + playerBlocks.add(Material.WEB.getId()); + playerBlocks.add(Material.SPONGE.getId()); + playerBlocks.add(Material.GRAVEL.getId()); + playerBlocks.add(Material.EMERALD_BLOCK.getId()); + playerBlocks.add(Material.SANDSTONE.getId()); + playerBlocks.add(Material.WOOD_STEP.getId()); + playerBlocks.add(Material.WOOD_DOUBLE_STEP.getId()); + playerBlocks.add(Material.ENDER_CHEST.getId()); + + //these are unnatural in the standard world, but not in the nether + if(environment != Environment.NETHER) + { + playerBlocks.add(Material.NETHERRACK.getId()); + playerBlocks.add(Material.SOUL_SAND.getId()); + playerBlocks.add(Material.GLOWSTONE.getId()); + playerBlocks.add(Material.NETHER_BRICK.getId()); + playerBlocks.add(Material.NETHER_FENCE.getId()); + playerBlocks.add(Material.NETHER_BRICK_STAIRS.getId()); + } + + //these are unnatural in the standard and nether worlds, but not in the end + if(environment != Environment.THE_END) + { + playerBlocks.add(Material.OBSIDIAN.getId()); + playerBlocks.add(Material.ENDER_STONE.getId()); + playerBlocks.add(Material.ENDER_PORTAL_FRAME.getId()); + } + + //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.LEAVES.getId()); + playerBlocks.add(Material.LOG.getId()); + } + + return playerBlocks; + } }