diff --git a/plugin.yml b/plugin.yml index 8580885..c102f8e 100644 --- a/plugin.yml +++ b/plugin.yml @@ -1,7 +1,7 @@ name: GriefPrevention main: me.ryanhamshire.GriefPrevention.GriefPrevention softdepend: [Vault, Multiverse-Core] -version: 4.7 +version: 4.9 commands: abandonclaim: description: Deletes a claim. diff --git a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java index b0d4e09..e92db94 100644 --- a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java @@ -68,7 +68,7 @@ public class BlockEventHandler implements Listener if(!GriefPrevention.instance.config_addItemsToClaimedChests) return; Block block = event.getBlock(); - Player player = event.getPlayer(); + Player player = event.getPlayer(); //only care about player-damaged blocks if(player == null) return; @@ -200,9 +200,7 @@ public class BlockEventHandler implements Listener PlayerData playerData = this.dataStore.getPlayerData(player.getName()); if(notEmpty && playerData.lastMessage != null && !playerData.lastMessage.equals(signMessage)) { - GriefPrevention.AddLogEntry("[Sign Placement] <" + player.getName() + "> " + lines.toString()); - GriefPrevention.AddLogEntry("Location: " + GriefPrevention.getfriendlyLocationString(event.getBlock().getLocation())); - + GriefPrevention.AddLogEntry("[Sign Placement] <" + player.getName() + "> " + lines.toString() + " @ " + GriefPrevention.getfriendlyLocationString(event.getBlock().getLocation())); playerData.lastMessage = signMessage; } } @@ -275,7 +273,7 @@ public class BlockEventHandler implements Listener //radius == 0 means protect ONLY the chest if(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius == 0) { - this.dataStore.createClaim(block.getWorld(), block.getX(), block.getX(), block.getY(), block.getY(), block.getZ(), block.getZ(), player.getName(), null); + this.dataStore.createClaim(block.getWorld(), block.getX(), block.getX(), block.getY(), block.getY(), block.getZ(), block.getZ(), player.getName(), null, null); GriefPrevention.sendMessage(player, TextMode.Success, Messages.ChestClaimConfirmation); } @@ -289,7 +287,7 @@ public class BlockEventHandler implements Listener block.getY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance, block.getY(), block.getZ() - radius, block.getZ() + radius, player.getName(), - null).succeeded) + null, null).succeeded) { radius--; } @@ -339,13 +337,17 @@ public class BlockEventHandler implements Listener @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onBlockPistonExtend (BlockPistonExtendEvent event) { + List blocks = event.getBlocks(); + + //if no blocks moving, then we don't care + if(blocks.size() == 0) return; + //who owns the piston, if anyone? String pistonClaimOwnerName = "_"; Claim claim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, null); if(claim != null) pistonClaimOwnerName = claim.getOwnerName(); //which blocks are being pushed? - List blocks = event.getBlocks(); for(int i = 0; i < blocks.size(); i++) { //if ANY of the pushed blocks are owned by someone other than the piston owner, cancel the event @@ -354,9 +356,69 @@ public class BlockEventHandler implements Listener if(claim != null && !claim.getOwnerName().equals(pistonClaimOwnerName)) { event.setCancelled(true); + event.getBlock().getWorld().createExplosion(event.getBlock().getLocation(), 0); + event.getBlock().getWorld().dropItem(event.getBlock().getLocation(), new ItemStack(event.getBlock().getType())); + event.getBlock().setType(Material.AIR); return; } } + + //which direction? note we're ignoring vertical push + int xchange = 0; + int zchange = 0; + + Block piston = event.getBlock(); + Block firstBlock = blocks.get(0); + + if(firstBlock.getX() > piston.getX()) + { + xchange = 1; + } + else if(firstBlock.getX() < piston.getX()) + { + xchange = -1; + } + else if(firstBlock.getZ() > piston.getZ()) + { + zchange = 1; + } + else if(firstBlock.getZ() < piston.getZ()) + { + zchange = -1; + } + + //if horizontal movement + if(xchange != 0 || zchange != 0) + { + for(int i = 0; i < blocks.size(); i++) + { + Block block = blocks.get(i); + Claim originalClaim = this.dataStore.getClaimAt(block.getLocation(), false, null); + String originalOwnerName = ""; + if(originalClaim != null) + { + originalOwnerName = originalClaim.getOwnerName(); + } + + Claim newClaim = this.dataStore.getClaimAt(block.getLocation().add(xchange, 0, zchange), false, null); + String newOwnerName = ""; + if(newClaim != null) + { + newOwnerName = newClaim.getOwnerName(); + } + + //if pushing this block will change ownership, cancel the event and take away the piston (for performance reasons) + if(!newOwnerName.equals(originalOwnerName)) + { + event.setCancelled(true); + event.getBlock().getWorld().createExplosion(event.getBlock().getLocation(), 0); + event.getBlock().getWorld().dropItem(event.getBlock().getLocation(), new ItemStack(event.getBlock().getType())); + event.getBlock().setType(Material.AIR); + return; + } + + } + } } //blocks theft by pulling blocks out of a claim (again pistons) @@ -469,11 +531,13 @@ public class BlockEventHandler implements Listener Location rootLocation = growEvent.getLocation(); Claim rootClaim = this.dataStore.getClaimAt(rootLocation, false, null); - //who owns the root, if anyone? //who owns the spreading block, if anyone? OfflinePlayer fromOwner = null; if(rootClaim != null) { + //tree growth in subdivisions is dependent on who owns the top level claim + if(rootClaim.parent != null) rootClaim = rootClaim.parent; + //if an administrative claim, just let the tree grow where it wants if(rootClaim.isAdminClaim()) return; diff --git a/src/me/ryanhamshire/GriefPrevention/Claim.java b/src/me/ryanhamshire/GriefPrevention/Claim.java index 5362241..dc05b0e 100644 --- a/src/me/ryanhamshire/GriefPrevention/Claim.java +++ b/src/me/ryanhamshire/GriefPrevention/Claim.java @@ -46,6 +46,9 @@ public class Claim //modification date. this comes from the file timestamp during load, and is updated with runtime changes public Date modifiedDate; + //id number. unique to this claim, never changes. + Long id = null; + //ownername. for admin claims, this is the empty string //use getOwnerName() to get a friendly name (will be "an administrator" for admin claims) public String ownerName; @@ -83,6 +86,12 @@ public class Claim return this.ownerName.isEmpty(); } + //accessor for ID + public Long getID() + { + return new Long(this.id); + } + //basic constructor, just notes the creation time //see above declarations for other defaults Claim() @@ -178,11 +187,14 @@ public class Claim } //main constructor. note that only creating a claim instance does nothing - a claim must be added to the data store to be effective - Claim(Location lesserBoundaryCorner, Location greaterBoundaryCorner, String ownerName, String [] builderNames, String [] containerNames, String [] accessorNames, String [] managerNames) + Claim(Location lesserBoundaryCorner, Location greaterBoundaryCorner, String ownerName, String [] builderNames, String [] containerNames, String [] accessorNames, String [] managerNames, Long id) { //modification date this.modifiedDate = Calendar.getInstance().getTime(); + //id + this.id = id; + //store corners this.lesserBoundaryCorner = lesserBoundaryCorner; this.greaterBoundaryCorner = greaterBoundaryCorner; @@ -253,7 +265,7 @@ public class Claim Claim claim = new Claim (new Location(this.lesserBoundaryCorner.getWorld(), this.lesserBoundaryCorner.getBlockX() - howNear, this.lesserBoundaryCorner.getBlockY(), this.lesserBoundaryCorner.getBlockZ() - howNear), new Location(this.greaterBoundaryCorner.getWorld(), this.greaterBoundaryCorner.getBlockX() + howNear, this.greaterBoundaryCorner.getBlockY(), this.greaterBoundaryCorner.getBlockZ() + howNear), - "", new String[] {}, new String[] {}, new String[] {}, new String[] {}); + "", new String[] {}, new String[] {}, new String[] {}, new String[] {}, null); return claim.contains(location, false, true); } diff --git a/src/me/ryanhamshire/GriefPrevention/DataStore.java b/src/me/ryanhamshire/GriefPrevention/DataStore.java index d429b61..cf4e4c7 100644 --- a/src/me/ryanhamshire/GriefPrevention/DataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DataStore.java @@ -45,12 +45,16 @@ public class DataStore //in-memory cache for messages private String [] messages; + //next claim ID + Long nextClaimID = (long)0; + //path information, for where stuff stored on disk is well... stored private final static String dataLayerFolderPath = "plugins" + File.separator + "GriefPreventionData"; private final static String playerDataFolderPath = dataLayerFolderPath + File.separator + "PlayerData"; private final static String claimDataFolderPath = dataLayerFolderPath + File.separator + "ClaimData"; final static String configFilePath = dataLayerFolderPath + File.separator + "config.yml"; final static String messagesFilePath = dataLayerFolderPath + File.separator + "messages.yml"; + final static String nextClaimIdFilePath = claimDataFolderPath + File.separator + "_nextClaimID"; //initialization! DataStore() @@ -97,6 +101,32 @@ public class DataStore //load claims data into memory File claimDataFolder = new File(claimDataFolderPath); + + //load next claim number from file + File nextClaimIdFile = new File(nextClaimIdFilePath); + if(nextClaimIdFile.exists()) + { + BufferedReader inStream = null; + try + { + inStream = new BufferedReader(new FileReader(nextClaimIdFile.getAbsolutePath())); + + //read the id + String line = inStream.readLine(); + + //try to parse into a long value + this.nextClaimID = Long.parseLong(line); + } + catch(Exception e){ } + + try + { + if(inStream != null) inStream.close(); + } + catch(IOException exception) {} + } + + //get a list of all the files in the claims data folder files = claimDataFolder.listFiles(); int loadedClaimCount = 0; @@ -105,6 +135,28 @@ public class DataStore { if(files[i].isFile()) //avoids folders { + //skip any file starting with an underscore, to avoid the _nextClaimID file. + if(files[i].getName().startsWith("_")) continue; + + //the filename is the claim ID. try to parse it + long claimID; + + try + { + claimID = Long.parseLong(files[i].getName()); + } + + //because some older versions used a different file name pattern before claim IDs were introduced, + //those files need to be "converted" by renaming them to a unique ID + catch(Exception e) + { + claimID = this.nextClaimID; + this.incrementNextClaimID(); + File newFile = new File(claimDataFolderPath + File.separator + String.valueOf(this.nextClaimID)); + files[i].renameTo(newFile); + files[i] = newFile; + } + BufferedReader inStream = null; try { @@ -153,7 +205,7 @@ public class DataStore if(topLevelClaim == null) { //instantiate - topLevelClaim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerName, builderNames, containerNames, accessorNames, managerNames); + topLevelClaim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerName, builderNames, containerNames, accessorNames, managerNames, claimID); //search for another claim overlapping this one Claim conflictClaim = this.getClaimAt(topLevelClaim.lesserBoundaryCorner, true, null); @@ -184,7 +236,7 @@ public class DataStore //otherwise there's already a top level claim, so this must be a subdivision of that top level claim else { - Claim subdivision = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, "--subdivision--", builderNames, containerNames, accessorNames, managerNames); + Claim subdivision = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, "--subdivision--", builderNames, containerNames, accessorNames, managerNames, null); //make sure there are no other subdivisions overlapping this one @@ -487,7 +539,13 @@ public class DataStore } //otherwise get a unique identifier for the claim which will be used to name the file on disk - String claimID = this.getClaimID(claim); + if(claim.id == null) + { + claim.id = this.nextClaimID; + this.incrementNextClaimID(); + } + + String claimID = String.valueOf(claim.id); BufferedWriter outStream = null; @@ -521,13 +579,41 @@ public class DataStore catch(IOException exception) {} } + private void incrementNextClaimID() + { + this.nextClaimID++; + + BufferedWriter outStream = null; + + try + { + //open the claim's file + File nextClaimIdFile = new File(nextClaimIdFilePath); + nextClaimIdFile.createNewFile(); + outStream = new BufferedWriter(new FileWriter(nextClaimIdFile)); + + outStream.write(String.valueOf(this.nextClaimID)); + } + + //if any problem, log it + catch(Exception e) + { + GriefPrevention.AddLogEntry("Unexpected exception saving next claim ID: " + e.getMessage()); + } + + //close the file + try + { + if(outStream != null) outStream.close(); + } + catch(IOException exception) {} + } + //actually writes claim data to an output stream private void writeClaimData(Claim claim, BufferedWriter outStream) throws IOException { - String claimID = this.getClaimID(claim); - //first line is lesser boundary corner location - outStream.write(claimID); + outStream.write(this.locationToString(claim.getLesserBoundaryCorner())); outStream.newLine(); //second line is greater boundary corner location @@ -721,12 +807,12 @@ public class DataStore } //otherwise, need to update the data store and ensure the claim's file is deleted - String claimID = this.getClaimID(claim); + String claimID = String.valueOf(claim.id); //remove from memory for(int i = 0; i < this.claims.size(); i++) { - if(this.getClaimID(this.claims.get(i)).equals(claimID)) + if(claims.get(i).id.equals(claim.id)) { this.claims.remove(i); claim.inDataStore = false; @@ -751,7 +837,7 @@ public class DataStore PlayerData ownerData = this.getPlayerData(claim.getOwnerName()); for(int i = 0; i < ownerData.claims.size(); i++) { - if(this.getClaimID(ownerData.claims.get(i)).equals(claimID)) + if(ownerData.claims.get(i).id.equals(claim.id)) { ownerData.claims.remove(i); break; @@ -811,7 +897,7 @@ public 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) + 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(); @@ -865,7 +951,8 @@ public class DataStore new String [] {}, new String [] {}, new String [] {}, - new String [] {}); + new String [] {}, + id); newClaim.parent = parent; @@ -960,12 +1047,6 @@ public class DataStore catch(IOException exception){} } - //gets a unique identifier for a claim - private String getClaimID(Claim claim) - { - return this.locationToString(claim.getLesserBoundaryCorner()); - } - //extends a claim to a new depth //respects the max depth config variable public void extendClaim(Claim claim, int newDepth) @@ -1216,7 +1297,7 @@ public class DataStore this.deleteClaim(claim); //try to create this new claim, ignoring the original when checking for overlap - CreateClaimResult result = this.createClaim(claim.getLesserBoundaryCorner().getWorld(), newx1, newx2, newy1, newy2, newz1, newz2, claim.ownerName, claim.parent); + CreateClaimResult result = this.createClaim(claim.getLesserBoundaryCorner().getWorld(), newx1, newx2, newy1, newy2, newz1, newz2, claim.ownerName, claim.parent, claim.id); //if succeeded if(result.succeeded) @@ -1418,7 +1499,7 @@ public class DataStore this.addDefault(defaults, Messages.AdjustGroupBlocksSuccess, "Adjusted bonus claim blocks for players with the {0} permission by {1}. New total: {2}.", "0: permission; 1: adjustment amount; 2: new total bonus"); this.addDefault(defaults, Messages.InvalidPermissionID, "Please specify a player name, or a permission in [brackets].", null); this.addDefault(defaults, Messages.UntrustOwnerOnly, "Only {0} can revoke permissions here.", "0: claim owner's name"); - this.addDefault(defaults, Messages.HowToClaimRegex, "(^|.*\\W)how\\W.*\\Wclaim(\\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.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."); //load the config file FileConfiguration config = YamlConfiguration.loadConfiguration(new File(messagesFilePath)); diff --git a/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java b/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java index 2b90a09..5126e74 100644 --- a/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java @@ -29,11 +29,11 @@ import org.bukkit.entity.Arrow; import org.bukkit.entity.Creature; import org.bukkit.entity.Enderman; import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Monster; import org.bukkit.entity.Player; import org.bukkit.entity.ThrownPotion; -import org.bukkit.entity.Vehicle; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -45,11 +45,13 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.entity.EntityInteractEvent; import org.bukkit.event.entity.ExpBottleEvent; import org.bukkit.event.entity.ItemSpawnEvent; import org.bukkit.event.painting.PaintingBreakByEntityEvent; import org.bukkit.event.painting.PaintingBreakEvent; import org.bukkit.event.painting.PaintingPlaceEvent; +import org.bukkit.event.vehicle.VehicleDamageEvent; //handles events related to entities class EntityEventHandler implements Listener @@ -62,6 +64,26 @@ class EntityEventHandler implements Listener this.dataStore = dataStore; } + //don't allow endermen to change blocks + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onEntityChangeBLock(EntityChangeBlockEvent event) + { + if(!GriefPrevention.instance.config_endermenMoveBlocks && event.getEntityType() == EntityType.ENDERMAN) + { + event.setCancelled(true); + } + } + + //don't allow entities to trample crops + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onEntityInteract(EntityInteractEvent event) + { + if(!GriefPrevention.instance.config_creaturesTrampleCrops && event.getBlock().getType() == Material.SOIL) + { + event.setCancelled(true); + } + } + //when an entity explodes... @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onEntityExplode(EntityExplodeEvent explodeEvent) @@ -147,10 +169,10 @@ class EntityEventHandler implements Listener event.setCancelled(true); return; } - - //otherwise, just apply the limit on total entities per claim + + //otherwise, just apply the limit on total entities per claim (and no spawning in the wilderness!) Claim claim = this.dataStore.getClaimAt(event.getLocation(), false, null); - if(claim != null && claim.allowMoreEntities() != null) + if(claim == null || claim.allowMoreEntities() != null) { event.setCancelled(true); return; @@ -363,8 +385,7 @@ class EntityEventHandler implements Listener if(event instanceof EntityDamageByEntityEvent) { //if the entity is an non-monster creature (remember monsters disqualified above), or a vehicle - if ((subEvent.getEntity() instanceof Creature && GriefPrevention.instance.config_claims_protectCreatures) || - (subEvent.getEntity() instanceof Vehicle && GriefPrevention.instance.config_claims_preventTheft)) + if ((subEvent.getEntity() instanceof Creature && GriefPrevention.instance.config_claims_protectCreatures)) { Claim cachedClaim = null; PlayerData playerData = null; @@ -405,4 +426,75 @@ class EntityEventHandler implements Listener } } } + + //when a vehicle is damaged + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onVehicleDamage (VehicleDamageEvent event) + { + //all of this is anti theft code + if(!GriefPrevention.instance.config_claims_preventTheft) return; + + //determine which player is attacking, if any + Player attacker = null; + Entity damageSource = event.getAttacker(); + if(damageSource instanceof Player) + { + attacker = (Player)damageSource; + } + else if(damageSource instanceof Arrow) + { + Arrow arrow = (Arrow)damageSource; + if(arrow.getShooter() instanceof Player) + { + attacker = (Player)arrow.getShooter(); + } + } + else if(damageSource instanceof ThrownPotion) + { + ThrownPotion potion = (ThrownPotion)damageSource; + if(potion.getShooter() instanceof Player) + { + attacker = (Player)potion.getShooter(); + } + } + + //NOTE: vehicles can be pushed around. + //so unless precautions are taken by the owner, a resourceful thief might find ways to steal anyway + Claim cachedClaim = null; + PlayerData playerData = null; + if(attacker != null) + { + playerData = this.dataStore.getPlayerData(attacker.getName()); + cachedClaim = playerData.lastClaim; + } + + Claim claim = this.dataStore.getClaimAt(event.getVehicle().getLocation(), false, cachedClaim); + + //if it's claimed + if(claim != null) + { + //if damaged by anything other than a player, cancel the event + if(attacker == null) + { + event.setCancelled(true); + } + + //otherwise the player damaging the entity must have permission + else + { + String noContainersReason = claim.allowContainers(attacker); + if(noContainersReason != null) + { + event.setCancelled(true); + GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.NoDamageClaimedEntity, claim.getOwnerName()); + } + + //cache claim for later + if(playerData != null) + { + playerData.lastClaim = claim; + } + } + } + } } diff --git a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java index bf3b3ad..c304533 100644 --- a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java +++ b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java @@ -80,6 +80,8 @@ public class GriefPrevention extends JavaPlugin public int config_claims_trappedCooldownHours; //number of hours between uses of the /trapped command + public Material config_claims_investigationTool; //which material will be used to investigate claims with a right click + public ArrayList config_siege_enabledWorlds; //whether or not /siege is enabled on this server public ArrayList config_siege_blocks; //which blocks will be breakable in siege mode @@ -114,6 +116,9 @@ public class GriefPrevention extends JavaPlugin public boolean config_smartBan; //whether to ban accounts which very likely owned by a banned player + public boolean config_endermenMoveBlocks; //whether or not endermen may move blocks around + public boolean config_creaturesTrampleCrops; //whether or not non-player entities may trample crops + //reference to the economy plugin, if economy integration is enabled public static Economy economy = null; @@ -252,6 +257,23 @@ public class GriefPrevention extends JavaPlugin this.config_smartBan = config.getBoolean("GriefPrevention.SmartBan", true); + this.config_endermenMoveBlocks = config.getBoolean("GriefPrevention.EndermenMoveBlocks", false); + this.config_creaturesTrampleCrops = config.getBoolean("GriefPrevention.CreaturesTrampleCrops", false); + + //default for claim investigation tool + String investigationToolMaterialName = Material.STICK.name(); + + //get investigation tool from config + investigationToolMaterialName = config.getString("GriefPrevention.Claims.InvestigationTool", investigationToolMaterialName); + + //validate investigation tool + this.config_claims_investigationTool = Material.getMaterial(investigationToolMaterialName); + if(this.config_claims_investigationTool == null) + { + GriefPrevention.AddLogEntry("ERROR: Material " + investigationToolMaterialName + " not found. Defaulting to the stick. Please update your config.yml."); + this.config_claims_investigationTool = Material.STICK; + } + //default for siege worlds list ArrayList defaultSiegeWorldNames = new ArrayList(); @@ -339,6 +361,7 @@ public class GriefPrevention extends JavaPlugin config.set("GriefPrevention.Claims.MaximumDepth", this.config_claims_maxDepth); config.set("GriefPrevention.Claims.IdleLimitDays", this.config_claims_expirationDays); config.set("GriefPrevention.Claims.TrappedCommandCooldownHours", this.config_claims_trappedCooldownHours); + config.set("GriefPrevention.Claims.InvestigationTool", this.config_claims_investigationTool.name()); config.set("GriefPrevention.Spam.Enabled", this.config_spam_enabled); config.set("GriefPrevention.Spam.LoginCooldownMinutes", this.config_spam_loginCooldownMinutes); @@ -367,13 +390,16 @@ public class GriefPrevention extends JavaPlugin config.set("GriefPrevention.FireDestroys", this.config_fireDestroys); config.set("GriefPrevention.AddItemsToClaimedChests", this.config_addItemsToClaimedChests); - config.set("GriefPrevention.EavesdropEnabled", this.config_eavesdrop); + config.set("GriefPrevention.EavesdropEnabled", this.config_eavesdrop); config.set("GriefPrevention.SmartBan", this.config_smartBan); config.set("GriefPrevention.Siege.Worlds", siegeEnabledWorldNames); config.set("GriefPrevention.Siege.BreakableBlocks", breakableBlocksList); - + + config.set("GriefPrevention.EndermenMoveBlocks", this.config_endermenMoveBlocks); + config.set("GriefPrevention.CreaturesTrampleCrops", this.config_creaturesTrampleCrops); + try { config.save(DataStore.configFilePath); @@ -1655,6 +1681,9 @@ public class GriefPrevention extends JavaPlugin //if pvp is disabled, do nothing if(!player.getWorld().getPVP()) return; + //if player is in creative mode, do nothing + if(player.getGameMode() == GameMode.CREATIVE) return; + //if anti spawn camping feature is not enabled, do nothing if(!this.config_pvp_protectFreshSpawns) return; diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerData.java b/src/me/ryanhamshire/GriefPrevention/PlayerData.java index f1237f5..4b4c735 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerData.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerData.java @@ -70,7 +70,7 @@ public class PlayerData //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 - public Date lastMessageTimestamp = new Date(); //last time the player sent a chat message or used a monitored slash command + public Date lastMessageTimestamp = new Date(); //last time the player sent a chat message or used a monitored slash command public int spamCount = 0; //number of consecutive "spams" public boolean spamWarned = false; //whether the player recently received a warning diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java index 18b63cd..2975094 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java @@ -126,6 +126,14 @@ class PlayerEventHandler implements Listener boolean spam = false; boolean muted = false; + //single-character messages will not be sent + if(message.length() == 1) + { + playerData.spamCount++; + spam = true; + muted = true; + } + //check message content and timing long millisecondsSinceLastMessage = (new Date()).getTime() - playerData.lastMessageTimestamp.getTime(); @@ -137,8 +145,8 @@ class PlayerEventHandler implements Listener spam = true; } - //if it's very similar to the last message - if(!muted && this.stringsAreSimilar(message, playerData.lastMessage)) + //if it's very similar to the last message and less than 10 seconds have passed + if(!muted && this.stringsAreSimilar(message, playerData.lastMessage) && millisecondsSinceLastMessage < 10000) { playerData.spamCount++; spam = true; @@ -148,7 +156,7 @@ class PlayerEventHandler implements Listener //filter IP addresses if(!muted && !(event instanceof PlayerCommandPreprocessEvent)) { - Pattern ipAddressPattern = Pattern.compile("\\d+\\.\\d+\\.\\d+\\.\\d+"); + 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()); //if it looks like an IP address @@ -515,6 +523,9 @@ class PlayerEventHandler implements Listener { if(player.getHealth() > 0) player.setHealth(0); //might already be zero from above, this avoids a double death message } + + //drop data about this player + this.dataStore.clearCachedPlayerData(player.getName()); } //determines whether or not a login or logout notification should be silenced, depending on how many there have been in the last minute @@ -993,8 +1004,8 @@ class PlayerEventHandler implements Listener return; } - //if it's stick or arrow, he's investigating a claim - else if(materialInHand == Material.STICK) + //if he's investigating a claim + else if(materialInHand == GriefPrevention.instance.config_claims_investigationTool) { //air indicates too far away if(clickedBlockType == Material.AIR) @@ -1316,7 +1327,7 @@ class PlayerEventHandler implements Listener Claim newClaim = new Claim( new Location(oldClaim.getLesserBoundaryCorner().getWorld(), newx1, newy1, newz1), new Location(oldClaim.getLesserBoundaryCorner().getWorld(), newx2, newy2, newz2), - "", new String[]{}, new String[]{}, new String[]{}, new String[]{}); + "", new String[]{}, new String[]{}, new String[]{}, new String[]{}, null); //if the new claim is smaller if(!newClaim.contains(oldClaim.getLesserBoundaryCorner(), true, false) || !newClaim.contains(oldClaim.getGreaterBoundaryCorner(), true, false)) @@ -1423,7 +1434,8 @@ class PlayerEventHandler implements Listener playerData.lastShovelLocation.getBlockY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance, clickedBlock.getY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance, playerData.lastShovelLocation.getBlockZ(), clickedBlock.getZ(), "--subdivision--", //owner name is not used for subdivisions - playerData.claimSubdividing); + playerData.claimSubdividing, + null); //if it didn't succeed, tell the player why if(!result.succeeded) @@ -1532,7 +1544,7 @@ class PlayerEventHandler implements Listener lastShovelLocation.getBlockY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance, clickedBlock.getY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance, lastShovelLocation.getBlockZ(), clickedBlock.getZ(), playerName, - null); + null, null); //if it didn't succeed, tell the player why if(!result.succeeded) diff --git a/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java b/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java index e5d5fa2..047b6a9 100644 --- a/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java +++ b/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java @@ -90,7 +90,7 @@ class RestoreNatureExecutionTask implements Runnable } //show visualization to player - Claim claim = new Claim(lesserCorner, greaterCorner, "", new String[] {}, new String[] {}, new String[] {}, new String[] {}); + Claim claim = new Claim(lesserCorner, greaterCorner, "", new String[] {}, new String[] {}, new String[] {}, new String[] {}, null); Visualization visualization = Visualization.FromClaim(claim, player.getLocation().getBlockY(), VisualizationType.RestoreNature); Visualization.Apply(player, visualization); }