From 311db2052243e6b46fd3560fe94b425e4bda6b60 Mon Sep 17 00:00:00 2001 From: Ryan Hamshire Date: Thu, 12 Jul 2012 19:44:22 -0700 Subject: [PATCH] 5.1 --- plugin.yml | 2 +- .../GriefPrevention/BlockEventHandler.java | 2 +- .../ryanhamshire/GriefPrevention/Claim.java | 9 + .../GriefPrevention/DataStore.java | 579 +----------------- .../DeliverClaimBlocksTask.java | 4 +- .../EquipShovelProcessingTask.java | 3 +- .../GriefPrevention/FlatFileDataStore.java | 574 +++++++++++++++++ .../GriefPrevention/GriefPrevention.java | 61 +- .../GriefPrevention/Messages.java | 2 +- .../GriefPrevention/PlayerEventHandler.java | 48 +- .../RestoreNatureExecutionTask.java | 2 +- .../GriefPrevention/Visualization.java | 29 +- 12 files changed, 727 insertions(+), 588 deletions(-) create mode 100644 src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java diff --git a/plugin.yml b/plugin.yml index 84e4a7b..578d327 100644 --- a/plugin.yml +++ b/plugin.yml @@ -1,7 +1,7 @@ name: GriefPrevention main: me.ryanhamshire.GriefPrevention.GriefPrevention softdepend: [Vault, Multiverse-Core] -version: 5.0 +version: 5.1 commands: abandonclaim: description: Deletes a claim. diff --git a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java index cd0d97a..af80252 100644 --- a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java @@ -297,7 +297,7 @@ public class BlockEventHandler implements Listener //show the player the protected area Claim newClaim = this.dataStore.getClaimAt(block.getLocation(), false, null); - Visualization visualization = Visualization.FromClaim(newClaim, block.getY(), VisualizationType.Claim); + Visualization visualization = Visualization.FromClaim(newClaim, block.getY(), VisualizationType.Claim, player.getLocation()); Visualization.Apply(player, visualization); } diff --git a/src/me/ryanhamshire/GriefPrevention/Claim.java b/src/me/ryanhamshire/GriefPrevention/Claim.java index dc05b0e..57084ba 100644 --- a/src/me/ryanhamshire/GriefPrevention/Claim.java +++ b/src/me/ryanhamshire/GriefPrevention/Claim.java @@ -118,6 +118,9 @@ public class Claim //don't do this for administrative claims if(this.isAdminClaim()) return; + //don't do it for very large claims + if(this.getArea() > 10000) return; + Location lesser = this.getLesserBoundaryCorner(); Location greater = this.getGreaterBoundaryCorner(); @@ -154,6 +157,9 @@ public class Claim Location lesser = this.getLesserBoundaryCorner(); Location greater = this.getGreaterBoundaryCorner(); + //don't bother for very large claims, too expensive + if(this.getArea() > 10000) return false; + int seaLevel = 0; //clean up all fluids in the end //respect sea level in normal worlds @@ -695,6 +701,9 @@ public class Claim //this rule only applies to creative mode worlds if(!GriefPrevention.instance.creativeRulesApply(this.getLesserBoundaryCorner())) return null; + //admin claims aren't restricted + if(this.isAdminClaim()) return null; + //determine maximum allowable entity count, based on claim size int maxEntities = this.getArea() / 50; if(maxEntities == 0) return GriefPrevention.instance.dataStore.getMessage(Messages.ClaimTooSmallForEntities); diff --git a/src/me/ryanhamshire/GriefPrevention/DataStore.java b/src/me/ryanhamshire/GriefPrevention/DataStore.java index 585c769..e74b7d0 100644 --- a/src/me/ryanhamshire/GriefPrevention/DataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DataStore.java @@ -19,9 +19,6 @@ package me.ryanhamshire.GriefPrevention; import java.io.*; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.*; import org.bukkit.*; @@ -31,13 +28,13 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; //singleton class which manages all GriefPrevention data (except for config options) -public class DataStore +public abstract class DataStore { //in-memory cache for player data - private HashMap playerNameToPlayerDataMap = new HashMap(); + protected HashMap playerNameToPlayerDataMap = new HashMap(); //in-memory cache for group (permission-based) data - private HashMap permissionToBonusBlocksMap = new HashMap(); + protected HashMap permissionToBonusBlocksMap = new HashMap(); //in-memory cache for claim data ArrayList claims = new ArrayList(); @@ -49,227 +46,18 @@ public class DataStore 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"; + protected final static String dataLayerFolderPath = "plugins" + File.separator + "GriefPreventionData"; 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! + abstract void initialize(); + DataStore() { - //ensure data folders exist - new File(playerDataFolderPath).mkdirs(); - new File(claimDataFolderPath).mkdirs(); + this.initialize(); - //load group data into memory - File playerDataFolder = new File(playerDataFolderPath); - File [] files = playerDataFolder.listFiles(); - for(int i = 0; i < files.length; i++) - { - File file = files[i]; - if(!file.isFile()) continue; //avoids folders - - //all group data files start with an underscore. ignoring the rest, which are player data files. - if(!file.getName().startsWith("$")) continue; - - String groupName = file.getName().substring(1); - if(groupName == null || groupName.isEmpty()) continue; //defensive coding, avoid unlikely cases - - BufferedReader inStream = null; - try - { - inStream = new BufferedReader(new FileReader(file.getAbsolutePath())); - String line = inStream.readLine(); - - int groupBonusBlocks = Integer.parseInt(line); - - this.permissionToBonusBlocksMap.put(groupName, groupBonusBlocks); - } - catch(Exception e) - { - GriefPrevention.AddLogEntry("Unable to load group bonus block data from file \"" + file.getName() + "\": " + e.getMessage()); - } - - try - { - if(inStream != null) inStream.close(); - } - catch(IOException exception) {} - } - - //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; - - for(int i = 0; i < files.length; i++) - { - 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 - { - Claim topLevelClaim = null; - - inStream = new BufferedReader(new FileReader(files[i].getAbsolutePath())); - String line = inStream.readLine(); - - while(line != null) - { - //first line is lesser boundary corner location - Location lesserBoundaryCorner = this.locationFromString(line); - - //second line is greater boundary corner location - line = inStream.readLine(); - Location greaterBoundaryCorner = this.locationFromString(line); - - //third line is owner name - line = inStream.readLine(); - String ownerName = line; - - //fourth line is list of builders - line = inStream.readLine(); - String [] builderNames = line.split(";"); - - //fifth line is list of players who can access containers - line = inStream.readLine(); - String [] containerNames = line.split(";"); - - //sixth line is list of players who can use buttons and switches - line = inStream.readLine(); - String [] accessorNames = line.split(";"); - - //seventh line is list of players who can grant permissions - line = inStream.readLine(); - if(line == null) line = ""; - String [] managerNames = line.split(";"); - - //skip any remaining extra lines, until the "===" string, indicating the end of this claim or subdivision - line = inStream.readLine(); - while(line != null && !line.contains("==========")) - line = inStream.readLine(); - - //build a claim instance from those data - //if this is the first claim loaded from this file, it's the top level claim - if(topLevelClaim == null) - { - //instantiate - 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); - - //if there is such a claim, delete this file and move on to the next - if(conflictClaim != null) - { - inStream.close(); - files[i].delete(); - line = null; - continue; - } - - //otherwise, add this claim to the claims collection - else - { - topLevelClaim.modifiedDate = new Date(files[i].lastModified()); - int j = 0; - while(j < this.claims.size() && !this.claims.get(j).greaterThan(topLevelClaim)) j++; - if(j < this.claims.size()) - this.claims.add(j, topLevelClaim); - else - this.claims.add(this.claims.size(), topLevelClaim); - topLevelClaim.inDataStore = true; - } - } - - //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, null); - - //make sure there are no other subdivisions overlapping this one - - subdivision.modifiedDate = new Date(files[i].lastModified()); - subdivision.parent = topLevelClaim; - topLevelClaim.children.add(subdivision); - subdivision.inDataStore = true; - } - - //move up to the first line in the next subdivision - line = inStream.readLine(); - } - - inStream.close(); - - loadedClaimCount++; - } - - //if there's any problem with the file's content, log an error message and skip it - catch(Exception e) - { - GriefPrevention.AddLogEntry("Unable to load data for claim \"" + files[i].getName() + "\": " + e.getMessage()); - } - - try - { - if(inStream != null) inStream.close(); - } - catch(IOException exception) {} - } - } - - GriefPrevention.AddLogEntry(loadedClaimCount + " total claims loaded."); + GriefPrevention.AddLogEntry(this.claims.size() + " total claims loaded."); //make a list of players who own claims Vector playerNames = new Vector(); @@ -377,39 +165,14 @@ public class DataStore currentValue += amount; this.permissionToBonusBlocksMap.put(groupName, currentValue); - //write changes to file to ensure they don't get lost - BufferedWriter outStream = null; - try - { - //open the group's file - File groupDataFile = new File(playerDataFolderPath + File.separator + "$" + groupName); - groupDataFile.createNewFile(); - outStream = new BufferedWriter(new FileWriter(groupDataFile)); - - //first line is number of bonus blocks - outStream.write(currentValue.toString()); - outStream.newLine(); - } - - //if any problem, log it - catch(Exception e) - { - GriefPrevention.AddLogEntry("Unexpected exception saving data for group \"" + groupName + "\": " + e.getMessage()); - } - - try - { - //close the file - if(outStream != null) - { - outStream.close(); - } - } - catch(IOException exception){} + //write changes to storage to ensure they don't get lost + this.saveGroupBonusBlocks(groupName, currentValue); return currentValue; } + abstract void saveGroupBonusBlocks(String groupName, int amount); + public void changeClaimOwner(Claim claim, String newOwnerName) throws Exception { //if it's a subdivision, throw an exception @@ -480,9 +243,9 @@ public class DataStore this.saveClaim(newClaim); } - //turns a location into a string, useful in data files and data file names + //turns a location into a string, useful in data storage private String locationStringDelimiter = ";"; - private String locationToString(Location location) + String locationToString(Location location) { StringBuilder stringBuilder = new StringBuilder(location.getWorld().getName()); stringBuilder.append(locationStringDelimiter); @@ -496,7 +259,7 @@ public class DataStore } //turns a location string back into a location - private Location locationFromString(String string) throws Exception + Location locationFromString(String string) throws Exception { //split the input string on the space String [] elements = string.split(locationStringDelimiter); @@ -527,7 +290,7 @@ public class DataStore return new Location(world, x, y, z); } - //does the work of actually writing a claim to file + //saves any changes to a claim to secondary storage public void saveClaim(Claim claim) { //subdivisions don't save to their own files, but instead live in their parent claim's file @@ -545,247 +308,27 @@ public class DataStore this.incrementNextClaimID(); } - String claimID = String.valueOf(claim.id); - - BufferedWriter outStream = null; - - try - { - //open the claim's file - File claimFile = new File(claimDataFolderPath + File.separator + claimID); - claimFile.createNewFile(); - outStream = new BufferedWriter(new FileWriter(claimFile)); - - this.writeClaimData(claim, outStream); - - for(int i = 0; i < claim.children.size(); i++) - { - //see below for details of writing data to file - this.writeClaimData(claim.children.get(i), outStream); - } - } - - //if any problem, log it - catch(Exception e) - { - GriefPrevention.AddLogEntry("PopulationDensity: Unexpected exception saving data for claim \"" + claimID + "\": " + e.getMessage()); - } - - //close the file - try - { - if(outStream != null) outStream.close(); - } - catch(IOException exception) {} + this.writeClaimToStorage(claim); } - 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 - { - //first line is lesser boundary corner location - outStream.write(this.locationToString(claim.getLesserBoundaryCorner())); - outStream.newLine(); - - //second line is greater boundary corner location - outStream.write(this.locationToString(claim.getGreaterBoundaryCorner())); - outStream.newLine(); - - //third line is owner name - outStream.write(claim.ownerName); - outStream.newLine(); - - ArrayList builders = new ArrayList(); - ArrayList containers = new ArrayList(); - ArrayList accessors = new ArrayList(); - ArrayList managers = new ArrayList(); - - claim.getPermissions(builders, containers, accessors, managers); - - //fourth line is list of players with build permission - for(int i = 0; i < builders.size(); i++) - { - outStream.write(builders.get(i) + ";"); - } - outStream.newLine(); - - //fifth line is list of players with container permission - for(int i = 0; i < containers.size(); i++) - { - outStream.write(containers.get(i) + ";"); - } - outStream.newLine(); - - //sixth line is list of players with access permission - for(int i = 0; i < accessors.size(); i++) - { - outStream.write(accessors.get(i) + ";"); - } - outStream.newLine(); - - //seventh line is list of players who may grant permissions for others - for(int i = 0; i < managers.size(); i++) - { - outStream.write(managers.get(i) + ";"); - } - outStream.newLine(); - - //cap each claim with "==========" - outStream.write("=========="); - outStream.newLine(); - } + abstract void writeClaimToStorage(Claim claim); - //retrieves player data from memory or file, as necessary + //increments the claim ID and updates secondary storage to be sure it's saved + abstract void incrementNextClaimID(); + + //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) { //first, look in memory PlayerData playerData = this.playerNameToPlayerDataMap.get(playerName); - //if not there, look on disk + //if not there, look in secondary storage if(playerData == null) { - File playerFile = new File(playerDataFolderPath + File.separator + playerName); - - playerData = new PlayerData(); + playerData = this.getPlayerDataFromStorage(playerName); playerData.playerName = playerName; - //if it doesn't exist as a file - if(!playerFile.exists()) - { - //create a file with defaults - this.savePlayerData(playerName, playerData); - } - - //otherwise, read the file - else - { - BufferedReader inStream = null; - try - { - inStream = new BufferedReader(new FileReader(playerFile.getAbsolutePath())); - - //first line is last login timestamp - String lastLoginTimestampString = inStream.readLine(); - - //convert that to a date and store it - DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss"); - try - { - playerData.lastLogin = dateFormat.parse(lastLoginTimestampString); - } - catch(ParseException parseException) - { - GriefPrevention.AddLogEntry("Unable to load last login for \"" + playerFile.getName() + "\"."); - playerData.lastLogin = null; - } - - //second line is accrued claim blocks - String accruedBlocksString = inStream.readLine(); - - //convert that to a number and store it - playerData.accruedClaimBlocks = Integer.parseInt(accruedBlocksString); - - //third line is any bonus claim blocks granted by administrators - String bonusBlocksString = inStream.readLine(); - - //convert that to a number and store it - playerData.bonusClaimBlocks = Integer.parseInt(bonusBlocksString); - - //fourth line is a double-semicolon-delimited list of claims, which is currently ignored - //String claimsString = inStream.readLine(); - inStream.readLine(); - /* - if(claimsString != null && claimsString.length() > 0) - { - String [] claimsStrings = claimsString.split(";;"); - boolean missingClaim = false; - - //search for each claim mentioned in the file - for(int i = 0; i < claimsStrings.length; i++) - { - String claimID = claimsStrings[i]; - if(claimID != null) - { - Claim claim = this.getClaimAt(this.locationFromString(claimID), true, null); - - //if the referenced claim exists, add it to the player data instance for later reference - if(claim != null) - { - playerData.claims.add(claim); - } - - //if the claim doesn't seem to exist anymore, plan to drop the reference from the file - else - { - missingClaim = true; - } - } - } - - //if any referenced claims no longer exist, write the player data back to file to eliminate those references - if(missingClaim) - { - this.savePlayerData(playerName, playerData); - } - } - */ - - //find all the claims belonging to this player and note them for future reference - for(int i = 0; i < this.claims.size(); i++) - { - Claim claim = this.claims.get(i); - if(claim.ownerName.equals(playerName)) - { - playerData.claims.add(claim); - } - } - - inStream.close(); - } - - //if there's any problem with the file's content, log an error message - catch(Exception e) - { - GriefPrevention.AddLogEntry("Unable to load data for player \"" + playerName + "\": " + e.getMessage()); - } - - try - { - if(inStream != null) inStream.close(); - } - catch(IOException exception) {} - } - //shove that new player data into the hash map cache this.playerNameToPlayerDataMap.put(playerName, playerData); } @@ -794,6 +337,8 @@ public class DataStore return this.playerNameToPlayerDataMap.get(playerName); } + abstract PlayerData getPlayerDataFromStorage(String playerName); + //deletes a claim or subdivision public void deleteClaim(Claim claim) { @@ -806,9 +351,6 @@ public class DataStore return; } - //otherwise, need to update the data store and ensure the claim's file is deleted - String claimID = String.valueOf(claim.id); - //remove from memory for(int i = 0; i < this.claims.size(); i++) { @@ -824,12 +366,8 @@ public class DataStore } } - //remove from disk - File claimFile = new File(claimDataFolderPath + File.separator + claimID); - if(claimFile.exists() && !claimFile.delete()) - { - GriefPrevention.AddLogEntry("Error: Unable to delete claim file \"" + claimFile.getAbsolutePath() + "\"."); - } + //remove from secondary storage + this.deleteClaimFromSecondaryStorage(claim); //update player data, except for administrative claims, which have no owner if(!claim.isAdminClaim()) @@ -847,6 +385,8 @@ public class DataStore } } + abstract void deleteClaimFromSecondaryStorage(Claim claim); + //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 @@ -990,62 +530,8 @@ public class DataStore return result; } - //saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them - public void savePlayerData(String playerName, PlayerData playerData) - { - //never save data for the "administrative" account. an empty string for claim owner indicates an administrative claim - if(playerName.length() == 0) return; - - BufferedWriter outStream = null; - try - { - //open the player's file - File playerDataFile = new File(playerDataFolderPath + File.separator + playerName); - playerDataFile.createNewFile(); - outStream = new BufferedWriter(new FileWriter(playerDataFile)); - - //first line is last login timestamp - if(playerData.lastLogin == null)playerData.lastLogin = new Date(); - DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss"); - outStream.write(dateFormat.format(playerData.lastLogin)); - outStream.newLine(); - - //second line is accrued claim blocks - outStream.write(String.valueOf(playerData.accruedClaimBlocks)); - outStream.newLine(); - - //third line is bonus claim blocks - outStream.write(String.valueOf(playerData.bonusClaimBlocks)); - outStream.newLine(); - - //fourth line is a double-semicolon-delimited list of claims - if(playerData.claims.size() > 0) - { - outStream.write(this.locationToString(playerData.claims.get(0).getLesserBoundaryCorner())); - for(int i = 1; i < playerData.claims.size(); i++) - { - outStream.write(";;" + this.locationToString(playerData.claims.get(i).getLesserBoundaryCorner())); - } - } - outStream.newLine(); - } - - //if any problem, log it - catch(Exception e) - { - GriefPrevention.AddLogEntry("PopulationDensity: Unexpected exception saving data for player \"" + playerName + "\": " + e.getMessage()); - } - - try - { - //close the file - if(outStream != null) - { - outStream.close(); - } - } - catch(IOException exception){} - } + //saves changes to player data to secondary storage. MUST be called after you're done making changes, otherwise a reload will lose them + public abstract void savePlayerData(String playerName, PlayerData playerData); //extends a claim to a new depth //respects the max depth config variable @@ -1500,6 +986,7 @@ public class DataStore 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.*\\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); //load the config file FileConfiguration config = YamlConfiguration.loadConfiguration(new File(messagesFilePath)); diff --git a/src/me/ryanhamshire/GriefPrevention/DeliverClaimBlocksTask.java b/src/me/ryanhamshire/GriefPrevention/DeliverClaimBlocksTask.java index 6837ea5..92e4aea 100644 --- a/src/me/ryanhamshire/GriefPrevention/DeliverClaimBlocksTask.java +++ b/src/me/ryanhamshire/GriefPrevention/DeliverClaimBlocksTask.java @@ -56,7 +56,9 @@ class DeliverClaimBlocksTask implements Runnable playerData.accruedClaimBlocks = GriefPrevention.instance.config_claims_maxAccruedBlocks; } - dataStore.savePlayerData(player.getName(), playerData); + //intentionally NOT saving data here to reduce overall secondary storage access frequency + //many other operations will cause this players data to save, including his eventual logout + //dataStore.savePlayerData(player.getName(), playerData); } } catch(Exception e) { } diff --git a/src/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java b/src/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java index a79870c..094708e 100644 --- a/src/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java +++ b/src/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java @@ -18,7 +18,6 @@ package me.ryanhamshire.GriefPrevention; -import org.bukkit.Material; import org.bukkit.entity.Player; //tells a player about how many claim blocks he has, etc @@ -41,7 +40,7 @@ class EquipShovelProcessingTask implements Runnable if(!player.isOnline()) return; //if he's not holding the golden shovel anymore, do nothing - if(player.getItemInHand().getType() != Material.GOLD_SPADE) return; + if(player.getItemInHand().getType() != GriefPrevention.instance.config_claims_modificationTool) return; PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getName()); diff --git a/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java b/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java new file mode 100644 index 0000000..b2b11e0 --- /dev/null +++ b/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java @@ -0,0 +1,574 @@ +/* + GriefPrevention Server Plugin for Minecraft + Copyright (C) 2012 Ryan Hamshire + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package me.ryanhamshire.GriefPrevention; + +import java.io.*; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +import org.bukkit.*; + +//manages data stored in the file system +public class FlatFileDataStore extends DataStore +{ + private final static String playerDataFolderPath = dataLayerFolderPath + File.separator + "PlayerData"; + private final static String claimDataFolderPath = dataLayerFolderPath + File.separator + "ClaimData"; + private final static String nextClaimIdFilePath = claimDataFolderPath + File.separator + "_nextClaimID"; + + static boolean hasData() + { + File playerDataFolder = new File(playerDataFolderPath); + File claimsDataFolder = new File(claimDataFolderPath); + + return playerDataFolder.exists() || claimsDataFolder.exists(); + } + + //initialization! + FlatFileDataStore() + { + super(); + } + + @Override + void initialize() + { + //ensure data folders exist + new File(playerDataFolderPath).mkdirs(); + new File(claimDataFolderPath).mkdirs(); + + //load group data into memory + File playerDataFolder = new File(playerDataFolderPath); + File [] files = playerDataFolder.listFiles(); + for(int i = 0; i < files.length; i++) + { + File file = files[i]; + if(!file.isFile()) continue; //avoids folders + + //all group data files start with a dollar sign. ignoring the rest, which are player data files. + if(!file.getName().startsWith("$")) continue; + + String groupName = file.getName().substring(1); + if(groupName == null || groupName.isEmpty()) continue; //defensive coding, avoid unlikely cases + + BufferedReader inStream = null; + try + { + inStream = new BufferedReader(new FileReader(file.getAbsolutePath())); + String line = inStream.readLine(); + + int groupBonusBlocks = Integer.parseInt(line); + + this.permissionToBonusBlocksMap.put(groupName, groupBonusBlocks); + } + catch(Exception e) + { + GriefPrevention.AddLogEntry("Unable to load group bonus block data from file \"" + file.getName() + "\": " + e.getMessage()); + } + + try + { + if(inStream != null) inStream.close(); + } + catch(IOException exception) {} + } + + //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) {} + } + + //load claims data into memory + //get a list of all the files in the claims data folder + File claimDataFolder = new File(claimDataFolderPath); + files = claimDataFolder.listFiles(); + + for(int i = 0; i < files.length; i++) + { + 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 + { + Claim topLevelClaim = null; + + inStream = new BufferedReader(new FileReader(files[i].getAbsolutePath())); + String line = inStream.readLine(); + + while(line != null) + { + //first line is lesser boundary corner location + Location lesserBoundaryCorner = this.locationFromString(line); + + //second line is greater boundary corner location + line = inStream.readLine(); + Location greaterBoundaryCorner = this.locationFromString(line); + + //third line is owner name + line = inStream.readLine(); + String ownerName = line; + + //fourth line is list of builders + line = inStream.readLine(); + String [] builderNames = line.split(";"); + + //fifth line is list of players who can access containers + line = inStream.readLine(); + String [] containerNames = line.split(";"); + + //sixth line is list of players who can use buttons and switches + line = inStream.readLine(); + String [] accessorNames = line.split(";"); + + //seventh line is list of players who can grant permissions + line = inStream.readLine(); + if(line == null) line = ""; + String [] managerNames = line.split(";"); + + //skip any remaining extra lines, until the "===" string, indicating the end of this claim or subdivision + line = inStream.readLine(); + while(line != null && !line.contains("==========")) + line = inStream.readLine(); + + //build a claim instance from those data + //if this is the first claim loaded from this file, it's the top level claim + if(topLevelClaim == null) + { + //instantiate + 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); + + //if there is such a claim, delete this file and move on to the next + if(conflictClaim != null) + { + inStream.close(); + files[i].delete(); + line = null; + continue; + } + + //otherwise, add this claim to the claims collection + else + { + topLevelClaim.modifiedDate = new Date(files[i].lastModified()); + int j = 0; + while(j < this.claims.size() && !this.claims.get(j).greaterThan(topLevelClaim)) j++; + if(j < this.claims.size()) + this.claims.add(j, topLevelClaim); + else + this.claims.add(this.claims.size(), topLevelClaim); + topLevelClaim.inDataStore = true; + } + } + + //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, null); + + //make sure there are no other subdivisions overlapping this one + + subdivision.modifiedDate = new Date(files[i].lastModified()); + subdivision.parent = topLevelClaim; + topLevelClaim.children.add(subdivision); + subdivision.inDataStore = true; + } + + //move up to the first line in the next subdivision + line = inStream.readLine(); + } + + inStream.close(); + } + + //if there's any problem with the file's content, log an error message and skip it + catch(Exception e) + { + GriefPrevention.AddLogEntry("Unable to load data for claim \"" + files[i].getName() + "\": " + e.getMessage()); + } + + try + { + if(inStream != null) inStream.close(); + } + catch(IOException exception) {} + } + } + } + + @Override + void writeClaimToStorage(Claim claim) + { + String claimID = String.valueOf(claim.id); + + BufferedWriter outStream = null; + + try + { + //open the claim's file + File claimFile = new File(claimDataFolderPath + File.separator + claimID); + claimFile.createNewFile(); + outStream = new BufferedWriter(new FileWriter(claimFile)); + + //write top level claim data to the file + this.writeClaimData(claim, outStream); + + //for each subdivision + for(int i = 0; i < claim.children.size(); i++) + { + //write the subdivision's data to the file + this.writeClaimData(claim.children.get(i), outStream); + } + } + + //if any problem, log it + catch(Exception e) + { + GriefPrevention.AddLogEntry("Unexpected exception saving data for claim \"" + claimID + "\": " + 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 + { + //first line is lesser boundary corner location + outStream.write(this.locationToString(claim.getLesserBoundaryCorner())); + outStream.newLine(); + + //second line is greater boundary corner location + outStream.write(this.locationToString(claim.getGreaterBoundaryCorner())); + outStream.newLine(); + + //third line is owner name + outStream.write(claim.ownerName); + outStream.newLine(); + + ArrayList builders = new ArrayList(); + ArrayList containers = new ArrayList(); + ArrayList accessors = new ArrayList(); + ArrayList managers = new ArrayList(); + + claim.getPermissions(builders, containers, accessors, managers); + + //fourth line is list of players with build permission + for(int i = 0; i < builders.size(); i++) + { + outStream.write(builders.get(i) + ";"); + } + outStream.newLine(); + + //fifth line is list of players with container permission + for(int i = 0; i < containers.size(); i++) + { + outStream.write(containers.get(i) + ";"); + } + outStream.newLine(); + + //sixth line is list of players with access permission + for(int i = 0; i < accessors.size(); i++) + { + outStream.write(accessors.get(i) + ";"); + } + outStream.newLine(); + + //seventh line is list of players who may grant permissions for others + for(int i = 0; i < managers.size(); i++) + { + outStream.write(managers.get(i) + ";"); + } + outStream.newLine(); + + //cap each claim with "==========" + outStream.write("=========="); + outStream.newLine(); + } + + //deletes a top level claim from the file system + @Override + void deleteClaimFromSecondaryStorage(Claim claim) + { + String claimID = String.valueOf(claim.id); + + //remove from disk + File claimFile = new File(claimDataFolderPath + File.separator + claimID); + if(claimFile.exists() && !claimFile.delete()) + { + GriefPrevention.AddLogEntry("Error: Unable to delete claim file \"" + claimFile.getAbsolutePath() + "\"."); + } + } + + @Override + PlayerData getPlayerDataFromStorage(String playerName) + { + File playerFile = new File(playerDataFolderPath + File.separator + playerName); + + PlayerData playerData = new PlayerData(); + playerData.playerName = playerName; + + //if it doesn't exist as a file + if(!playerFile.exists()) + { + //create a file with defaults + this.savePlayerData(playerName, playerData); + } + + //otherwise, read the file + else + { + BufferedReader inStream = null; + try + { + inStream = new BufferedReader(new FileReader(playerFile.getAbsolutePath())); + + //first line is last login timestamp + String lastLoginTimestampString = inStream.readLine(); + + //convert that to a date and store it + DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss"); + try + { + playerData.lastLogin = dateFormat.parse(lastLoginTimestampString); + } + catch(ParseException parseException) + { + GriefPrevention.AddLogEntry("Unable to load last login for \"" + playerFile.getName() + "\"."); + playerData.lastLogin = null; + } + + //second line is accrued claim blocks + String accruedBlocksString = inStream.readLine(); + + //convert that to a number and store it + playerData.accruedClaimBlocks = Integer.parseInt(accruedBlocksString); + + //third line is any bonus claim blocks granted by administrators + String bonusBlocksString = inStream.readLine(); + + //convert that to a number and store it + playerData.bonusClaimBlocks = Integer.parseInt(bonusBlocksString); + + //fourth line is a double-semicolon-delimited list of claims, which is currently ignored + //String claimsString = inStream.readLine(); + inStream.readLine(); + + //find all the claims belonging to this player and note them for future reference + for(int i = 0; i < this.claims.size(); i++) + { + Claim claim = this.claims.get(i); + if(claim.ownerName.equals(playerName)) + { + playerData.claims.add(claim); + } + } + + inStream.close(); + } + + //if there's any problem with the file's content, log an error message + catch(Exception e) + { + GriefPrevention.AddLogEntry("Unable to load data for player \"" + playerName + "\": " + e.getMessage()); + } + + try + { + if(inStream != null) inStream.close(); + } + catch(IOException exception) {} + } + + return playerData; + } + + //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) + { + //never save data for the "administrative" account. an empty string for claim owner indicates administrative account + if(playerName.length() == 0) return; + + BufferedWriter outStream = null; + try + { + //open the player's file + File playerDataFile = new File(playerDataFolderPath + File.separator + playerName); + playerDataFile.createNewFile(); + outStream = new BufferedWriter(new FileWriter(playerDataFile)); + + //first line is last login timestamp + if(playerData.lastLogin == null)playerData.lastLogin = new Date(); + DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss"); + outStream.write(dateFormat.format(playerData.lastLogin)); + outStream.newLine(); + + //second line is accrued claim blocks + outStream.write(String.valueOf(playerData.accruedClaimBlocks)); + outStream.newLine(); + + //third line is bonus claim blocks + outStream.write(String.valueOf(playerData.bonusClaimBlocks)); + outStream.newLine(); + + //fourth line is a double-semicolon-delimited list of claims + if(playerData.claims.size() > 0) + { + outStream.write(this.locationToString(playerData.claims.get(0).getLesserBoundaryCorner())); + for(int i = 1; i < playerData.claims.size(); i++) + { + outStream.write(";;" + this.locationToString(playerData.claims.get(i).getLesserBoundaryCorner())); + } + } + outStream.newLine(); + } + + //if any problem, log it + catch(Exception e) + { + GriefPrevention.AddLogEntry("GriefPrevention: Unexpected exception saving data for player \"" + playerName + "\": " + e.getMessage()); + } + + try + { + //close the file + if(outStream != null) + { + outStream.close(); + } + } + catch(IOException exception){} + } + + @Override + void incrementNextClaimID() + { + //increment in memory + this.nextClaimID++; + + BufferedWriter outStream = null; + + try + { + //open the file and write the new value + 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) {} + } + + //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) + { + //write changes to file to ensure they don't get lost + BufferedWriter outStream = null; + try + { + //open the group's file + File groupDataFile = new File(playerDataFolderPath + File.separator + "$" + groupName); + groupDataFile.createNewFile(); + outStream = new BufferedWriter(new FileWriter(groupDataFile)); + + //first line is number of bonus blocks + outStream.write(String.valueOf(currentValue)); + outStream.newLine(); + } + + //if any problem, log it + catch(Exception e) + { + GriefPrevention.AddLogEntry("Unexpected exception saving data for group \"" + groupName + "\": " + e.getMessage()); + } + + try + { + //close the file + if(outStream != null) + { + outStream.close(); + } + } + catch(IOException exception){} + } +} diff --git a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java index 1965a20..6423226 100644 --- a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java +++ b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java @@ -79,9 +79,12 @@ public class GriefPrevention extends JavaPlugin public int config_claims_claimsExtendIntoGroundDistance; //how far below the shoveled block a new claim will reach public int config_claims_minSize; //minimum width and height for non-admin claims + public boolean config_claims_noBuildOutsideClaims; //whether players can build in survival worlds outside their claimed areas + 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 Material config_claims_modificationTool; //which material will be used to create/resize 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 @@ -227,6 +230,7 @@ public class GriefPrevention extends JavaPlugin this.config_claims_maxDepth = config.getInt("GriefPrevention.Claims.MaximumDepth", 0); 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_spam_enabled = config.getBoolean("GriefPrevention.Spam.Enabled", true); this.config_spam_loginCooldownMinutes = config.getInt("GriefPrevention.Spam.LoginCooldownMinutes", 2); @@ -276,6 +280,20 @@ public class GriefPrevention extends JavaPlugin this.config_claims_investigationTool = Material.STICK; } + //default for claim creation/modification tool + String modificationToolMaterialName = Material.GOLD_SPADE.name(); + + //get modification tool from config + modificationToolMaterialName = config.getString("GriefPrevention.Claims.ModificationTool", modificationToolMaterialName); + + //validate modification tool + this.config_claims_modificationTool = Material.getMaterial(modificationToolMaterialName); + if(this.config_claims_modificationTool == null) + { + GriefPrevention.AddLogEntry("ERROR: Material " + modificationToolMaterialName + " not found. Defaulting to the golden shovel. Please update your config.yml."); + this.config_claims_modificationTool = Material.GOLD_SPADE; + } + //default for siege worlds list ArrayList defaultSiegeWorldNames = new ArrayList(); @@ -365,6 +383,8 @@ public class GriefPrevention extends JavaPlugin 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.Claims.ModificationTool", this.config_claims_modificationTool.name()); + config.set("GriefPrevention.Claims.NoSurvivalBuildingOutsideClaims", this.config_claims_noBuildOutsideClaims); config.set("GriefPrevention.Spam.Enabled", this.config_spam_enabled); config.set("GriefPrevention.Spam.LoginCooldownMinutes", this.config_spam_loginCooldownMinutes); @@ -421,7 +441,7 @@ public class GriefPrevention extends JavaPlugin } //when datastore initializes, it loads player and claim data, and posts some stats to the log - this.dataStore = new DataStore(); + this.dataStore = new FlatFileDataStore(); //unless claim block accrual is disabled, start the recurring per 5 minute event to give claim blocks to online players //20L ~ 1 second @@ -1685,6 +1705,16 @@ public class GriefPrevention extends JavaPlugin public void onDisable() { + //save data for any online players + Player [] players = this.getServer().getOnlinePlayers(); + for(int i = 0; i < players.length; i++) + { + Player player = players[i]; + String playerName = player.getName(); + PlayerData playerData = this.dataStore.getPlayerData(playerName); + this.dataStore.savePlayerData(playerName, playerData); + } + AddLogEntry("GriefPrevention disabled."); } @@ -2001,17 +2031,25 @@ public class GriefPrevention extends JavaPlugin PlayerData playerData = this.dataStore.getPlayerData(player.getName()); Claim claim = this.dataStore.getClaimAt(location, false, playerData.lastClaim); + //exception: administrators in ignore claims mode + if(playerData.ignoreClaims) return null; + //wilderness rules if(claim == null) { //no building in the wilderness in creative mode if(this.creativeRulesApply(location)) { - //exception: administrators in ignore claims mode - if(playerData.ignoreClaims) return null; - - return "You can't build here. Use the golden shovel to claim some land first."; + return this.dataStore.getMessage(Messages.NoBuildOutsideClaims) + " " + this.dataStore.getMessage(Messages.CreativeBasicsDemoAdvertisement); + } + + //no building in survival wilderness when that is configured + else if(this.config_claims_noBuildOutsideClaims && this.config_claims_enabledWorlds.contains(location.getWorld())) + { + return this.dataStore.getMessage(Messages.NoBuildOutsideClaims) + " " + this.dataStore.getMessage(Messages.SurvivalBasicsDemoAdvertisement); + } + else { //but it's fine in survival mode @@ -2033,16 +2071,21 @@ public class GriefPrevention extends JavaPlugin PlayerData playerData = this.dataStore.getPlayerData(player.getName()); Claim claim = this.dataStore.getClaimAt(location, false, playerData.lastClaim); + //exception: administrators in ignore claims mode + if(playerData.ignoreClaims) return null; + //wilderness rules if(claim == null) { //no building in the wilderness in creative mode if(this.creativeRulesApply(location)) { - //exception: administrators in ignore claims mode - if(playerData.ignoreClaims) return null; - - return "You can only build where you have claimed land. To claim, watch this: http://tinyurl.com/c7bajb8"; + return this.dataStore.getMessage(Messages.NoBuildOutsideClaims) + " " + this.dataStore.getMessage(Messages.CreativeBasicsDemoAdvertisement); + } + + else if(this.config_claims_noBuildOutsideClaims && this.config_claims_enabledWorlds.contains(location.getWorld())) + { + return this.dataStore.getMessage(Messages.NoBuildOutsideClaims) + " " + this.dataStore.getMessage(Messages.SurvivalBasicsDemoAdvertisement); } //but it's fine in survival mode diff --git a/src/me/ryanhamshire/GriefPrevention/Messages.java b/src/me/ryanhamshire/GriefPrevention/Messages.java index 16c6a80..e1f8f54 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 + 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 } diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java index 2637999..4897bf5 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java @@ -137,8 +137,8 @@ class PlayerEventHandler implements Listener spam = true; } - //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) + //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) { playerData.spamCount++; spam = true; @@ -197,10 +197,18 @@ class PlayerEventHandler implements Listener } } + //very short messages close together are spam + if(!muted && message.length() < 5 && millisecondsSinceLastMessage < 5000) + { + spam = true; + if(playerData.spamCount > 4) muted = true; + playerData.spamCount++; + } + //if the message was determined to be a spam, consider taking action if(!player.hasPermission("griefprevention.spam") && spam) { - //anything above level 4 for a player which has received a warning... kick or if enabled, ban + //anything above level 8 for a player which has received a warning... kick or if enabled, ban if(playerData.spamCount > 8 && playerData.spamWarned) { if(GriefPrevention.instance.config_spam_banOffenders) @@ -463,8 +471,11 @@ class PlayerEventHandler implements Listener playerData.lastLogin = new Date(); this.dataStore.savePlayerData(playerName, playerData); - //check inventory, may need pvp protection - GriefPrevention.instance.checkPvpProtectionNeeded(event.getPlayer()); + //if player has never played on the server before, may need pvp protection + if(!event.getPlayer().hasPlayedBefore()) + { + GriefPrevention.instance.checkPvpProtectionNeeded(event.getPlayer()); + } //silence notifications when they're coming too fast if(event.getJoinMessage() != null && this.shouldSilenceNotification()) @@ -493,6 +504,9 @@ class PlayerEventHandler implements Listener event.setQuitMessage(null); } + //make sure his data is all saved - he might have accrued some claim blocks while playing that were not saved immediately + this.dataStore.savePlayerData(player.getName(), playerData); + this.onPlayerDisconnect(event.getPlayer(), event.getQuitMessage()); } @@ -717,7 +731,7 @@ class PlayerEventHandler implements Listener //if he's switching to the golden shovel ItemStack newItemStack = player.getInventory().getItem(event.getNewSlot()); - if(newItemStack != null && newItemStack.getType() == Material.GOLD_SPADE) + if(newItemStack != null && newItemStack.getType() == GriefPrevention.instance.config_claims_modificationTool) { EquipShovelProcessingTask task = new EquipShovelProcessingTask(player); GriefPrevention.instance.getServer().getScheduler().scheduleSyncDelayedTask(GriefPrevention.instance, task, 15L); //15L is approx. 3/4 of a second @@ -1037,7 +1051,7 @@ class PlayerEventHandler implements Listener GriefPrevention.sendMessage(player, TextMode.Info, Messages.BlockClaimed, claim.getOwnerName()); //visualize boundary - Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.Claim); + Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.Claim, player.getLocation()); Visualization.Apply(player, visualization); } @@ -1045,7 +1059,7 @@ class PlayerEventHandler implements Listener } //if it's a golden shovel - else if(materialInHand != Material.GOLD_SPADE) return; + else if(materialInHand != GriefPrevention.instance.config_claims_modificationTool) return; PlayerData playerData = this.dataStore.getPlayerData(player.getName()); @@ -1074,7 +1088,7 @@ class PlayerEventHandler implements Listener if(claim != null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.BlockClaimed, claim.getOwnerName()); - Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.ErrorClaim); + Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.ErrorClaim, player.getLocation()); Visualization.Apply(player, visualization); return; @@ -1359,7 +1373,7 @@ class PlayerEventHandler implements Listener { //inform and show the player GriefPrevention.sendMessage(player, TextMode.Success, Messages.ClaimResizeSuccess, String.valueOf(playerData.getRemainingClaimBlocks())); - Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.Claim); + Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.Claim, player.getLocation()); Visualization.Apply(player, visualization); //if resizing someone else's claim, make a log entry @@ -1378,7 +1392,7 @@ class PlayerEventHandler implements Listener GriefPrevention.sendMessage(player, TextMode.Err, Messages.ResizeFailOverlap); //show the player the conflicting claim - Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.ErrorClaim); + Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.ErrorClaim, player.getLocation()); Visualization.Apply(player, visualization); } @@ -1450,7 +1464,7 @@ class PlayerEventHandler implements Listener { GriefPrevention.sendMessage(player, TextMode.Err, Messages.CreateSubdivisionOverlap); - Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.ErrorClaim); + Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.ErrorClaim, player.getLocation()); Visualization.Apply(player, visualization); return; @@ -1460,7 +1474,7 @@ class PlayerEventHandler implements Listener else { GriefPrevention.sendMessage(player, TextMode.Success, Messages.SubdivisionSuccess); - Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.Claim); + Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.Claim, player.getLocation()); Visualization.Apply(player, visualization); playerData.lastShovelLocation = null; playerData.claimSubdividing = null; @@ -1473,7 +1487,7 @@ class PlayerEventHandler implements Listener else { GriefPrevention.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlap); - Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.Claim); + Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.Claim, player.getLocation()); Visualization.Apply(player, visualization); } } @@ -1482,7 +1496,7 @@ class PlayerEventHandler implements Listener else { GriefPrevention.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlapOtherPlayer, claim.getOwnerName()); - Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.ErrorClaim); + Visualization visualization = Visualization.FromClaim(claim, clickedBlock.getY(), VisualizationType.ErrorClaim, player.getLocation()); Visualization.Apply(player, visualization); } @@ -1559,7 +1573,7 @@ class PlayerEventHandler implements Listener { GriefPrevention.sendMessage(player, TextMode.Err, Messages.CreateClaimFailOverlapShort); - Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.ErrorClaim); + Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.ErrorClaim, player.getLocation()); Visualization.Apply(player, visualization); return; @@ -1569,7 +1583,7 @@ class PlayerEventHandler implements Listener else { GriefPrevention.sendMessage(player, TextMode.Success, Messages.CreateClaimSuccess); - Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.Claim); + Visualization visualization = Visualization.FromClaim(result.claim, clickedBlock.getY(), VisualizationType.Claim, player.getLocation()); Visualization.Apply(player, visualization); playerData.lastShovelLocation = null; } diff --git a/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java b/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java index 047b6a9..8aedb2e 100644 --- a/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java +++ b/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java @@ -91,7 +91,7 @@ class RestoreNatureExecutionTask implements Runnable //show visualization to player Claim claim = new Claim(lesserCorner, greaterCorner, "", new String[] {}, new String[] {}, new String[] {}, new String[] {}, null); - Visualization visualization = Visualization.FromClaim(claim, player.getLocation().getBlockY(), VisualizationType.RestoreNature); + Visualization visualization = Visualization.FromClaim(claim, player.getLocation().getBlockY(), VisualizationType.RestoreNature, player.getLocation()); Visualization.Apply(player, visualization); } } diff --git a/src/me/ryanhamshire/GriefPrevention/Visualization.java b/src/me/ryanhamshire/GriefPrevention/Visualization.java index e6cf6f6..f6fe486 100644 --- a/src/me/ryanhamshire/GriefPrevention/Visualization.java +++ b/src/me/ryanhamshire/GriefPrevention/Visualization.java @@ -77,12 +77,12 @@ public class Visualization //convenience method to build a visualization from a claim //visualizationType determines the style (gold blocks, silver, red, diamond, etc) - public static Visualization FromClaim(Claim claim, int height, VisualizationType visualizationType) + public static Visualization FromClaim(Claim claim, int height, VisualizationType visualizationType, Location locality) { //visualize only top level claims if(claim.parent != null) { - return FromClaim(claim.parent, height, visualizationType); + return FromClaim(claim.parent, height, visualizationType, locality); } Visualization visualization = new Visualization(); @@ -90,18 +90,19 @@ public class Visualization //add subdivisions first for(int i = 0; i < claim.children.size(); i++) { - visualization.addClaimElements(claim.children.get(i), height, VisualizationType.Subdivision); + visualization.addClaimElements(claim.children.get(i), height, VisualizationType.Subdivision, locality); } //add top level last so that it takes precedence (it shows on top when the child claim boundaries overlap with its boundaries) - visualization.addClaimElements(claim, height, visualizationType); + visualization.addClaimElements(claim, height, visualizationType, locality); return visualization; } //adds a claim's visualization to the current visualization //handy for combining several visualizations together, as when visualization a top level claim with several subdivisions inside - private void addClaimElements(Claim claim, int height, VisualizationType visualizationType) + //locality is a performance consideration. only create visualization blocks for around 100 blocks of the locality + private void addClaimElements(Claim claim, int height, VisualizationType visualizationType, Location locality) { Location smallXsmallZ = claim.getLesserBoundaryCorner(); Location bigXbigZ = claim.getGreaterBoundaryCorner(); @@ -159,28 +160,38 @@ public class Visualization this.elements.add(new VisualizationElement(getVisibleLocation(world, smallx + 1, height, bigz), accentMaterial, (byte)0)); this.elements.add(new VisualizationElement(getVisibleLocation(world, smallx, height, bigz - 1), accentMaterial, (byte)0)); + //locality + int minx = locality.getBlockX() - 100; + int minz = locality.getBlockZ() - 100; + int maxx = locality.getBlockX() + 100; + int maxz = locality.getBlockZ() + 100; + //top line for(int x = smallx + 10; x < bigx - 10; x += 10) { - this.elements.add(new VisualizationElement(getVisibleLocation(world, x, height, bigz), accentMaterial, (byte)0)); + if(x > minx && x < maxx) + this.elements.add(new VisualizationElement(getVisibleLocation(world, x, height, bigz), accentMaterial, (byte)0)); } //bottom line for(int x = smallx + 10; x < bigx - 10; x += 10) { - this.elements.add(new VisualizationElement(getVisibleLocation(world, x, height, smallz), accentMaterial, (byte)0)); + if(x > minx && x < maxx) + this.elements.add(new VisualizationElement(getVisibleLocation(world, x, height, smallz), accentMaterial, (byte)0)); } //left line for(int z = smallz + 10; z < bigz - 10; z += 10) { - this.elements.add(new VisualizationElement(getVisibleLocation(world, smallx, height, z), accentMaterial, (byte)0)); + if(z > minz && z < maxz) + this.elements.add(new VisualizationElement(getVisibleLocation(world, smallx, height, z), accentMaterial, (byte)0)); } //right line for(int z = smallz + 10; z < bigz - 10; z += 10) { - this.elements.add(new VisualizationElement(getVisibleLocation(world, bigx, height, z), accentMaterial, (byte)0)); + if(z > minz && z < maxz) + this.elements.add(new VisualizationElement(getVisibleLocation(world, bigx, height, z), accentMaterial, (byte)0)); } }