From 008f313d09e3dff0f640b7328e1a9e12bd475d5b Mon Sep 17 00:00:00 2001 From: Ryan Hamshire Date: Wed, 29 Aug 2012 19:24:33 -0700 Subject: [PATCH] 6.3 --- plugin.yml | 2 +- .../GriefPrevention/BlockEventHandler.java | 5 +- .../ryanhamshire/GriefPrevention/Claim.java | 9 +- .../GriefPrevention/DataStore.java | 22 +- .../GriefPrevention/EntityEventHandler.java | 8 + .../GriefPrevention/GriefPrevention.java | 80 ++++++- .../GriefPrevention/Messages.java | 2 +- .../GriefPrevention/PlayerEventHandler.java | 174 +++++++++------ .../RestoreNatureExecutionTask.java | 12 +- .../RestoreNatureProcessingTask.java | 198 ++++++++++++++++-- 10 files changed, 408 insertions(+), 104 deletions(-) diff --git a/plugin.yml b/plugin.yml index bef485c..efdeba7 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.1 +version: 6.3 commands: abandonclaim: description: Deletes a claim. diff --git a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java index 2fc90d2..7dacfa1 100644 --- a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java @@ -352,7 +352,7 @@ public class BlockEventHandler implements Listener } //FEATURE: warn players when they're placing non-trash blocks outside of their claimed areas - else if(GriefPrevention.instance.config_claims_warnOnBuildOutside && !this.trashBlocks.contains(block.getType()) && GriefPrevention.instance.claimsEnabledForWorld(block.getWorld())) + else if(GriefPrevention.instance.config_claims_warnOnBuildOutside && !this.trashBlocks.contains(block.getType()) && GriefPrevention.instance.claimsEnabledForWorld(block.getWorld()) && playerData.claims.size() > 0) { if(--playerData.unclaimedBlockPlacementsUntilWarning <= 0) { @@ -601,7 +601,8 @@ public class BlockEventHandler implements Listener Claim toClaim = this.dataStore.getClaimAt(toBlock.getLocation(), false, fromClaim); //into wilderness is NOT OK when surface buckets are limited - if(GriefPrevention.instance.config_blockWildernessWaterBuckets && toClaim == null) + Material materialDispensed = dispenseEvent.getItem().getType(); + if((materialDispensed == Material.WATER_BUCKET || materialDispensed == Material.LAVA_BUCKET) && GriefPrevention.instance.config_blockWildernessWaterBuckets && toClaim == null) { dispenseEvent.setCancelled(true); return; diff --git a/src/me/ryanhamshire/GriefPrevention/Claim.java b/src/me/ryanhamshire/GriefPrevention/Claim.java index dc4505a..8468266 100644 --- a/src/me/ryanhamshire/GriefPrevention/Claim.java +++ b/src/me/ryanhamshire/GriefPrevention/Claim.java @@ -121,6 +121,9 @@ public class Claim //don't do it for very large claims if(this.getArea() > 10000) return; + //don't do it when surface fluids are allowed to be dumped + if(!GriefPrevention.instance.config_blockWildernessWaterBuckets) return; + Location lesser = this.getLesserBoundaryCorner(); Location greater = this.getGreaterBoundaryCorner(); @@ -430,9 +433,6 @@ public class Claim //access permission check public String allowAccess(Player player) { - //everyone always has access to admin claims - if(this.isAdminClaim()) return null; - //following a siege where the defender lost, the claim will allow everyone access for a time if(this.doorsOpen) return null; @@ -471,9 +471,6 @@ public class Claim return GriefPrevention.instance.dataStore.getMessage(Messages.NoContainersSiege, siegeData.attacker.getName()); } - //containers are always accessible in admin claims - if(this.isAdminClaim()) return null; - //owner and administrators in ignoreclaims mode have access if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null; diff --git a/src/me/ryanhamshire/GriefPrevention/DataStore.java b/src/me/ryanhamshire/GriefPrevention/DataStore.java index 1867341..415ff30 100644 --- a/src/me/ryanhamshire/GriefPrevention/DataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DataStore.java @@ -100,6 +100,13 @@ public abstract class DataStore { 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."); } } @@ -776,8 +783,16 @@ public abstract class DataStore //delete them one by one for(int i = 0; i < claimsToDelete.size(); i++) { - claimsToDelete.get(i).removeSurfaceFluids(null); - this.deleteClaim(claimsToDelete.get(i)); + Claim claim = claimsToDelete.get(i); + 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); + } } } @@ -996,7 +1011,8 @@ public abstract class DataStore 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 undo your work here! Consider claiming this area to protect your work.", null); 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.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); //load the config file FileConfiguration config = YamlConfiguration.loadConfiguration(new File(messagesFilePath)); diff --git a/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java b/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java index 36bf02a..18b83a9 100644 --- a/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java @@ -41,6 +41,7 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; +import org.bukkit.event.entity.EntityBreakDoorEvent; import org.bukkit.event.entity.EntityChangeBlockEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent; @@ -75,6 +76,13 @@ class EntityEventHandler implements Listener } } + //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) diff --git a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java index f476543..ae01161 100644 --- a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java +++ b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java @@ -80,6 +80,7 @@ public class GriefPrevention extends JavaPlugin public boolean config_claims_creationRequiresPermission; //whether creating claims with the shovel requires a permission public int config_claims_claimsExtendIntoGroundDistance; //how far below the shoveled block a new claim will reach public int config_claims_minSize; //minimum width and height for non-admin claims + public boolean config_claims_allowUnclaimInCreative; //whether players may unclaim land (resize or abandon) in creative mode public boolean config_claims_noBuildOutsideClaims; //whether players can build in survival worlds outside their claimed areas @@ -127,6 +128,7 @@ public class GriefPrevention extends JavaPlugin public boolean config_endermenMoveBlocks; //whether or not endermen may move blocks around public boolean config_creaturesTrampleCrops; //whether or not non-player entities may trample crops + public boolean config_zombiesBreakDoors; //whether or not hard-mode zombies may break down wooden doors public List config_mods_accessTrustIds; //list of block IDs which should require /accesstrust for player interaction public List config_mods_containerTrustIds; //list of block IDs which should require /containertrust for player interaction @@ -245,7 +247,8 @@ public class GriefPrevention extends JavaPlugin this.config_claims_expirationDays = config.getInt("GriefPrevention.Claims.IdleLimitDays", 0); this.config_claims_trappedCooldownHours = config.getInt("GriefPrevention.Claims.TrappedCommandCooldownHours", 8); this.config_claims_noBuildOutsideClaims = config.getBoolean("GriefPrevention.Claims.NoSurvivalBuildingOutsideClaims", false); - this.config_claims_warnOnBuildOutside = config.getBoolean("GriefPrevention.Claims.WarnWhenBuildingOutsideClaims"); + this.config_claims_warnOnBuildOutside = config.getBoolean("GriefPrevention.Claims.WarnWhenBuildingOutsideClaims", true); + this.config_claims_allowUnclaimInCreative = config.getBoolean("GriefPrevention.Claims.AllowUnclaimingCreativeModeLand", true); this.config_spam_enabled = config.getBoolean("GriefPrevention.Spam.Enabled", true); this.config_spam_loginCooldownMinutes = config.getInt("GriefPrevention.Spam.LoginCooldownMinutes", 2); @@ -283,6 +286,7 @@ public class GriefPrevention extends JavaPlugin this.config_endermenMoveBlocks = config.getBoolean("GriefPrevention.EndermenMoveBlocks", false); this.config_creaturesTrampleCrops = config.getBoolean("GriefPrevention.CreaturesTrampleCrops", false); + this.config_zombiesBreakDoors = config.getBoolean("GriefPrevention.HardModeZombiesBreakDoors", false); this.config_mods_accessTrustIds = config.getIntegerList("GriefPrevention.Mods.BlockIdsRequiringAccessTrust"); if(this.config_mods_accessTrustIds == null) this.config_mods_accessTrustIds = new ArrayList(); @@ -423,6 +427,7 @@ public class GriefPrevention extends JavaPlugin config.set("GriefPrevention.Claims.ModificationTool", this.config_claims_modificationTool.name()); config.set("GriefPrevention.Claims.NoSurvivalBuildingOutsideClaims", this.config_claims_noBuildOutsideClaims); config.set("GriefPrevention.Claims.WarnWhenBuildingOutsideClaims", this.config_claims_warnOnBuildOutside); + config.set("GriefPrevention.Claims.AllowUnclaimingCreativeModeLand", this.config_claims_allowUnclaimInCreative); config.set("GriefPrevention.Spam.Enabled", this.config_spam_enabled); config.set("GriefPrevention.Spam.LoginCooldownMinutes", this.config_spam_loginCooldownMinutes); @@ -463,6 +468,7 @@ public class GriefPrevention extends JavaPlugin config.set("GriefPrevention.EndermenMoveBlocks", this.config_endermenMoveBlocks); config.set("GriefPrevention.CreaturesTrampleCrops", this.config_creaturesTrampleCrops); + config.set("GriefPrevention.HardModeZombiesBreakDoors", this.config_zombiesBreakDoors); config.set("GriefPrevention.Database.URL", databaseUrl); config.set("GriefPrevention.Database.UserName", databaseUserName); @@ -657,7 +663,7 @@ public class GriefPrevention extends JavaPlugin { if(args.length != 0) return false; - if(creativeRulesApply(player.getLocation())) + if(!GriefPrevention.instance.config_claims_allowUnclaimInCreative && creativeRulesApply(player.getLocation())) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoCreativeUnClaim); return true; @@ -1235,6 +1241,13 @@ public class GriefPrevention extends JavaPlugin { claim.removeSurfaceFluids(null); this.dataStore.deleteClaim(claim); + + //if in a creative mode world, delete the claim + if(GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner())) + { + 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())); @@ -1633,7 +1646,7 @@ public class GriefPrevention extends JavaPlugin } //don't allow abandon of creative mode claims - else if(this.creativeRulesApply(player.getLocation())) + else if(!GriefPrevention.instance.config_claims_allowUnclaimInCreative && this.creativeRulesApply(player.getLocation())) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoCreativeUnClaim); } @@ -1658,6 +1671,14 @@ public class GriefPrevention extends JavaPlugin claim.removeSurfaceFluids(null); this.dataStore.deleteClaim(claim); + //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); + } + //tell the player how many claim blocks he has left int remainingBlocks = playerData.getRemainingClaimBlocks(); GriefPrevention.sendMessage(player, TextMode.Success, Messages.AbandonSuccess, String.valueOf(remainingBlocks)); @@ -1694,7 +1715,7 @@ public class GriefPrevention extends JavaPlugin else { otherPlayer = this.resolvePlayer(recipientName); - if(otherPlayer == null && !recipientName.equals("public")) + if(otherPlayer == null && !recipientName.equals("public") && !recipientName.equals("all")) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound); return; @@ -2268,4 +2289,55 @@ public class GriefPrevention extends JavaPlugin return claim.allowBreak(player, location.getBlock().getType()); } } + + //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; + + Chunk lesserChunk = claim.getLesserBoundaryCorner().getChunk(); + Chunk greaterChunk = claim.getGreaterBoundaryCorner().getChunk(); + + for(int x = lesserChunk.getX(); x <= greaterChunk.getX(); x++) + for(int z = lesserChunk.getZ(); z <= greaterChunk.getZ(); z++) + { + Chunk chunk = lesserChunk.getWorld().getChunkAt(x, z); + this.restoreChunk(chunk, chunk.getWorld().getSeaLevel() - 15, false, delayInTicks, null); + } + } + + 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.getTypeId(), block.getData()); + } + } + } + + //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(), chunk.getWorld().getBiome(lesserBoundaryCorner.getBlockX(), lesserBoundaryCorner.getBlockZ()), 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 64651e2..6dfcadc 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 + 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 } diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java index 1dec4a8..b097b1d 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java @@ -807,10 +807,11 @@ class PlayerEventHandler implements Listener } //if the bucket is being used in a claim, allow for dumping lava closer to other players - Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, null); + PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, playerData.lastClaim); if(claim != null) { - minLavaDistance = 3; + minLavaDistance = 3; } //otherwise no wilderness dumping (unless underground) in worlds where claims are enabled @@ -900,6 +901,25 @@ class PlayerEventHandler implements Listener Material clickedBlockType = clickedBlock.getType(); + //apply rules for putting out fires (requires build permission) + PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + if(event.getClickedBlock() != null && event.getClickedBlock().getRelative(event.getBlockFace()).getType() == Material.FIRE) + { + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); + if(claim != null) + { + playerData.lastClaim = claim; + + String noBuildReason = claim.allowBuild(player); + if(noBuildReason != null) + { + event.setCancelled(true); + GriefPrevention.sendMessage(player, TextMode.Err, noBuildReason); + return; + } + } + } + //apply rules for containers and crafting blocks if( GriefPrevention.instance.config_claims_preventTheft && ( event.getAction() == Action.RIGHT_CLICK_BLOCK && ( @@ -913,7 +933,6 @@ class PlayerEventHandler implements Listener GriefPrevention.instance.config_mods_containerTrustIds.contains(clickedBlock.getTypeId())))) { //block container use while under siege, so players can't hide items from attackers - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); if(playerData.siegeData != null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeNoContainers); @@ -930,9 +949,11 @@ class PlayerEventHandler implements Listener } //otherwise check permissions for the claim the player is in - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, null); + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); if(claim != null) { + playerData.lastClaim = claim; + String noContainersReason = claim.allowContainers(player); if(noContainersReason != null) { @@ -956,9 +977,11 @@ class PlayerEventHandler implements Listener (GriefPrevention.instance.config_claims_lockTrapDoors && clickedBlockType == Material.TRAP_DOOR) || (GriefPrevention.instance.config_claims_lockFenceGates && clickedBlockType == Material.FENCE_GATE)) { - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, null); + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); if(claim != null) { + playerData.lastClaim = claim; + String noAccessReason = claim.allowAccess(player); if(noAccessReason != null) { @@ -972,9 +995,11 @@ class PlayerEventHandler implements Listener //otherwise apply rules for buttons and switches else if(GriefPrevention.instance.config_claims_preventButtonsSwitches && (clickedBlockType == null || clickedBlockType == Material.STONE_BUTTON || clickedBlockType == Material.LEVER || GriefPrevention.instance.config_mods_accessTrustIds.contains(clickedBlock.getTypeId()))) { - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, null); + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); if(claim != null) { + playerData.lastClaim = claim; + String noAccessReason = claim.allowAccess(player); if(noAccessReason != null) { @@ -996,7 +1021,7 @@ class PlayerEventHandler implements Listener //apply rule for note blocks and repeaters else if(clickedBlockType == Material.NOTE_BLOCK || clickedBlockType == Material.DIODE_BLOCK_ON || clickedBlockType == Material.DIODE_BLOCK_OFF) { - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, null); + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); if(claim != null) { String noBuildReason = claim.allowBuild(player); @@ -1045,7 +1070,6 @@ class PlayerEventHandler implements Listener } //enforce limit on total number of entities in this claim - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); if(claim == null) return; @@ -1070,7 +1094,7 @@ class PlayerEventHandler implements Listener return; } - Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false /*ignore height*/, null); + Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false /*ignore height*/, playerData.lastClaim); //no claim case if(claim == null) @@ -1082,6 +1106,7 @@ class PlayerEventHandler implements Listener //claim case else { + playerData.lastClaim = claim; GriefPrevention.sendMessage(player, TextMode.Info, Messages.BlockClaimed, claim.getOwnerName()); //visualize boundary @@ -1116,8 +1141,6 @@ class PlayerEventHandler implements Listener //if it's a golden shovel else if(materialInHand != GriefPrevention.instance.config_claims_modificationTool) return; - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); - //disable golden shovel while under siege if(playerData.siegeData != null) { @@ -1152,24 +1175,7 @@ class PlayerEventHandler implements Listener //figure out which chunk to repair Chunk chunk = player.getWorld().getChunkAt(clickedBlock.getLocation()); - //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.getTypeId(), block.getData()); - } - } - } - - //create task to process those data in another thread + //start the repair process //set boundaries for processing int miny = clickedBlock.getY(); @@ -1183,13 +1189,7 @@ class PlayerEventHandler implements Listener } } - 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(), chunk.getWorld().getBiome(lesserBoundaryCorner.getBlockX(), lesserBoundaryCorner.getBlockZ()), lesserBoundaryCorner, greaterBoundaryCorner, chunk.getWorld().getSeaLevel(), playerData.shovelMode == ShovelMode.RestoreNatureAggressive, player); - GriefPrevention.instance.getServer().getScheduler().scheduleAsyncDelayedTask(GriefPrevention.instance, task); + GriefPrevention.instance.restoreChunk(chunk, miny, playerData.shovelMode == ShovelMode.RestoreNatureAggressive, 0, player); return; } @@ -1209,11 +1209,11 @@ class PlayerEventHandler implements Listener } else { + allowedFillBlocks.add(Material.GRASS); + allowedFillBlocks.add(Material.DIRT); allowedFillBlocks.add(Material.STONE); allowedFillBlocks.add(Material.SAND); allowedFillBlocks.add(Material.SANDSTONE); - allowedFillBlocks.add(Material.DIRT); - allowedFillBlocks.add(Material.GRASS); allowedFillBlocks.add(Material.ICE); } @@ -1236,6 +1236,29 @@ class PlayerEventHandler implements Listener 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); + + //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 || centerBlock.getType() == Material.STATIONARY_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++) { @@ -1252,41 +1275,43 @@ class PlayerEventHandler implements Listener //only replace air, spilling water, snow, long grass if(block.getType() == Material.AIR || block.getType() == Material.SNOW || (block.getType() == Material.STATIONARY_WATER && block.getData() != 0) || block.getType() == Material.LONG_GRASS) { - //look to neighbors for an appropriate fill block - Block eastBlock = block.getRelative(BlockFace.EAST); - Block westBlock = block.getRelative(BlockFace.WEST); - Block northBlock = block.getRelative(BlockFace.NORTH); - Block southBlock = block.getRelative(BlockFace.SOUTH); - Block underBlock = block.getRelative(BlockFace.DOWN); - - //first, check lateral neighbors (ideally, want to keep natural layers) - if(allowedFillBlocks.contains(eastBlock.getType())) + //if the top level, always use the default filler picked above + if(y == maxHeight) { - 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()); + block.setType(defaultFiller); } - //then check underneath - else if(allowedFillBlocks.contains(underBlock.getType())) - { - block.setType(underBlock.getType()); - } - - //if all else fails, use the first material listed in the acceptable fill blocks above + //otherwise look to neighbors for an appropriate fill block else { - block.setType(allowedFillBlocks.get(0)); + 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()); + } + + //if all else fails, use the default filler selected above + else + { + block.setType(defaultFiller); + } } } } @@ -1380,6 +1405,7 @@ class PlayerEventHandler implements Listener //rule1: in creative mode, top-level claims can't be moved or resized smaller. //rule2: in any mode, shrinking a claim removes any surface fluids Claim oldClaim = playerData.claimResizing; + boolean smaller = false; if(oldClaim.parent == null) { //temporary claim instance, just for checking contains() @@ -1391,8 +1417,10 @@ class PlayerEventHandler implements Listener //if the new claim is smaller if(!newClaim.contains(oldClaim.getLesserBoundaryCorner(), true, false) || !newClaim.contains(oldClaim.getGreaterBoundaryCorner(), true, false)) { + smaller = true; + //enforce creative mode rule - if(!player.hasPermission("griefprevention.deleteclaims") && GriefPrevention.instance.creativeRulesApply(player.getLocation())) + if(!GriefPrevention.instance.config_claims_allowUnclaimInCreative && !player.hasPermission("griefprevention.deleteclaims") && GriefPrevention.instance.creativeRulesApply(player.getLocation())) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoCreativeUnClaim); return; @@ -1419,6 +1447,14 @@ class PlayerEventHandler implements Listener GriefPrevention.AddLogEntry(playerName + " resized " + playerData.claimResizing.getOwnerName() + "'s claim at " + GriefPrevention.getfriendlyLocationString(playerData.claimResizing.lesserBoundaryCorner) + "."); } + //if in a creative mode world and shrinking an existing claim, restore any unclaimed area + 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; diff --git a/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java b/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java index 17237be..a8216ac 100644 --- a/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java +++ b/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java @@ -23,6 +23,7 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; +import org.bukkit.entity.Animals; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; @@ -88,7 +89,7 @@ class RestoreNatureExecutionTask implements Runnable for(int i = 0; i < entities.length; i++) { Entity entity = entities[i]; - if(!(entity instanceof Player)) + if(!(entity instanceof Player || entity instanceof Animals)) { entity.remove(); } @@ -101,8 +102,11 @@ class RestoreNatureExecutionTask implements Runnable } //show visualization to player - Claim claim = new Claim(lesserCorner, greaterCorner, "", new String[] {}, new String[] {}, new String[] {}, new String[] {}, null); - Visualization visualization = Visualization.FromClaim(claim, player.getLocation().getBlockY(), VisualizationType.RestoreNature, player.getLocation()); - Visualization.Apply(player, visualization); + if(player != null) + { + Claim claim = new Claim(lesserCorner, greaterCorner, "", new String[] {}, new String[] {}, new String[] {}, new String[] {}, null); + Visualization visualization = Visualization.FromClaim(claim, player.getLocation().getBlockY(), VisualizationType.RestoreNature, player.getLocation()); + Visualization.Apply(player, visualization); + } } } diff --git a/src/me/ryanhamshire/GriefPrevention/RestoreNatureProcessingTask.java b/src/me/ryanhamshire/GriefPrevention/RestoreNatureProcessingTask.java index 529d48a..a2dee81 100644 --- a/src/me/ryanhamshire/GriefPrevention/RestoreNatureProcessingTask.java +++ b/src/me/ryanhamshire/GriefPrevention/RestoreNatureProcessingTask.java @@ -42,6 +42,7 @@ class RestoreNatureProcessingTask implements Runnable 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; @@ -49,7 +50,7 @@ class RestoreNatureProcessingTask implements Runnable 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 - public RestoreNatureProcessingTask(BlockSnapshot[][][] snapshots, int miny, Environment environment, Biome biome, Location lesserBoundaryCorner, Location greaterBoundaryCorner, int seaLevel, boolean aggressiveMode, Player player) + 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; @@ -60,6 +61,7 @@ class RestoreNatureProcessingTask implements Runnable this.seaLevel = seaLevel; this.aggressiveMode = aggressiveMode; this.player = player; + this.creativeMode = creativeMode; this.notAllowedToHang = new ArrayList(); this.notAllowedToHang.add(Material.DIRT.getId()); @@ -150,6 +152,9 @@ class RestoreNatureProcessingTask implements Runnable 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) @@ -169,25 +174,33 @@ class RestoreNatureProcessingTask implements Runnable } //these are unnatural in sandy biomes, but not elsewhere - if(this.biome == Biome.DESERT || this.biome == Biome.DESERT_HILLS || this.biome == Biome.BEACH || this.aggressiveMode) + 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()); } - //in aggressive mode, also treat these blocks as user placed, to be removed + //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 logs - if(this.aggressiveMode) + //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.getId()); - this.playerBlocks.add(Material.PUMPKIN.getId()); - this.playerBlocks.add(Material.PUMPKIN_STEM.getId()); + this.playerBlocks.add(Material.IRON_ORE.getId()); + this.playerBlocks.add(Material.GOLD_ORE.getId()); + this.playerBlocks.add(Material.DIAMOND_ORE.getId()); this.playerBlocks.add(Material.MELON_BLOCK.getId()); this.playerBlocks.add(Material.MELON_STEM.getId()); this.playerBlocks.add(Material.BEDROCK.getId()); - this.playerBlocks.add(Material.GRAVEL.getId()); - this.playerBlocks.add(Material.SANDSTONE.getId()); + this.playerBlocks.add(Material.COAL_ORE.getId()); + this.playerBlocks.add(Material.PUMPKIN.getId()); + this.playerBlocks.add(Material.PUMPKIN_STEM.getId()); + this.playerBlocks.add(Material.MELON.getId()); + } + + if(this.aggressiveMode) + { + this.playerBlocks.add(Material.LEAVES.getId()); + this.playerBlocks.add(Material.VINE.getId()); } } @@ -196,18 +209,24 @@ class RestoreNatureProcessingTask implements Runnable { //order is important! + //remove sandstone which appears to be unnatural + this.removeSandstone(); + //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 stacked high this.removeWallsAndTowers(); - //cover surface stone and gravel with sand or grass, as the biome requires - this.coverSurfaceStone(); - //fill unnatural thin trenches and single-block potholes this.fillHolesAndTrenches(); @@ -217,11 +236,161 @@ class RestoreNatureProcessingTask implements Runnable //remove water/lava above sea level this.removeDumpedFluids(); + //cover over any gaping holes in creative mode worlds + if(this.creativeMode && this.environment == Environment.NORMAL) + { + this.fillBigHoles(); + } + + //cover surface stone and gravel with sand or grass, as the biome requires + this.coverSurfaceStone(); + //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); } + private void fillBigHoles() + { + for(int x = 1; x < snapshots.length - 1; x++) + { + 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)) + { + 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)) + { + this.snapshots[x][this.seaLevel - 4][z].typeId = Material.STONE.getId(); + } + } + } + } + + //converts sandstone adjacent to sand to sand, and any other sandstone to air + 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.getId()) 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.getId() && underBlock.typeId == Material.AIR.getId()) continue; + + //count adjacent non-air/non-leaf blocks + if( leftBlock.typeId == Material.SAND.getId() || + rightBlock.typeId == Material.SAND.getId() || + upBlock.typeId == Material.SAND.getId() || + downBlock.typeId == Material.SAND.getId() || + aboveBlock.typeId == Material.SAND.getId() || + underBlock.typeId == Material.SAND.getId()) + { + snapshots[x][y][z].typeId = Material.SAND.getId(); + } + else + { + snapshots[x][y][z].typeId = Material.AIR.getId(); + } + } + } + } + } + + private void reduceStone() + { + 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 - 2 && (this.snapshots[x][thisy][z].typeId == Material.STONE.getId() || this.snapshots[x][thisy][z].typeId == Material.SANDSTONE.getId())) + { + 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.getId() && leftBlock.typeId != Material.LEAVES.getId() && leftBlock.typeId != Material.VINE.getId()) + { + adjacentBlockCount++; + } + if(rightBlock.typeId != Material.AIR.getId() && rightBlock.typeId != Material.LEAVES.getId() && rightBlock.typeId != Material.VINE.getId()) + { + adjacentBlockCount++; + } + if(downBlock.typeId != Material.AIR.getId() && downBlock.typeId != Material.LEAVES.getId() && downBlock.typeId != Material.VINE.getId()) + { + adjacentBlockCount++; + } + if(upBlock.typeId != Material.AIR.getId() && upBlock.typeId != Material.LEAVES.getId() && upBlock.typeId != Material.VINE.getId()) + { + adjacentBlockCount++; + } + + if(adjacentBlockCount < 3) + { + this.snapshots[x][thisy][z].typeId = Material.AIR.getId(); + } + + thisy--; + } + } + } + } + + private void reduceLogs() + { + 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 - 2; y < snapshots[0].length; y++) + { + BlockSnapshot block = snapshots[x][y][z]; + + //skip non-logs + if(block.typeId != Material.LOG.getId()) continue; + + //if in jungle biome, skip jungle logs + if(jungleBiome && block.data == 3) 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(leftBlock.typeId == Material.LOG.getId() || rightBlock.typeId == Material.LOG.getId() || upBlock.typeId == Material.LOG.getId() || downBlock.typeId == Material.LOG.getId()) + { + this.snapshots[x][y][z].typeId = Material.AIR.getId(); + } + } + } + } + } + private void removePlayerBlocks() { int miny = this.miny; @@ -332,7 +501,7 @@ class RestoreNatureProcessingTask implements Runnable int y = this.highestY(x, z, true); BlockSnapshot block = snapshots[x][y][z]; - if(block.typeId == Material.STONE.getId() || block.typeId == Material.GRAVEL.getId() || block.typeId == Material.DIRT.getId()) + if(block.typeId == Material.STONE.getId() || block.typeId == Material.GRAVEL.getId() || block.typeId == Material.DIRT.getId() || block.typeId == Material.SANDSTONE.getId()) { if(this.biome == Biome.DESERT || this.biome == Biome.DESERT_HILLS || this.biome == Biome.BEACH) { @@ -516,6 +685,7 @@ class RestoreNatureProcessingTask implements Runnable { BlockSnapshot block = this.snapshots[x][y][z]; if(block.typeId != Material.AIR.getId() && + !(ignoreLeaves && block.typeId == Material.SNOW.getId()) && !(ignoreLeaves && block.typeId == Material.LEAVES.getId()) && !(block.typeId == Material.STATIONARY_WATER.getId() && block.data != 0) && !(block.typeId == Material.STATIONARY_LAVA.getId() && block.data != 0))