diff --git a/plugin.yml b/plugin.yml index 42a6ab1..13cc197 100644 --- a/plugin.yml +++ b/plugin.yml @@ -1,7 +1,8 @@ name: GriefPrevention main: me.ryanhamshire.GriefPrevention.GriefPrevention softdepend: [Vault, Multiverse-Core, My Worlds] -version: 5.5 +dev-url: http://dev.bukkit.org/server-mods/grief-prevention +version: 5.9 commands: abandonclaim: description: Deletes a claim. @@ -124,7 +125,7 @@ commands: claimslist: description: Lists information about a player's claim blocks and claims. usage: /ClaimsList - permission: griefprevention.adjustbonusclaimblocks + permission: griefprevention.adjustclaimblocks permissions: griefprevention.createclaims: description: Grants permission to create claims. diff --git a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java index af80252..4c02cc4 100644 --- a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java @@ -18,6 +18,7 @@ package me.ryanhamshire.GriefPrevention; +import java.util.ArrayList; import java.util.List; import org.bukkit.GameMode; @@ -35,6 +36,7 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockBurnEvent; import org.bukkit.event.block.BlockDamageEvent; +import org.bukkit.event.block.BlockDispenseEvent; import org.bukkit.event.block.BlockFromToEvent; import org.bukkit.event.block.BlockIgniteEvent; import org.bukkit.event.block.BlockIgniteEvent.IgniteCause; @@ -47,6 +49,7 @@ import org.bukkit.event.world.StructureGrowEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; +import org.bukkit.util.Vector; //event handlers related to blocks public class BlockEventHandler implements Listener @@ -54,10 +57,23 @@ public class BlockEventHandler implements Listener //convenience reference to singleton datastore private DataStore dataStore; - //boring typical constructor + private ArrayList trashBlocks; + + //constructor public BlockEventHandler(DataStore dataStore) { this.dataStore = dataStore; + + //create the list of blocks which will not trigger a warning when they're placed outside of land claims + this.trashBlocks = new ArrayList(); + this.trashBlocks.add(Material.COBBLESTONE); + this.trashBlocks.add(Material.TORCH); + this.trashBlocks.add(Material.DIRT); + this.trashBlocks.add(Material.SAPLING); + this.trashBlocks.add(Material.GRAVEL); + this.trashBlocks.add(Material.SAND); + this.trashBlocks.add(Material.TNT); + this.trashBlocks.add(Material.WORKBENCH); } //when a block is damaged... @@ -251,6 +267,9 @@ public class BlockEventHandler implements Listener //extend the claim downward this.dataStore.extendClaim(claim, claim.getLesserBoundaryCorner().getBlockY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance); } + + //reset the counter for warning the player when he places outside his claims + playerData.unclaimedBlockPlacementsUntilWarning = 1; } //FEATURE: automatically create a claim when a player who has no claims places a chest @@ -330,7 +349,23 @@ public class BlockEventHandler implements Listener placeEvent.setCancelled(true); } } - } + } + + //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())) + { + if(--playerData.unclaimedBlockPlacementsUntilWarning <= 0) + { + GriefPrevention.sendMessage(player, TextMode.Warn, Messages.BuildingOutsideClaims); + playerData.unclaimedBlockPlacementsUntilWarning = 15; + + if(playerData.lastClaim != null && playerData.lastClaim.allowBuild(player) == null) + { + Visualization visualization = Visualization.FromClaim(playerData.lastClaim, block.getY(), VisualizationType.Claim, player.getLocation()); + Visualization.Apply(player, visualization); + } + } + } } //blocks "pushing" other players' blocks around (pistons) @@ -339,8 +374,21 @@ public class BlockEventHandler implements Listener { List blocks = event.getBlocks(); - //if no blocks moving, then we don't care - if(blocks.size() == 0) return; + //if no blocks moving, then only check to make sure we're not pushing into a claim from outside + //this avoids pistons breaking non-solids just inside a claim, like torches, doors, and touchplates + if(blocks.size() == 0) + { + Block pistonBlock = event.getBlock(); + Block invadedBlock = pistonBlock.getRelative(event.getDirection()); + + if( this.dataStore.getClaimAt(pistonBlock.getLocation(), false, null) == null && + this.dataStore.getClaimAt(invadedBlock.getLocation(), false, null) != null) + { + event.setCancelled(true); + } + + return; + } //who owns the piston, if anyone? String pistonClaimOwnerName = "_"; @@ -525,6 +573,50 @@ public class BlockEventHandler implements Listener } } + //ensures dispensers can't be used to dispense a block(like water or lava) or item across a claim boundary + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onDispense(BlockDispenseEvent dispenseEvent) + { + //from where? + Block fromBlock = dispenseEvent.getBlock(); + + //to where? + Vector velocity = dispenseEvent.getVelocity(); + int xChange = 0; + int zChange = 0; + if(Math.abs(velocity.getX()) > Math.abs(velocity.getZ())) + { + if(velocity.getX() > 0) xChange = 1; + else xChange = -1; + } + else + { + if(velocity.getZ() > 0) zChange = 1; + else zChange = -1; + } + + Block toBlock = fromBlock.getRelative(xChange, 0, zChange); + + Claim fromClaim = this.dataStore.getClaimAt(fromBlock.getLocation(), false, null); + 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) + { + dispenseEvent.setCancelled(true); + return; + } + + //wilderness to wilderness is OK + if(fromClaim == null && toClaim == null) return; + + //within claim is OK + if(fromClaim == toClaim) return; + + //everything else is NOT OK + dispenseEvent.setCancelled(true); + } + @EventHandler(ignoreCancelled = true) public void onTreeGrow (StructureGrowEvent growEvent) { diff --git a/src/me/ryanhamshire/GriefPrevention/DataStore.java b/src/me/ryanhamshire/GriefPrevention/DataStore.java index 1e019a3..7328400 100644 --- a/src/me/ryanhamshire/GriefPrevention/DataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DataStore.java @@ -128,13 +128,13 @@ public abstract class DataStore } //removes cached player data from memory - void clearCachedPlayerData(String playerName) + synchronized void clearCachedPlayerData(String playerName) { this.playerNameToPlayerDataMap.remove(playerName); } //gets the number of bonus blocks a player has from his permissions - int getGroupBonusBlocks(String playerName) + synchronized int getGroupBonusBlocks(String playerName) { int bonusBlocks = 0; Set keys = permissionToBonusBlocksMap.keySet(); @@ -153,7 +153,7 @@ public abstract class DataStore } //grants a group (players with a specific permission) bonus claim blocks as long as they're still members of the group - public int adjustGroupBonusBlocks(String groupName, int amount) + synchronized public int adjustGroupBonusBlocks(String groupName, int amount) { Integer currentValue = this.permissionToBonusBlocksMap.get(groupName); if(currentValue == null) currentValue = 0; @@ -169,7 +169,7 @@ public abstract class DataStore abstract void saveGroupBonusBlocks(String groupName, int amount); - public void changeClaimOwner(Claim claim, String newOwnerName) throws Exception + synchronized public void changeClaimOwner(Claim claim, String newOwnerName) throws Exception { //if it's a subdivision, throw an exception if(claim.parent != null) @@ -207,7 +207,7 @@ public abstract class DataStore } //adds a claim to the datastore, making it an effective claim - void addClaim(Claim newClaim) + synchronized void addClaim(Claim newClaim) { //subdivisions are easy if(newClaim.parent != null) @@ -287,7 +287,7 @@ public abstract class DataStore } //saves any changes to a claim to secondary storage - public void saveClaim(Claim claim) + synchronized public void saveClaim(Claim claim) { //subdivisions don't save to their own files, but instead live in their parent claim's file //so any attempt to save a subdivision will save its parent (and thus the subdivision) @@ -314,7 +314,7 @@ public abstract class DataStore //retrieves player data from memory or secondary storage, as necessary //if the player has never been on the server before, this will return a fresh player data with default values - public PlayerData getPlayerData(String playerName) + synchronized public PlayerData getPlayerData(String playerName) { //first, look in memory PlayerData playerData = this.playerNameToPlayerDataMap.get(playerName); @@ -346,7 +346,7 @@ public abstract class DataStore abstract PlayerData getPlayerDataFromStorage(String playerName); //deletes a claim or subdivision - public void deleteClaim(Claim claim) + synchronized public void deleteClaim(Claim claim) { //subdivisions are simple - just remove them from their parent claim and save that claim if(claim.parent != null) @@ -396,7 +396,7 @@ public abstract class DataStore //gets the claim at a specific location //ignoreHeight = TRUE means that a location UNDER an existing claim will return the claim //cachedClaim can be NULL, but will help performance if you have a reasonable guess about which claim the location is in - public Claim getClaimAt(Location location, boolean ignoreHeight, Claim cachedClaim) + synchronized public Claim getClaimAt(Location location, boolean ignoreHeight, Claim cachedClaim) { //check cachedClaim guess first. if it's in the datastore and the location is inside it, we're done if(cachedClaim != null && cachedClaim.inDataStore && cachedClaim.contains(location, ignoreHeight, true)) return cachedClaim; @@ -443,7 +443,7 @@ public abstract class DataStore //does NOT check a player has permission to create a claim, or enough claim blocks. //does NOT check minimum claim size constraints //does NOT visualize the new claim for any players - public CreateClaimResult createClaim(World world, int x1, int x2, int y1, int y2, int z1, int z2, String ownerName, Claim parent, Long id) + synchronized public CreateClaimResult createClaim(World world, int x1, int x2, int y1, int y2, int z1, int z2, String ownerName, Claim parent, Long id) { CreateClaimResult result = new CreateClaimResult(); @@ -541,7 +541,7 @@ public abstract class DataStore //extends a claim to a new depth //respects the max depth config variable - public void extendClaim(Claim claim, int newDepth) + synchronized public void extendClaim(Claim claim, int newDepth) { if(newDepth < GriefPrevention.instance.config_claims_maxDepth) newDepth = GriefPrevention.instance.config_claims_maxDepth; @@ -567,7 +567,7 @@ public abstract class DataStore //starts a siege on a claim //does NOT check siege cooldowns, see onCooldown() below - public void startSiege(Player attacker, Player defender, Claim defenderClaim) + synchronized public void startSiege(Player attacker, Player defender, Claim defenderClaim) { //fill-in the necessary SiegeData instance SiegeData siegeData = new SiegeData(attacker, defender, defenderClaim); @@ -586,7 +586,7 @@ public abstract class DataStore //ends a siege //either winnerName or loserName can be null, but not both - public void endSiege(SiegeData siegeData, String winnerName, String loserName, boolean death) + synchronized public void endSiege(SiegeData siegeData, String winnerName, String loserName, boolean death) { boolean grantAccess = false; @@ -704,7 +704,7 @@ public abstract class DataStore private HashMap siegeCooldownRemaining = new HashMap(); //whether or not a sieger can siege a particular victim or claim, considering only cooldowns - public boolean onCooldown(Player attacker, Player defender, Claim defenderClaim) + synchronized public boolean onCooldown(Player attacker, Player defender, Claim defenderClaim) { Long cooldownEnd = null; @@ -740,7 +740,7 @@ public abstract class DataStore } //extend a siege, if it's possible to do so - void tryExtendSiege(Player player, Claim claim) + synchronized void tryExtendSiege(Player player, Claim claim) { PlayerData playerData = this.getPlayerData(player.getName()); @@ -762,7 +762,7 @@ public abstract class DataStore } //deletes all claims owned by a player - public void deleteClaimsForPlayer(String playerName, boolean deleteCreativeClaims) + synchronized public void deleteClaimsForPlayer(String playerName, boolean deleteCreativeClaims) { //make a list of the player's claims ArrayList claimsToDelete = new ArrayList(); @@ -783,7 +783,7 @@ public abstract class DataStore //tries to resize a claim //see CreateClaim() for details on return value - public CreateClaimResult resizeClaim(Claim claim, int newx1, int newx2, int newy1, int newy2, int newz1, int newz2) + synchronized public CreateClaimResult resizeClaim(Claim claim, int newx1, int newx2, int newy1, int newy2, int newz1, int newz2) { //remove old claim this.deleteClaim(claim); @@ -993,7 +993,8 @@ public abstract class DataStore this.addDefault(defaults, Messages.UntrustOwnerOnly, "Only {0} can revoke permissions here.", "0: claim owner's name"); this.addDefault(defaults, Messages.HowToClaimRegex, "(^|.*\\W)how\\W.*\\W(claim|protect)(\\W.*|$)", "This is a Java Regular Expression. Look it up before editing! It's used to tell players about the demo video when they ask how to claim land."); this.addDefault(defaults, Messages.NoBuildOutsideClaims, "You can't build here unless you claim some land first.", null); - this.addDefault(defaults, Messages.PlayerOfflineTime, " Last login: {0} days ago.", "0: number of full days since last login"); + this.addDefault(defaults, Messages.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); //load the config file FileConfiguration config = YamlConfiguration.loadConfiguration(new File(messagesFilePath)); @@ -1044,7 +1045,7 @@ public abstract class DataStore defaults.put(id.name(), message); } - public String getMessage(Messages messageID, String... args) + synchronized public String getMessage(Messages messageID, String... args) { String message = messages[messageID.ordinal()]; diff --git a/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java b/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java index cfa2790..0ad388e 100644 --- a/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java @@ -236,7 +236,7 @@ public class DatabaseDataStore extends DataStore } @Override - void writeClaimToStorage(Claim claim) //see datastore.cs. this will ALWAYS be a top level claim + synchronized void writeClaimToStorage(Claim claim) //see datastore.cs. this will ALWAYS be a top level claim { try { @@ -261,7 +261,7 @@ public class DatabaseDataStore extends DataStore } //actually writes claim data to the database - private void writeClaimData(Claim claim) throws SQLException + synchronized private void writeClaimData(Claim claim) throws SQLException { String lesserCornerString = this.locationToString(claim.getLesserBoundaryCorner()); String greaterCornerString = this.locationToString(claim.getGreaterBoundaryCorner()); @@ -342,7 +342,7 @@ public class DatabaseDataStore extends DataStore //deletes a top level claim from the database @Override - void deleteClaimFromSecondaryStorage(Claim claim) + synchronized void deleteClaimFromSecondaryStorage(Claim claim) { try { @@ -358,7 +358,7 @@ public class DatabaseDataStore extends DataStore } @Override - PlayerData getPlayerDataFromStorage(String playerName) + synchronized PlayerData getPlayerDataFromStorage(String playerName) { PlayerData playerData = new PlayerData(); playerData.playerName = playerName; @@ -393,7 +393,7 @@ public class DatabaseDataStore extends DataStore //saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them @Override - public void savePlayerData(String playerName, PlayerData playerData) + synchronized public void savePlayerData(String playerName, PlayerData playerData) { //never save data for the "administrative" account. an empty string for player name indicates administrative account if(playerName.length() == 0) return; @@ -415,13 +415,13 @@ public class DatabaseDataStore extends DataStore } @Override - void incrementNextClaimID() + synchronized void incrementNextClaimID() { this.setNextClaimID(this.nextClaimID + 1); } //sets the next claim ID. used by incrementNextClaimID() above, and also while migrating data from a flat file data store - void setNextClaimID(long nextID) + synchronized void setNextClaimID(long nextID) { this.nextClaimID = nextID; @@ -440,7 +440,7 @@ public class DatabaseDataStore extends DataStore //updates the database with a group's bonus blocks @Override - void saveGroupBonusBlocks(String groupName, int currentValue) + synchronized void saveGroupBonusBlocks(String groupName, int currentValue) { //group bonus blocks are stored in the player data table, with player name = $groupName String playerName = "$" + groupName; @@ -451,7 +451,7 @@ public class DatabaseDataStore extends DataStore } @Override - void close() + synchronized void close() { if(this.databaseConnection != null) { diff --git a/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java b/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java index 3d0875a..f9b3a1f 100644 --- a/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java @@ -27,6 +27,7 @@ import org.bukkit.World.Environment; import org.bukkit.block.Block; import org.bukkit.entity.Arrow; import org.bukkit.entity.Creature; +import org.bukkit.entity.Creeper; import org.bukkit.entity.Enderman; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; @@ -92,8 +93,8 @@ class EntityEventHandler implements Listener Location location = explodeEvent.getLocation(); //FEATURE: explosions don't destroy blocks when they explode near or above sea level in standard worlds - - if(GriefPrevention.instance.config_blockSurfaceExplosions && location.getWorld().getEnvironment() == Environment.NORMAL) + boolean isCreeper = (explodeEvent.getEntity() != null && explodeEvent.getEntity() instanceof Creeper); + if( location.getWorld().getEnvironment() == Environment.NORMAL && ((isCreeper && GriefPrevention.instance.config_blockSurfaceCreeperExplosions) || (!isCreeper && GriefPrevention.instance.config_blockSurfaceOtherExplosions))) { for(int i = 0; i < blocks.size(); i++) { diff --git a/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java b/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java index 295f582..164e7d1 100644 --- a/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java @@ -257,7 +257,7 @@ public class FlatFileDataStore extends DataStore } @Override - void writeClaimToStorage(Claim claim) + synchronized void writeClaimToStorage(Claim claim) { String claimID = String.valueOf(claim.id); @@ -296,7 +296,7 @@ public class FlatFileDataStore extends DataStore } //actually writes claim data to an output stream - private void writeClaimData(Claim claim, BufferedWriter outStream) throws IOException + synchronized private void writeClaimData(Claim claim, BufferedWriter outStream) throws IOException { //first line is lesser boundary corner location outStream.write(this.locationToString(claim.getLesserBoundaryCorner())); @@ -352,7 +352,7 @@ public class FlatFileDataStore extends DataStore //deletes a top level claim from the file system @Override - void deleteClaimFromSecondaryStorage(Claim claim) + synchronized void deleteClaimFromSecondaryStorage(Claim claim) { String claimID = String.valueOf(claim.id); @@ -365,7 +365,7 @@ public class FlatFileDataStore extends DataStore } @Override - PlayerData getPlayerDataFromStorage(String playerName) + synchronized PlayerData getPlayerDataFromStorage(String playerName) { File playerFile = new File(playerDataFolderPath + File.separator + playerName); @@ -439,7 +439,7 @@ public class FlatFileDataStore extends DataStore //saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them @Override - public void savePlayerData(String playerName, PlayerData playerData) + synchronized public void savePlayerData(String playerName, PlayerData playerData) { //never save data for the "administrative" account. an empty string for claim owner indicates administrative account if(playerName.length() == 0) return; @@ -496,7 +496,7 @@ public class FlatFileDataStore extends DataStore } @Override - void incrementNextClaimID() + synchronized void incrementNextClaimID() { //increment in memory this.nextClaimID++; @@ -529,7 +529,7 @@ public class FlatFileDataStore extends DataStore //grants a group (players with a specific permission) bonus claim blocks as long as they're still members of the group @Override - void saveGroupBonusBlocks(String groupName, int currentValue) + synchronized void saveGroupBonusBlocks(String groupName, int currentValue) { //write changes to file to ensure they don't get lost BufferedWriter outStream = null; @@ -562,7 +562,7 @@ public class FlatFileDataStore extends DataStore catch(IOException exception){} } - void migrateData(DatabaseDataStore databaseStore) + synchronized void migrateData(DatabaseDataStore databaseStore) { //migrate claims for(int i = 0; i < this.claims.size(); i++) @@ -629,5 +629,5 @@ public class FlatFileDataStore extends DataStore } @Override - void close() { } + synchronized void close() { } } diff --git a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java index ecb77ad..813dbe0 100644 --- a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java +++ b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java @@ -66,7 +66,9 @@ public class GriefPrevention extends JavaPlugin public boolean config_claims_preventTheft; //whether containers and crafting blocks are protectable public boolean config_claims_protectCreatures; //whether claimed animals may be injured by players without permission public boolean config_claims_preventButtonsSwitches; //whether buttons and switches are protectable - public boolean config_claims_lockAllDoors; //whether wooden doors, trap doors, fence gates, etc require permission to use + public boolean config_claims_lockWoodenDoors; //whether wooden doors should be locked by default (require /accesstrust) + public boolean config_claims_lockTrapDoors; //whether trap doors should be locked by default (require /accesstrust) + public boolean config_claims_lockFenceGates; //whether fence gates should be locked by default (require /accesstrust) public int config_claims_initialBlocks; //the number of claim blocks a new player starts with public int config_claims_blocksAccruedPerHour; //how many additional blocks players get each hour of play (can be zero) @@ -108,7 +110,8 @@ public class GriefPrevention extends JavaPlugin public double config_economy_claimBlocksPurchaseCost; //cost to purchase a claim block. set to zero to disable purchase. public double config_economy_claimBlocksSellValue; //return on a sold claim block. set to zero to disable sale. - public boolean config_blockSurfaceExplosions; //whether creeper/TNT explosions near or above the surface destroy blocks + public boolean config_blockSurfaceCreeperExplosions; //whether creeper explosions near or above the surface destroy blocks + public boolean config_blockSurfaceOtherExplosions; //whether non-creeper explosions near or above the surface destroy blocks public boolean config_blockWildernessWaterBuckets; //whether players can dump water buckets outside their claims public boolean config_blockSkyTrees; //whether players can build trees on platforms in the sky @@ -128,6 +131,8 @@ public class GriefPrevention extends JavaPlugin public List config_mods_containerTrustIds; //list of block IDs which should require /containertrust for player interaction public List config_mods_ignoreClaimsAccounts; //list of player names which ALWAYS ignore claims public List config_mods_explodableIds; //list of block IDs which can be destroyed by explosions, even in claimed areas + + public boolean config_claims_warnOnBuildOutside; //whether players should be warned when they're building in an unclaimed area //reference to the economy plugin, if economy integration is enabled public static Economy economy = null; @@ -225,7 +230,9 @@ public class GriefPrevention extends JavaPlugin this.config_claims_preventTheft = config.getBoolean("GriefPrevention.Claims.PreventTheft", true); this.config_claims_protectCreatures = config.getBoolean("GriefPrevention.Claims.ProtectCreatures", true); this.config_claims_preventButtonsSwitches = config.getBoolean("GriefPrevention.Claims.PreventButtonsSwitches", true); - this.config_claims_lockAllDoors = config.getBoolean("GriefPrevention.Claims.LockAllDoors", false); + this.config_claims_lockWoodenDoors = config.getBoolean("GriefPrevention.Claims.LockWoodenDoors", false); + this.config_claims_lockTrapDoors = config.getBoolean("GriefPrevention.Claims.LockTrapDoors", false); + this.config_claims_lockFenceGates = config.getBoolean("GriefPrevention.Claims.LockFenceGates", false); this.config_claims_initialBlocks = config.getInt("GriefPrevention.Claims.InitialBlocks", 100); this.config_claims_blocksAccruedPerHour = config.getInt("GriefPrevention.Claims.BlocksAccruedPerHour", 100); this.config_claims_maxAccruedBlocks = config.getInt("GriefPrevention.Claims.MaxAccruedBlocks", 80000); @@ -237,6 +244,7 @@ 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_spam_enabled = config.getBoolean("GriefPrevention.Spam.Enabled", true); this.config_spam_loginCooldownMinutes = config.getInt("GriefPrevention.Spam.LoginCooldownMinutes", 2); @@ -257,7 +265,8 @@ public class GriefPrevention extends JavaPlugin this.config_economy_claimBlocksPurchaseCost = config.getDouble("GriefPrevention.Economy.ClaimBlocksPurchaseCost", 0); this.config_economy_claimBlocksSellValue = config.getDouble("GriefPrevention.Economy.ClaimBlocksSellValue", 0); - this.config_blockSurfaceExplosions = config.getBoolean("GriefPrevention.BlockSurfaceExplosions", true); + this.config_blockSurfaceCreeperExplosions = config.getBoolean("GriefPrevention.BlockSurfaceCreeperExplosions", true); + this.config_blockSurfaceOtherExplosions = config.getBoolean("GriefPrevention.BlockSurfaceOtherExplosions", true); this.config_blockWildernessWaterBuckets = config.getBoolean("GriefPrevention.LimitSurfaceWaterBuckets", true); this.config_blockSkyTrees = config.getBoolean("GriefPrevention.LimitSkyTrees", true); @@ -276,7 +285,7 @@ public class GriefPrevention extends JavaPlugin this.config_mods_accessTrustIds = config.getIntegerList("GriefPrevention.Mods.BlockIdsRequiringAccessTrust"); if(this.config_mods_accessTrustIds == null) this.config_mods_accessTrustIds = new ArrayList(); - this.config_mods_accessTrustIds = config.getIntegerList("GriefPrevention.Mods.BlockIdsRequiringContainerTrust"); + this.config_mods_containerTrustIds = config.getIntegerList("GriefPrevention.Mods.BlockIdsRequiringContainerTrust"); if(this.config_mods_containerTrustIds == null) this.config_mods_containerTrustIds = new ArrayList(); this.config_mods_ignoreClaimsAccounts = config.getStringList("GriefPrevention.Mods.PlayersIgnoringAllClaims"); @@ -395,7 +404,9 @@ public class GriefPrevention extends JavaPlugin config.set("GriefPrevention.Claims.PreventTheft", this.config_claims_preventTheft); config.set("GriefPrevention.Claims.ProtectCreatures", this.config_claims_protectCreatures); config.set("GriefPrevention.Claims.PreventButtonsSwitches", this.config_claims_preventButtonsSwitches); - config.set("GriefPrevention.Claims.LockAllDoors", this.config_claims_lockAllDoors); + config.set("GriefPrevention.Claims.LockWoodenDoors", this.config_claims_lockWoodenDoors); + config.set("GriefPrevention.Claims.LockTrapDoors", this.config_claims_lockTrapDoors); + config.set("GriefPrevention.Claims.LockFenceGates", this.config_claims_lockFenceGates); config.set("GriefPrevention.Claims.InitialBlocks", this.config_claims_initialBlocks); config.set("GriefPrevention.Claims.BlocksAccruedPerHour", this.config_claims_blocksAccruedPerHour); config.set("GriefPrevention.Claims.MaxAccruedBlocks", this.config_claims_maxAccruedBlocks); @@ -408,7 +419,8 @@ public class GriefPrevention extends JavaPlugin config.set("GriefPrevention.Claims.TrappedCommandCooldownHours", this.config_claims_trappedCooldownHours); config.set("GriefPrevention.Claims.InvestigationTool", this.config_claims_investigationTool.name()); config.set("GriefPrevention.Claims.ModificationTool", this.config_claims_modificationTool.name()); - config.set("GriefPrevention.Claims.NoSurvivalBuildingOutsideClaims", this.config_claims_noBuildOutsideClaims); + config.set("GriefPrevention.Claims.NoSurvivalBuildingOutsideClaims", this.config_claims_noBuildOutsideClaims); + config.set("GriefPrevention.Claims.WarnWhenBuildingOutsideClaims", this.config_claims_warnOnBuildOutside); config.set("GriefPrevention.Spam.Enabled", this.config_spam_enabled); config.set("GriefPrevention.Spam.LoginCooldownMinutes", this.config_spam_loginCooldownMinutes); @@ -429,7 +441,8 @@ public class GriefPrevention extends JavaPlugin config.set("GriefPrevention.Economy.ClaimBlocksPurchaseCost", this.config_economy_claimBlocksPurchaseCost); config.set("GriefPrevention.Economy.ClaimBlocksSellValue", this.config_economy_claimBlocksSellValue); - config.set("GriefPrevention.BlockSurfaceExplosions", this.config_blockSurfaceExplosions); + config.set("GriefPrevention.BlockSurfaceCreeperExplosions", this.config_blockSurfaceCreeperExplosions); + config.set("GriefPrevention.BlockSurfaceOtherExplosions", this.config_blockSurfaceOtherExplosions); config.set("GriefPrevention.LimitSurfaceWaterBuckets", this.config_blockWildernessWaterBuckets); config.set("GriefPrevention.LimitSkyTrees", this.config_blockSkyTrees); @@ -2117,9 +2130,15 @@ public class GriefPrevention extends JavaPlugin //sends a color-coded message to a player static void sendMessage(Player player, ChatColor color, Messages messageID, String... args) + { + sendMessage(player, color, messageID, 0, args); + } + + //sends a color-coded message to a player + static void sendMessage(Player player, ChatColor color, Messages messageID, long delayInTicks, String... args) { String message = GriefPrevention.instance.dataStore.getMessage(messageID, args); - sendMessage(player, color, message); + sendMessage(player, color, message, delayInTicks); } //sends a color-coded message to a player @@ -2135,6 +2154,19 @@ public class GriefPrevention extends JavaPlugin } } + static void sendMessage(Player player, ChatColor color, String message, long delayInTicks) + { + SendPlayerMessageTask task = new SendPlayerMessageTask(player, color, message); + if(delayInTicks > 0) + { + GriefPrevention.instance.getServer().getScheduler().scheduleAsyncDelayedTask(GriefPrevention.instance, task, delayInTicks); + } + else + { + task.run(); + } + } + //determines whether creative anti-grief rules apply at a location boolean creativeRulesApply(Location location) { diff --git a/src/me/ryanhamshire/GriefPrevention/Messages.java b/src/me/ryanhamshire/GriefPrevention/Messages.java index 1b61330..af644e6 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 + 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 } diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerData.java b/src/me/ryanhamshire/GriefPrevention/PlayerData.java index 4b4c735..84bb436 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerData.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerData.java @@ -67,6 +67,9 @@ public class PlayerData //last place the player damaged a chest public Location lastChestDamageLocation = null; + //number of blocks placed outside claims before next warning + int unclaimedBlockPlacementsUntilWarning = 1; + //spam public Date lastLogin; //when the player last logged into the server public String lastMessage = ""; //the player's last chat message, or slash command complete with parameters diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java index b6bf22b..fb05ec4 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java @@ -74,11 +74,17 @@ class PlayerEventHandler implements Listener //when a player chats, monitor for spam @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) - void onPlayerChat (PlayerChatEvent event) + void onPlayerChat (AsyncPlayerChatEvent event) { Player player = event.getPlayer(); String message = event.getMessage(); + event.setCancelled(this.handlePlayerChat(player, message, event)); + } + + //returns true if the message should be sent, false if it should be muted + private boolean handlePlayerChat(Player player, String message, PlayerEvent event) + { //FEATURE: automatically educate players about claiming land //watching for message format how*claim*, and will send a link to the basics video if(this.howToClaimPattern == null) @@ -90,11 +96,11 @@ class PlayerEventHandler implements Listener { if(GriefPrevention.instance.creativeRulesApply(player.getLocation())) { - GriefPrevention.sendMessage(player, TextMode.Info, Messages.CreativeBasicsDemoAdvertisement); + GriefPrevention.sendMessage(player, TextMode.Info, Messages.CreativeBasicsDemoAdvertisement, 10L); } else { - GriefPrevention.sendMessage(player, TextMode.Info, Messages.SurvivalBasicsDemoAdvertisement); + GriefPrevention.sendMessage(player, TextMode.Info, Messages.SurvivalBasicsDemoAdvertisement, 10L); } } @@ -102,23 +108,26 @@ class PlayerEventHandler implements Listener //check for "trapped" or "stuck" to educate players about the /trapped command if(message.contains("trapped") || message.contains("stuck") || message.contains(this.dataStore.getMessage(Messages.TrappedChatKeyword))) { - GriefPrevention.sendMessage(player, TextMode.Info, Messages.TrappedInstructions); + GriefPrevention.sendMessage(player, TextMode.Info, Messages.TrappedInstructions, 10L); } //FEATURE: monitor for chat and command spam - if(!GriefPrevention.instance.config_spam_enabled) return; + if(!GriefPrevention.instance.config_spam_enabled) return false; //if the player has permission to spam, don't bother even examining the message - if(player.hasPermission("griefprevention.spam")) return; + if(player.hasPermission("griefprevention.spam")) return false; //remedy any CAPS SPAM without bothering to fault the player for it - if(message.length() > 4 && message.toUpperCase().equals(message)) + if(message.length() > 4 && this.stringsAreSimilar(message.toUpperCase(), message)) { - event.setMessage(message.toLowerCase()); + if(event instanceof AsyncPlayerChatEvent) + { + ((AsyncPlayerChatEvent)event).setMessage(message.toLowerCase()); + } } - //where spam is concerned, casing isn't significant + //where other types of spam are concerned, casing isn't significant message = message.toLowerCase(); PlayerData playerData = this.dataStore.getPlayerData(player.getName()); @@ -137,8 +146,8 @@ class PlayerEventHandler implements Listener spam = true; } - //if it's very similar to the last message and less than 15 seconds have passed - if(!muted && this.stringsAreSimilar(message, playerData.lastMessage) && millisecondsSinceLastMessage < 15000) + //if it's very similar to the last message + if(!muted && this.stringsAreSimilar(message, playerData.lastMessage)) { playerData.spamCount++; spam = true; @@ -146,10 +155,10 @@ class PlayerEventHandler implements Listener } //filter IP addresses - if(!muted && !(event instanceof PlayerCommandPreprocessEvent)) + if(!muted) { Pattern ipAddressPattern = Pattern.compile("\\d{1,4}\\D{1,3}\\d{1,4}\\D{1,3}\\d{1,4}\\D{1,3}\\d{1,4}"); - Matcher matcher = ipAddressPattern.matcher(event.getMessage()); + Matcher matcher = ipAddressPattern.matcher(message); //if it looks like an IP address while(matcher.find()) @@ -158,7 +167,7 @@ class PlayerEventHandler implements Listener if(!GriefPrevention.instance.config_spam_allowedIpAddresses.contains(matcher.group())) { //log entry - GriefPrevention.AddLogEntry("Muted IP address from " + player.getName() + ": " + event.getMessage()); + GriefPrevention.AddLogEntry("Muted IP address from " + player.getName() + ": " + message); //spam notation playerData.spamCount++; @@ -201,7 +210,6 @@ class PlayerEventHandler implements Listener if(!muted && message.length() < 5 && millisecondsSinceLastMessage < 5000) { spam = true; - if(playerData.spamCount > 4) muted = true; playerData.spamCount++; } @@ -235,7 +243,7 @@ class PlayerEventHandler implements Listener muted = true; if(!playerData.spamWarned) { - GriefPrevention.sendMessage(player, TextMode.Warn, GriefPrevention.instance.config_spam_warningMessage); + GriefPrevention.sendMessage(player, TextMode.Warn, GriefPrevention.instance.config_spam_warningMessage, 10L); GriefPrevention.AddLogEntry("Warned " + player.getName() + " about spam penalties."); playerData.spamWarned = true; } @@ -243,14 +251,15 @@ class PlayerEventHandler implements Listener if(muted) { - //cancel the event and make a log entry - //cancelling the event guarantees players don't receive the message - event.setCancelled(true); + //make a log entry GriefPrevention.AddLogEntry("Muted spam from " + player.getName() + ": " + message); //send a fake message so the player doesn't realize he's muted //less information for spammers = less effective spam filter dodging - player.sendMessage("<" + player.getName() + "> " + event.getMessage()); + player.sendMessage("<" + player.getName() + "> " + message); + + //cancelling the event guarantees other players don't receive the message + return true; } } @@ -263,7 +272,9 @@ class PlayerEventHandler implements Listener //in any case, record the timestamp of this message and also its content for next time playerData.lastMessageTimestamp = new Date(); - playerData.lastMessage = message; + playerData.lastMessage = message; + + return false; } //if two strings are 75% identical, they're too close to follow each other in the chat @@ -345,7 +356,10 @@ class PlayerEventHandler implements Listener if(!GriefPrevention.instance.config_spam_enabled) return; //if the slash command used is in the list of monitored commands, treat it like a chat message (see above) - if(GriefPrevention.instance.config_spam_monitorSlashCommands.contains(args[0])) this.onPlayerChat(event); + if(GriefPrevention.instance.config_spam_monitorSlashCommands.contains(args[0])) + { + event.setCancelled(this.handlePlayerChat(event.getPlayer(), event.getMessage(), event)); + } } //when a player attempts to join the server... @@ -878,9 +892,11 @@ class PlayerEventHandler implements Listener //apply rules for containers and crafting blocks if( GriefPrevention.instance.config_claims_preventTheft && ( event.getAction() == Action.RIGHT_CLICK_BLOCK && ( - clickedBlock.getState() instanceof InventoryHolder || - clickedBlockType == Material.BREWING_STAND || + clickedBlock.getState() instanceof InventoryHolder || clickedBlockType == Material.WORKBENCH || + clickedBlockType == Material.ENDER_CHEST || + clickedBlockType == Material.DISPENSER || + clickedBlockType == Material.BREWING_STAND || clickedBlockType == Material.JUKEBOX || clickedBlockType == Material.ENCHANTMENT_TABLE || GriefPrevention.instance.config_mods_containerTrustIds.contains(clickedBlock.getTypeId())))) @@ -925,7 +941,9 @@ class PlayerEventHandler implements Listener } //otherwise apply rules for doors, if configured that way - else if(GriefPrevention.instance.config_claims_lockAllDoors && (clickedBlockType == Material.WOOD_DOOR || clickedBlockType == Material.WOODEN_DOOR || clickedBlockType == Material.TRAP_DOOR || clickedBlockType == Material.FENCE_GATE)) + else if((GriefPrevention.instance.config_claims_lockWoodenDoors && clickedBlockType == Material.WOOD_DOOR) || + (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); if(claim != null) @@ -1346,7 +1364,7 @@ class PlayerEventHandler implements Listener } //make sure player has enough blocks to make up the difference - if(!playerData.claimResizing.isAdminClaim()) + if(!playerData.claimResizing.isAdminClaim() && player.getName().equals(playerData.claimResizing.getOwnerName())) { int newArea = newWidth * newHeight; int blocksRemainingAfter = playerData.getRemainingClaimBlocks() + playerData.claimResizing.getArea() - newArea; diff --git a/src/me/ryanhamshire/GriefPrevention/SendPlayerMessageTask.java b/src/me/ryanhamshire/GriefPrevention/SendPlayerMessageTask.java new file mode 100644 index 0000000..fce80b7 --- /dev/null +++ b/src/me/ryanhamshire/GriefPrevention/SendPlayerMessageTask.java @@ -0,0 +1,44 @@ +/* + 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 org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +//sends a message to a player +//used to send delayed messages, for example help text triggered by a player's chat +class SendPlayerMessageTask implements Runnable +{ + private Player player; + private ChatColor color; + private String message; + + public SendPlayerMessageTask(Player player, ChatColor color, String message) + { + this.player = player; + this.color = color; + this.message = message; + } + + @Override + public void run() + { + GriefPrevention.sendMessage(this.player, this.color, this.message); + } +}