This commit is contained in:
Ryan Hamshire 2012-08-07 21:46:31 -07:00
parent 50f572fc2b
commit 452fd7f11a
11 changed files with 273 additions and 81 deletions

View File

@ -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 <player>
permission: griefprevention.adjustbonusclaimblocks
permission: griefprevention.adjustclaimblocks
permissions:
griefprevention.createclaims:
description: Grants permission to create claims.

View File

@ -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<Material> 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<Material>();
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<Block> 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)
{

View File

@ -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<String> 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<String, Long> siegeCooldownRemaining = new HashMap<String, Long>();
//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<Claim> claimsToDelete = new ArrayList<Claim>();
@ -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()];

View File

@ -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)
{

View File

@ -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++)
{

View File

@ -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() { }
}

View File

@ -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<Integer> config_mods_containerTrustIds; //list of block IDs which should require /containertrust for player interaction
public List<String> config_mods_ignoreClaimsAccounts; //list of player names which ALWAYS ignore claims
public List<Integer> 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<Integer>();
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<Integer>();
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)
{

View File

@ -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
}

View File

@ -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

View File

@ -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;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
}