This commit is contained in:
Ryan Hamshire 2012-07-12 19:44:22 -07:00
parent 6cb8df52c7
commit 311db20522
12 changed files with 727 additions and 588 deletions

View File

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

View File

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

View File

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

View File

@ -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<String, PlayerData> playerNameToPlayerDataMap = new HashMap<String, PlayerData>();
protected HashMap<String, PlayerData> playerNameToPlayerDataMap = new HashMap<String, PlayerData>();
//in-memory cache for group (permission-based) data
private HashMap<String, Integer> permissionToBonusBlocksMap = new HashMap<String, Integer>();
protected HashMap<String, Integer> permissionToBonusBlocksMap = new HashMap<String, Integer>();
//in-memory cache for claim data
ArrayList<Claim> claims = new ArrayList<Claim>();
@ -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<String> playerNames = new Vector<String>();
@ -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++;
abstract void writeClaimToStorage(Claim claim);
BufferedWriter outStream = null;
//increments the claim ID and updates secondary storage to be sure it's saved
abstract void incrementNextClaimID();
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<String> builders = new ArrayList<String>();
ArrayList<String> containers = new ArrayList<String>();
ArrayList<String> accessors = new ArrayList<String>();
ArrayList<String> managers = new ArrayList<String>();
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();
}
//retrieves player data from memory or file, as necessary
//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));

View File

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

View File

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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<String> builders = new ArrayList<String>();
ArrayList<String> containers = new ArrayList<String>();
ArrayList<String> accessors = new ArrayList<String>();
ArrayList<String> managers = new ArrayList<String>();
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){}
}
}

View File

@ -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<World> config_siege_enabledWorlds; //whether or not /siege is enabled on this server
public ArrayList<Material> 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<String> defaultSiegeWorldNames = new ArrayList<String>();
@ -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 this.dataStore.getMessage(Messages.NoBuildOutsideClaims) + " " + this.dataStore.getMessage(Messages.CreativeBasicsDemoAdvertisement);
return "You can't build here. Use the golden shovel to claim some land first.";
}
//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 this.dataStore.getMessage(Messages.NoBuildOutsideClaims) + " " + this.dataStore.getMessage(Messages.CreativeBasicsDemoAdvertisement);
}
return "You can only build where you have claimed land. To claim, watch this: http://tinyurl.com/c7bajb8";
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

View File

@ -2,5 +2,5 @@ package me.ryanhamshire.GriefPrevention;
public enum Messages
{
RespectingClaims, IgnoringClaims, SuccessfulAbandon, RestoreNatureActivate, RestoreNatureAggressiveActivate, FillModeActive, TransferClaimPermission, TransferClaimMissing, TransferClaimAdminOnly, PlayerNotFound, TransferTopLevel, TransferSuccess, TrustListNoClaim, ClearPermsOwnerOnly, UntrustIndividualAllClaims, UntrustEveryoneAllClaims, NoPermissionTrust, ClearPermissionsOneClaim, UntrustIndividualSingleClaim, OnlySellBlocks, BlockPurchaseCost, ClaimBlockLimit, InsufficientFunds, PurchaseConfirmation, OnlyPurchaseBlocks, BlockSaleValue, NotEnoughBlocksForSale, BlockSaleConfirmation, AdminClaimsMode, BasicClaimsMode, SubdivisionMode, SubdivisionDemo, DeleteClaimMissing, DeletionSubdivisionWarning, DeleteSuccess, CantDeleteAdminClaim, DeleteAllSuccess, NoDeletePermission, AllAdminDeleted, AdjustBlocksSuccess, NotTrappedHere, TrappedOnCooldown, RescuePending, NonSiegeWorld, AlreadySieging, NotSiegableThere, SiegeTooFarAway, NoSiegeDefenseless, AlreadyUnderSiegePlayer, AlreadyUnderSiegeArea, NoSiegeAdminClaim, SiegeOnCooldown, SiegeAlert, SiegeConfirmed, AbandonClaimMissing, NotYourClaim, DeleteTopLevelClaim, AbandonSuccess, CantGrantThatPermission, GrantPermissionNoClaim, GrantPermissionConfirmation, ManageUniversalPermissionsInstruction, ManageOneClaimPermissionsInstruction, CollectivePublic, BuildPermission, ContainersPermission, AccessPermission, PermissionsPermission, LocationCurrentClaim, LocationAllClaims, PvPImmunityStart, SiegeNoDrop, DonateItemsInstruction, ChestFull, DonationSuccess, PlayerTooCloseForFire, TooDeepToClaim, ChestClaimConfirmation, AutomaticClaimNotification, TrustCommandAdvertisement, GoldenShovelAdvertisement, UnprotectedChestWarning, ThatPlayerPvPImmune, CantFightWhileImmune, NoDamageClaimedEntity, ShovelBasicClaimMode, RemainingBlocks, CreativeBasicsDemoAdvertisement, SurvivalBasicsDemoAdvertisement, TrappedChatKeyword, TrappedInstructions, PvPNoDrop, SiegeNoTeleport, BesiegedNoTeleport, SiegeNoContainers, PvPNoContainers, PvPImmunityEnd, NoBedPermission, NoWildernessBuckets, NoLavaNearOtherPlayer, TooFarAway, BlockNotClaimed, BlockClaimed, SiegeNoShovel, RestoreNaturePlayerInChunk, NoCreateClaimPermission, ResizeClaimTooSmall, ResizeNeedMoreBlocks, NoCreativeUnClaim, ClaimResizeSuccess, ResizeFailOverlap, ResizeStart, ResizeFailOverlapSubdivision, SubdivisionStart, CreateSubdivisionOverlap, SubdivisionSuccess, CreateClaimFailOverlap, CreateClaimFailOverlapOtherPlayer, ClaimsDisabledWorld, ClaimStart, NewClaimTooSmall, CreateClaimInsufficientBlocks, AbandonClaimAdvertisement, CreateClaimFailOverlapShort, CreateClaimSuccess, SiegeWinDoorsOpen, RescueAbortedMoved, SiegeDoorsLockedEjection, NoModifyDuringSiege, OnlyOwnersModifyClaims, NoBuildUnderSiege, NoBuildPvP, NoBuildPermission, NonSiegeMaterial, NoOwnerBuildUnderSiege, NoAccessPermission, NoContainersSiege, NoContainersPermission, OwnerNameForAdminClaims, ClaimTooSmallForEntities, TooManyEntitiesInClaim, YouHaveNoClaims, ConfirmFluidRemoval, AutoBanNotify, AdjustGroupBlocksSuccess, InvalidPermissionID, UntrustOwnerOnly, HowToClaimRegex
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
}

View File

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

View File

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

View File

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