diff --git a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java index 03b3df0..fbb3a47 100644 --- a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java @@ -109,7 +109,7 @@ public class BlockEventHandler implements Listener if(claim == null || claim.allowContainers(player) == null) return; //if the player is under siege, he can't give away items - PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getName()); + PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getUniqueId()); if(playerData.siegeData != null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.SiegeNoDrop); @@ -177,7 +177,7 @@ public class BlockEventHandler implements Listener return; } - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); Claim claim = this.dataStore.getClaimAt(block.getLocation(), true, playerData.lastClaim); //if there's a claim here @@ -218,7 +218,7 @@ public class BlockEventHandler implements Listener String signMessage = lines.toString(); //if not empty and wasn't the same as the last sign, log it and remember it for later - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); if(notEmpty && playerData.lastMessage != null && !playerData.lastMessage.equals(signMessage)) { GriefPrevention.AddLogEntry("[Sign Placement] <" + player.getName() + "> " + lines.toString() + " @ " + GriefPrevention.getfriendlyLocationString(event.getBlock().getLocation())); @@ -275,7 +275,7 @@ public class BlockEventHandler implements Listener } //if the block is being placed within or under an existing claim - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); Claim claim = this.dataStore.getClaimAt(block.getLocation(), true, playerData.lastClaim); if(claim != null) { @@ -350,7 +350,7 @@ public class BlockEventHandler implements Listener //radius == 0 means protect ONLY the chest if(GriefPrevention.instance.config_claims_automaticClaimsForNewPlayersRadius == 0) { - this.dataStore.createClaim(block.getWorld(), block.getX(), block.getX(), block.getY(), block.getY(), block.getZ(), block.getZ(), player.getName(), null, null); + this.dataStore.createClaim(block.getWorld(), block.getX(), block.getX(), block.getY(), block.getY(), block.getZ(), block.getZ(), player.getUniqueId(), null, null); GriefPrevention.sendMessage(player, TextMode.Success, Messages.ChestClaimConfirmation); } @@ -363,7 +363,7 @@ public class BlockEventHandler implements Listener block.getX() - radius, block.getX() + radius, block.getY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance, block.getY(), block.getZ() - radius, block.getZ() + radius, - player.getName(), + player.getUniqueId(), null, null).succeeded) { radius--; @@ -687,7 +687,7 @@ public class BlockEventHandler implements Listener OfflinePlayer fromOwner = null; if(fromClaim != null) { - fromOwner = GriefPrevention.instance.getServer().getOfflinePlayer(fromClaim.ownerName); + fromOwner = GriefPrevention.instance.getServer().getOfflinePlayer(fromClaim.ownerID); } //cancel unless the owner of the spreading block is allowed to build in the receiving claim diff --git a/src/me/ryanhamshire/GriefPrevention/Claim.java b/src/me/ryanhamshire/GriefPrevention/Claim.java index c5a163e..d2c11d7 100644 --- a/src/me/ryanhamshire/GriefPrevention/Claim.java +++ b/src/me/ryanhamshire/GriefPrevention/Claim.java @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.*; import org.bukkit.*; import org.bukkit.World.Environment; @@ -48,15 +49,15 @@ public class Claim //id number. unique to this claim, never changes. Long id = null; - //ownername. for admin claims, this is the empty string + //ownerID. for admin claims, this is NULL //use getOwnerName() to get a friendly name (will be "an administrator" for admin claims) - public String ownerName; + public UUID ownerID; //list of players who (beyond the claim owner) have permission to grant permissions in this claim public ArrayList managers = new ArrayList(); //permissions for this claim, see ClaimPermission class - private HashMap playerNameToClaimPermissionMap = new HashMap(); + private HashMap playerIDToClaimPermissionMap = new HashMap(); //whether or not this claim is in the data store //if a claim instance isn't in the data store, it isn't "active" - players can't interract with it @@ -84,7 +85,7 @@ public class Claim //administrative claims are created and maintained by players with the griefprevention.adminclaims permission. public boolean isAdminClaim() { - return (this.ownerName == null || this.ownerName.isEmpty()); + return (this.ownerID == null); } //accessor for ID @@ -197,7 +198,7 @@ public class Claim } //main constructor. note that only creating a claim instance does nothing - a claim must be added to the data store to be effective - Claim(Location lesserBoundaryCorner, Location greaterBoundaryCorner, String ownerName, String [] builderNames, String [] containerNames, String [] accessorNames, String [] managerNames, Long id) + Claim(Location lesserBoundaryCorner, Location greaterBoundaryCorner, UUID ownerID, String [] builderIDs, String [] containerIds, String [] accessorIDs, String [] managerIDs, Long id) { //modification date this.modifiedDate = Calendar.getInstance().getTime(); @@ -210,42 +211,42 @@ public class Claim this.greaterBoundaryCorner = greaterBoundaryCorner; //owner - this.ownerName = ownerName; + this.ownerID = ownerID; //other permissions - for(int i = 0; i < builderNames.length; i++) + for(int i = 0; i < builderIDs.length; i++) { - String name = builderNames[i]; - if(name != null && !name.isEmpty()) + String builderID = builderIDs[i]; + if(builderID != null) { - this.playerNameToClaimPermissionMap.put(name, ClaimPermission.Build); + this.playerIDToClaimPermissionMap.put(builderID, ClaimPermission.Build); } } - for(int i = 0; i < containerNames.length; i++) + for(int i = 0; i < containerIds.length; i++) { - String name = containerNames[i]; - if(name != null && !name.isEmpty()) + String containerID = containerIds[i]; + if(containerID != null) { - this.playerNameToClaimPermissionMap.put(name, ClaimPermission.Inventory); + this.playerIDToClaimPermissionMap.put(containerID, ClaimPermission.Inventory); } } - for(int i = 0; i < accessorNames.length; i++) + for(int i = 0; i < accessorIDs.length; i++) { - String name = accessorNames[i]; - if(name != null && !name.isEmpty()) + String accessorID = accessorIDs[i]; + if(accessorID != null) { - this.playerNameToClaimPermissionMap.put(name, ClaimPermission.Access); + this.playerIDToClaimPermissionMap.put(accessorID, ClaimPermission.Access); } } - for(int i = 0; i < managerNames.length; i++) + for(int i = 0; i < managerIDs.length; i++) { - String name = managerNames[i]; - if(name != null && !name.isEmpty()) + String managerID = managerIDs[i]; + if(managerID != null) { - this.managers.add(name); + this.managers.add(managerID); } } } @@ -275,7 +276,7 @@ public class Claim Claim claim = new Claim (new Location(this.lesserBoundaryCorner.getWorld(), this.lesserBoundaryCorner.getBlockX() - howNear, this.lesserBoundaryCorner.getBlockY(), this.lesserBoundaryCorner.getBlockZ() - howNear), new Location(this.greaterBoundaryCorner.getWorld(), this.greaterBoundaryCorner.getBlockX() + howNear, this.greaterBoundaryCorner.getBlockY(), this.greaterBoundaryCorner.getBlockZ() + howNear), - "", new String[] {}, new String[] {}, new String[] {}, new String[] {}, null); + null, new String[] {}, new String[] {}, new String[] {}, new String[] {}, null); return claim.contains(location, false, true); } @@ -302,7 +303,7 @@ public class Claim } //no resizing, deleting, and so forth while under siege - if(this.ownerName.equals(player.getName())) + if(player.getUniqueId().equals(this.ownerID)) { if(this.siegeData != null) { @@ -343,20 +344,20 @@ public class Claim } //no building while in pvp combat - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getName()); + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); if(playerData.inPvpCombat()) { return GriefPrevention.instance.dataStore.getMessage(Messages.NoBuildPvP); } //owners can make changes, or admins with ignore claims mode enabled - if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null; + if(player.getUniqueId().equals(this.ownerID) || GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()).ignoreClaims) return null; //anyone with explicit build permission can make changes if(this.hasExplicitPermission(player, ClaimPermission.Build)) return null; - //also everyone is a member of the "public", so check for public permission - ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get("public"); + //also everyone is a member of the "public", so check for public permission, indicated by a null key + ClaimPermission permissionLevel = this.playerIDToClaimPermissionMap.get(null); if(ClaimPermission.Build == permissionLevel) return null; //subdivision permission inheritance @@ -372,13 +373,13 @@ public class Claim private boolean hasExplicitPermission(Player player, ClaimPermission level) { - String playerName = player.getName(); - Set keys = this.playerNameToClaimPermissionMap.keySet(); + String playerID = player.getUniqueId().toString(); + Set keys = this.playerIDToClaimPermissionMap.keySet(); Iterator iterator = keys.iterator(); while(iterator.hasNext()) { String identifier = iterator.next(); - if(playerName.equalsIgnoreCase(identifier) && this.playerNameToClaimPermissionMap.get(identifier) == level) return true; + if(playerID.equalsIgnoreCase(identifier) && this.playerIDToClaimPermissionMap.get(identifier) == level) return true; else if(identifier.startsWith("[") && identifier.endsWith("]")) { @@ -389,7 +390,7 @@ public class Claim if(permissionIdentifier == null || permissionIdentifier.isEmpty()) continue; //check permission - if(player.hasPermission(permissionIdentifier) && this.playerNameToClaimPermissionMap.get(identifier) == level) return true; + if(player.hasPermission(permissionIdentifier) && this.playerIDToClaimPermissionMap.get(identifier) == level) return true; } } @@ -408,7 +409,7 @@ public class Claim for(int i = 0; i < GriefPrevention.instance.config_siege_blocks.size(); i++) { Material breakableMaterial = GriefPrevention.instance.config_siege_blocks.get(i); - if(breakableMaterial.getId() == material.getId()) + if(breakableMaterial == material) { breakable = true; break; @@ -420,7 +421,7 @@ public class Claim { return GriefPrevention.instance.dataStore.getMessage(Messages.NonSiegeMaterial); } - else if(this.ownerName.equals(player.getName())) + else if(player.getUniqueId().equals(this.ownerID)) { return GriefPrevention.instance.dataStore.getMessage(Messages.NoOwnerBuildUnderSiege); } @@ -447,7 +448,7 @@ public class Claim } //claim owner and admins in ignoreclaims mode have access - if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null; + if(player.getUniqueId().equals(this.ownerID) || GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()).ignoreClaims) return null; //look for explicit individual access, inventory, or build permission if(this.hasExplicitPermission(player, ClaimPermission.Access)) return null; @@ -455,7 +456,7 @@ public class Claim if(this.hasExplicitPermission(player, ClaimPermission.Build)) return null; //also check for public permission - ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get("public"); + ClaimPermission permissionLevel = this.playerIDToClaimPermissionMap.get("public"); if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel || ClaimPermission.Access == permissionLevel) return null; //permission inheritance for subdivisions @@ -485,7 +486,7 @@ public class Claim } //owner and administrators in ignoreclaims mode have access - if(this.ownerName.equals(player.getName()) || GriefPrevention.instance.dataStore.getPlayerData(player.getName()).ignoreClaims) return null; + if(player.getUniqueId().equals(this.ownerID) || GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()).ignoreClaims) return null; //admin claims need adminclaims permission only. if(this.isAdminClaim()) @@ -498,7 +499,7 @@ public class Claim if(this.hasExplicitPermission(player, ClaimPermission.Build)) return null; //check for public container or build permission - ClaimPermission permissionLevel = this.playerNameToClaimPermissionMap.get("public"); + ClaimPermission permissionLevel = this.playerIDToClaimPermissionMap.get("public"); if(ClaimPermission.Build == permissionLevel || ClaimPermission.Inventory == permissionLevel) return null; //permission inheritance for subdivisions @@ -525,7 +526,7 @@ public class Claim for(int i = 0; i < this.managers.size(); i++) { String managerID = this.managers.get(i); - if(player.getName().equalsIgnoreCase(managerID)) return null; + if(player.getUniqueId().toString().equals(managerID)) return null; else if(managerID.startsWith("[") && managerID.endsWith("]")) { @@ -547,21 +548,21 @@ public class Claim } //grants a permission for a player or the public - public void setPermission(String playerName, ClaimPermission permissionLevel) + public void setPermission(String playerID, ClaimPermission permissionLevel) { - this.playerNameToClaimPermissionMap.put(playerName.toLowerCase(), permissionLevel); + this.playerIDToClaimPermissionMap.put(playerID.toLowerCase(), permissionLevel); } //revokes a permission for a player or the public - public void dropPermission(String playerName) + public void dropPermission(String playerID) { - this.playerNameToClaimPermissionMap.remove(playerName.toLowerCase()); + this.playerIDToClaimPermissionMap.remove(playerID.toLowerCase()); } //clears all permissions (except owner of course) public void clearPermissions() { - this.playerNameToClaimPermissionMap.clear(); + this.playerIDToClaimPermissionMap.clear(); } //gets ALL permissions @@ -569,7 +570,7 @@ public class Claim public void getPermissions(ArrayList builders, ArrayList containers, ArrayList accessors, ArrayList managers) { //loop through all the entries in the hash map - Iterator> mappingsIterator = this.playerNameToClaimPermissionMap.entrySet().iterator(); + Iterator> mappingsIterator = this.playerIDToClaimPermissionMap.entrySet().iterator(); while(mappingsIterator.hasNext()) { Map.Entry entry = mappingsIterator.next(); @@ -615,10 +616,10 @@ public class Claim if(this.parent != null) return this.parent.getOwnerName(); - if(this.ownerName.length() == 0) + if(this.ownerID == null) return GriefPrevention.instance.dataStore.getMessage(Messages.OwnerNameForAdminClaims); - return this.ownerName; + return GriefPrevention.lookupPlayerName(this.ownerID); } //whether or not a location is in a claim diff --git a/src/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimsTask.java b/src/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimsTask.java index f795aa0..211def7 100644 --- a/src/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimsTask.java +++ b/src/me/ryanhamshire/GriefPrevention/CleanupUnusedClaimsTask.java @@ -68,7 +68,7 @@ class CleanupUnusedClaimsTask implements Runnable boolean cleanupChunks = false; //get data for the player, especially last login timestamp - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(claim.ownerName); + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(claim.ownerID); //determine area of the default chest claim int areaOfDefaultClaim = 0; @@ -118,7 +118,7 @@ class CleanupUnusedClaimsTask implements Runnable } //delete them - GriefPrevention.instance.dataStore.deleteClaimsForPlayer(claim.getOwnerName(), true); + GriefPrevention.instance.dataStore.deleteClaimsForPlayer(claim.ownerID, true); GriefPrevention.AddLogEntry(" All of " + claim.getOwnerName() + "'s claims have expired."); for(int i = 0; i < claims.size(); i++) @@ -189,9 +189,9 @@ class CleanupUnusedClaimsTask implements Runnable } //toss that player data out of the cache, it's probably not needed in memory right now - if(!GriefPrevention.instance.getServer().getOfflinePlayer(claim.ownerName).isOnline()) + if(!GriefPrevention.instance.getServer().getOfflinePlayer(claim.ownerID).isOnline()) { - GriefPrevention.instance.dataStore.clearCachedPlayerData(claim.ownerName); + GriefPrevention.instance.dataStore.clearCachedPlayerData(claim.ownerID); } //since we're potentially loading a lot of chunks to scan parts of the world where there are no players currently playing, be mindful of memory usage diff --git a/src/me/ryanhamshire/GriefPrevention/DataStore.java b/src/me/ryanhamshire/GriefPrevention/DataStore.java index b846aee..f4cad97 100644 --- a/src/me/ryanhamshire/GriefPrevention/DataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DataStore.java @@ -21,6 +21,7 @@ package me.ryanhamshire.GriefPrevention; import java.io.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; import org.bukkit.*; import org.bukkit.configuration.file.FileConfiguration; @@ -32,7 +33,7 @@ import org.bukkit.inventory.ItemStack; public abstract class DataStore { //in-memory cache for player data - protected ConcurrentHashMap playerNameToPlayerDataMap = new ConcurrentHashMap(); + protected ConcurrentHashMap playerNameToPlayerDataMap = new ConcurrentHashMap(); //in-memory cache for group (permission-based) data protected ConcurrentHashMap permissionToBonusBlocksMap = new ConcurrentHashMap(); @@ -43,6 +44,9 @@ public abstract class DataStore //in-memory cache for messages private String [] messages; + //pattern for unique user identifiers (UUIDs) + protected final static Pattern uuidpattern = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); + //next claim ID Long nextClaimID = (long)0; @@ -50,6 +54,35 @@ public abstract class DataStore 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"; + + //the latest version of the data schema implemented here + protected static final int latestSchemaVersion = 1; + + //reading and writing the schema version to the data store + abstract int getSchemaVersionFromStorage(); + abstract void updateSchemaVersionInStorage(int versionToSet); + + //current version of the schema of data in secondary storage + private int currentSchemaVersion = -1; //-1 means not determined yet + + protected int getSchemaVersion() + { + if(this.currentSchemaVersion >= 0) + { + return this.currentSchemaVersion; + } + else + { + this.currentSchemaVersion = this.getSchemaVersionFromStorage(); + return this.currentSchemaVersion; + } + } + + protected void setSchemaVersion(int versionToSet) + { + this.currentSchemaVersion = versionToSet; + this.updateSchemaVersionInStorage(versionToSet); + } //initialization! void initialize() throws Exception @@ -57,7 +90,7 @@ public abstract class DataStore GriefPrevention.AddLogEntry(this.claims.size() + " total claims loaded."); //make a list of players who own claims - Vector playerNames = new Vector(); + Vector playerNames = new Vector(); for(int i = 0; i < this.claims.size(); i++) { Claim claim = this.claims.get(i); @@ -65,8 +98,8 @@ public abstract class DataStore //ignore admin claims if(claim.isAdminClaim()) continue; - if(!playerNames.contains(claim.ownerName)) - playerNames.add(claim.ownerName); + if(!playerNames.contains(claim.ownerID)) + playerNames.add(claim.ownerID); } GriefPrevention.AddLogEntry(playerNames.size() + " players have staked claims."); @@ -74,18 +107,34 @@ public abstract class DataStore //load up all the messages from messages.yml this.loadMessages(); + //if converting up from an earlier schema version, write all claims back to storage using the latest format + if(this.getSchemaVersion() < this.latestSchemaVersion) + { + GriefPrevention.AddLogEntry("Please wait. Updating data format."); + + for(Claim claim : this.claims) + { + this.saveClaim(claim); + } + } + //collect garbage, since lots of stuff was loaded into memory and then tossed out System.gc(); + + //make a note of the data store schema version + this.setSchemaVersion(this.latestSchemaVersion); } //removes cached player data from memory - synchronized void clearCachedPlayerData(String playerName) + synchronized void clearCachedPlayerData(UUID playerID) { - this.playerNameToPlayerDataMap.remove(playerName); + this.playerNameToPlayerDataMap.remove(playerID); } //gets the number of bonus blocks a player has from his permissions - synchronized int getGroupBonusBlocks(String playerName) + //Bukkit doesn't allow for checking permissions of an offline player. + //this will return 0 when he's offline, and the correct number when online. + synchronized int getGroupBonusBlocks(UUID playerID) { int bonusBlocks = 0; Set keys = permissionToBonusBlocksMap.keySet(); @@ -93,7 +142,7 @@ public abstract class DataStore while(iterator.hasNext()) { String groupName = iterator.next(); - Player player = GriefPrevention.instance.getServer().getPlayer(playerName); + Player player = GriefPrevention.instance.getServer().getPlayer(playerID); if(player != null && player.hasPermission(groupName)) { bonusBlocks += this.permissionToBonusBlocksMap.get(groupName); @@ -120,7 +169,7 @@ public abstract class DataStore abstract void saveGroupBonusBlocks(String groupName, int amount); - synchronized public void changeClaimOwner(Claim claim, String newOwnerName) throws Exception + synchronized public void changeClaimOwner(Claim claim, UUID newOwnerID) throws Exception { //if it's a subdivision, throw an exception if(claim.parent != null) @@ -134,14 +183,14 @@ public abstract class DataStore PlayerData ownerData = null; if(!claim.isAdminClaim()) { - ownerData = this.getPlayerData(claim.ownerName); + ownerData = this.getPlayerData(claim.ownerID); } //determine new owner - PlayerData newOwnerData = this.getPlayerData(newOwnerName); + PlayerData newOwnerData = this.getPlayerData(newOwnerID); //transfer - claim.ownerName = newOwnerName; + claim.ownerID = newOwnerID; this.saveClaim(claim); //adjust blocks and other records @@ -149,12 +198,12 @@ public abstract class DataStore { ownerData.claims.remove(claim); ownerData.bonusClaimBlocks -= claim.getArea(); - this.savePlayerData(claim.ownerName, ownerData); + this.savePlayerData(claim.ownerID, ownerData); } newOwnerData.claims.add(claim); newOwnerData.bonusClaimBlocks += claim.getArea(); - this.savePlayerData(newOwnerName, newOwnerData); + this.savePlayerData(newOwnerID, newOwnerData); } //adds a claim to the datastore, making it an effective claim @@ -181,9 +230,9 @@ public abstract class DataStore //except for administrative claims (which have no owner), update the owner's playerData with the new claim if(!newClaim.isAdminClaim()) { - PlayerData ownerData = this.getPlayerData(newClaim.getOwnerName()); + PlayerData ownerData = this.getPlayerData(newClaim.ownerID); ownerData.claims.add(newClaim); - this.savePlayerData(newClaim.getOwnerName(), ownerData); + this.savePlayerData(newClaim.ownerID, ownerData); } //make sure the claim is saved to disk @@ -265,36 +314,36 @@ public abstract class DataStore //retrieves player data from memory or secondary storage, as necessary //if the player has never been on the server before, this will return a fresh player data with default values - synchronized public PlayerData getPlayerData(String playerName) + synchronized public PlayerData getPlayerData(UUID playerID) { //first, look in memory - PlayerData playerData = this.playerNameToPlayerDataMap.get(playerName); + PlayerData playerData = this.playerNameToPlayerDataMap.get(playerID); //if not there, look in secondary storage if(playerData == null) { - playerData = this.getPlayerDataFromStorage(playerName); - playerData.playerName = playerName; + playerData = this.getPlayerDataFromStorage(playerID); + playerData.playerID = playerID; //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)) + if(playerID.equals(claim.ownerID)) { playerData.claims.add(claim); } } //shove that new player data into the hash map cache - this.playerNameToPlayerDataMap.put(playerName, playerData); + this.playerNameToPlayerDataMap.put(playerID, playerData); } //try the hash map again. if it's STILL not there, we have a bug to fix - return this.playerNameToPlayerDataMap.get(playerName); + return this.playerNameToPlayerDataMap.get(playerID); } - abstract PlayerData getPlayerDataFromStorage(String playerName); + abstract PlayerData getPlayerDataFromStorage(UUID playerID); //deletes a claim or subdivision synchronized public void deleteClaim(Claim claim) @@ -329,7 +378,7 @@ public abstract class DataStore //update player data, except for administrative claims, which have no owner if(!claim.isAdminClaim()) { - PlayerData ownerData = this.getPlayerData(claim.getOwnerName()); + PlayerData ownerData = this.getPlayerData(claim.ownerID); for(int i = 0; i < ownerData.claims.size(); i++) { if(ownerData.claims.get(i).id.equals(claim.id)) @@ -338,7 +387,7 @@ public abstract class DataStore break; } } - this.savePlayerData(claim.getOwnerName(), ownerData); + this.savePlayerData(claim.ownerID, ownerData); } } @@ -394,7 +443,7 @@ public abstract class DataStore //does NOT check a player has permission to create a claim, or enough claim blocks. //does NOT check minimum claim size constraints //does NOT visualize the new claim for any players - synchronized public CreateClaimResult createClaim(World world, int x1, int x2, int y1, int y2, int z1, int z2, String ownerName, Claim parent, Long id) + synchronized public CreateClaimResult createClaim(World world, int x1, int x2, int y1, int y2, int z1, int z2, UUID ownerID, Claim parent, Long id) { CreateClaimResult result = new CreateClaimResult(); @@ -444,7 +493,7 @@ public abstract class DataStore Claim newClaim = new Claim( new Location(world, smallx, smally, smallz), new Location(world, bigx, bigy, bigz), - ownerName, + ownerID, new String [] {}, new String [] {}, new String [] {}, @@ -488,7 +537,7 @@ public abstract class DataStore } //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); + public abstract void savePlayerData(UUID playerID, PlayerData playerData); //extends a claim to a new depth //respects the max depth config variable @@ -522,8 +571,8 @@ public abstract class DataStore { //fill-in the necessary SiegeData instance SiegeData siegeData = new SiegeData(attacker, defender, defenderClaim); - PlayerData attackerData = this.getPlayerData(attacker.getName()); - PlayerData defenderData = this.getPlayerData(defender.getName()); + PlayerData attackerData = this.getPlayerData(attacker.getUniqueId()); + PlayerData defenderData = this.getPlayerData(defender.getUniqueId()); attackerData.siegeData = siegeData; defenderData.siegeData = siegeData; defenderClaim.siegeData = siegeData; @@ -571,10 +620,10 @@ public abstract class DataStore grantAccess = true; } - PlayerData attackerData = this.getPlayerData(siegeData.attacker.getName()); + PlayerData attackerData = this.getPlayerData(siegeData.attacker.getUniqueId()); attackerData.siegeData = null; - PlayerData defenderData = this.getPlayerData(siegeData.defender.getName()); + PlayerData defenderData = this.getPlayerData(siegeData.defender.getUniqueId()); defenderData.siegeData = null; //start a cooldown for this attacker/defender pair @@ -587,7 +636,7 @@ public abstract class DataStore { Claim claim = siegeData.claims.get(i); claim.siegeData = null; - this.siegeCooldownRemaining.put(siegeData.attacker.getName() + "_" + claim.ownerName, cooldownEnd); + this.siegeCooldownRemaining.put(siegeData.attacker.getName() + "_" + claim.getOwnerName(), cooldownEnd); //if doors should be opened for looting, do that now if(grantAccess) @@ -674,9 +723,9 @@ public abstract class DataStore } //look for an attacker/claim cooldown - if(cooldownEnd == null && this.siegeCooldownRemaining.get(attacker.getName() + "_" + defenderClaim.ownerName) != null) + if(cooldownEnd == null && this.siegeCooldownRemaining.get(attacker.getName() + "_" + defenderClaim.getOwnerName()) != null) { - cooldownEnd = this.siegeCooldownRemaining.get(attacker.getName() + "_" + defenderClaim.ownerName); + cooldownEnd = this.siegeCooldownRemaining.get(attacker.getName() + "_" + defenderClaim.getOwnerName()); if(Calendar.getInstance().getTimeInMillis() < cooldownEnd) { @@ -684,7 +733,7 @@ public abstract class DataStore } //if found but expired, remove it - this.siegeCooldownRemaining.remove(attacker.getName() + "_" + defenderClaim.ownerName); + this.siegeCooldownRemaining.remove(attacker.getName() + "_" + defenderClaim.getOwnerName()); } return false; @@ -693,7 +742,7 @@ public abstract class DataStore //extend a siege, if it's possible to do so synchronized void tryExtendSiege(Player player, Claim claim) { - PlayerData playerData = this.getPlayerData(player.getName()); + PlayerData playerData = this.getPlayerData(player.getUniqueId()); //player must be sieged if(playerData.siegeData == null) return; @@ -713,14 +762,14 @@ public abstract class DataStore } //deletes all claims owned by a player - synchronized public void deleteClaimsForPlayer(String playerName, boolean deleteCreativeClaims) + synchronized public void deleteClaimsForPlayer(UUID playerID, boolean deleteCreativeClaims) { //make a list of the player's claims ArrayList claimsToDelete = new ArrayList(); for(int i = 0; i < this.claims.size(); i++) { Claim claim = this.claims.get(i); - if(claim.ownerName.equals(playerName) && (deleteCreativeClaims || !GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner()))) + if(playerID.equals(claim.ownerID) && (deleteCreativeClaims || !GriefPrevention.instance.creativeRulesApply(claim.getLesserBoundaryCorner()))) claimsToDelete.add(claim); } @@ -748,7 +797,7 @@ public abstract class DataStore this.deleteClaim(claim); //try to create this new claim, ignoring the original when checking for overlap - CreateClaimResult result = this.createClaim(claim.getLesserBoundaryCorner().getWorld(), newx1, newx2, newy1, newy2, newz1, newz2, claim.ownerName, claim.parent, claim.id); + CreateClaimResult result = this.createClaim(claim.getLesserBoundaryCorner().getWorld(), newx1, newx2, newy1, newy2, newz1, newz2, claim.ownerID, claim.parent, claim.id); //if succeeded if(result.succeeded) @@ -1031,5 +1080,49 @@ public abstract class DataStore return message; } - abstract void close(); + //used in updating the data schema from 0 to 1. + //converts player names in a list to uuids + protected String[] convertNameListToUUIDList(String[] names) + { + //doesn't apply after schema has been updated to version 1 + if(this.getSchemaVersion() >= 1) return names; + + //list to build results + List resultNames = new ArrayList(); + + for(String name : names) + { + //skip non-player-names (groups and "public"), leave them as-is + if(name.startsWith("[") || name.equals("public")) + { + resultNames.add(name); + continue; + } + + //otherwise try to convert to a UUID + UUID playerID = null; + try + { + playerID = UUIDFetcher.getUUIDOf(name); + } + catch(Exception ex){ } + + //if successful, replace player name with corresponding UUID + if(playerID != null) + { + resultNames.add(playerID.toString()); + } + } + + //return final result of conversion + String [] resultArray = new String [resultNames.size()]; + for(int i = 0; i < resultNames.size(); i++) + { + resultArray[i] = resultNames.get(i); + } + + return resultArray; + } + + abstract void close(); } diff --git a/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java b/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java index 604a1c7..74443df 100644 --- a/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java @@ -80,6 +80,16 @@ public class DatabaseDataStore extends DataStore statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_claimdata (id INT(15), owner VARCHAR(50), lessercorner VARCHAR(100), greatercorner VARCHAR(100), builders VARCHAR(1000), containers VARCHAR(1000), accessors VARCHAR(1000), managers VARCHAR(1000), parentid INT(15));"); statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_playerdata (name VARCHAR(50), lastlogin DATETIME, accruedblocks INT(15), bonusblocks INT(15));"); + + statement.execute("CREATE TABLE IF NOT EXISTS griefprevention_schemaversion (version INT(15));"); + + //if the next claim id table is empty, this is a brand new database which will write using the latest schema + //otherwise, schema version is determined by schemaversion table (or =0 if table is empty, see getSchemaVersion()) + ResultSet results = statement.executeQuery("SELECT * FROM griefprevention_nextclaimid;"); + if(!results.next()) + { + this.setSchemaVersion(latestSchemaVersion); + } } catch(Exception e3) { @@ -145,20 +155,49 @@ public class DatabaseDataStore extends DataStore Location greaterBoundaryCorner = this.locationFromString(greaterCornerString); String ownerName = results.getString("owner"); + UUID ownerID = null; + if(ownerName.isEmpty()) + { + ownerID = null; //administrative land claim + } + else if(this.getSchemaVersion() < 0) + { + try + { + ownerID = UUIDFetcher.getUUIDOf(ownerName); + } + catch(Exception ex){ } //if UUID not found, use NULL + } + else + { + try + { + ownerID = UUID.fromString(ownerName); + } + catch(Exception ex) + { + GriefPrevention.AddLogEntry("Failed to look up UUID for player " + ownerName + "."); + GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString()); + } + } String buildersString = results.getString("builders"); String [] builderNames = buildersString.split(";"); + builderNames = this.convertNameListToUUIDList(builderNames); String containersString = results.getString("containers"); String [] containerNames = containersString.split(";"); + containerNames = this.convertNameListToUUIDList(containerNames); String accessorsString = results.getString("accessors"); String [] accessorNames = accessorsString.split(";"); + accessorNames = this.convertNameListToUUIDList(accessorNames); String managersString = results.getString("managers"); String [] managerNames = managersString.split(";"); + managerNames = this.convertNameListToUUIDList(managerNames); - Claim topLevelClaim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerName, builderNames, containerNames, accessorNames, managerNames, claimID); + Claim topLevelClaim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builderNames, containerNames, accessorNames, managerNames, claimID); //search for another claim overlapping this one Claim conflictClaim = this.getClaimAt(topLevelClaim.lesserBoundaryCorner, true, null); @@ -196,17 +235,21 @@ public class DatabaseDataStore extends DataStore buildersString = childResults.getString("builders"); builderNames = buildersString.split(";"); + builderNames = this.convertNameListToUUIDList(builderNames); containersString = childResults.getString("containers"); containerNames = containersString.split(";"); + containerNames = this.convertNameListToUUIDList(containerNames); accessorsString = childResults.getString("accessors"); accessorNames = accessorsString.split(";"); + accessorNames = this.convertNameListToUUIDList(accessorNames); managersString = childResults.getString("managers"); managerNames = managersString.split(";"); + managerNames = this.convertNameListToUUIDList(managerNames); - Claim childClaim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerName, builderNames, containerNames, accessorNames, managerNames, null); + Claim childClaim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, null, builderNames, containerNames, accessorNames, managerNames, null); //add this claim to the list of children of the current top level claim childClaim.parent = topLevelClaim; @@ -226,6 +269,57 @@ public class DatabaseDataStore extends DataStore this.deleteClaimFromSecondaryStorage(claimsToRemove.get(i)); } + if(this.getSchemaVersion() == 0) + { + try + { + this.refreshDataConnection(); + + //pull ALL player data from the database + statement = this.databaseConnection.createStatement(); + results = statement.executeQuery("SELECT * FROM griefprevention_playerdata;"); + + //make a list of changes to be made + HashMap changes = new HashMap(); + + //for each result + while(results.next()) + { + //get the id + String playerName = results.getString("name"); + + //ignore groups + if(playerName.startsWith("$")) continue; + + //try to convert player name to UUID + try + { + UUID playerID = UUIDFetcher.getUUIDOf(playerName); + + //if successful, update the playerdata row by replacing the player's name with the player's UUID + if(playerID != null) + { + changes.put(playerName, playerID); + } + } + //otherwise leave it as-is. no harm done - it won't be requested by name, and this update only happens once. + catch(Exception ex){ } + } + + for(String name : changes.keySet()) + { + statement = this.databaseConnection.createStatement(); + statement.execute("UPDATE griefprevention_playerdata SET name = '" + changes.get(name).toString() + "' WHERE name = '" + name + "';"); + } + } + catch(SQLException e) + { + GriefPrevention.AddLogEntry("Unable to convert player data. Details:"); + GriefPrevention.AddLogEntry(e.getMessage()); + e.printStackTrace(); + } + } + super.initialize(); } @@ -261,7 +355,8 @@ public class DatabaseDataStore extends DataStore { String lesserCornerString = this.locationToString(claim.getLesserBoundaryCorner()); String greaterCornerString = this.locationToString(claim.getGreaterBoundaryCorner()); - String owner = claim.ownerName; + String owner = ""; + if(claim.ownerID != null) owner = claim.ownerID.toString(); ArrayList builders = new ArrayList(); ArrayList containers = new ArrayList(); @@ -358,22 +453,22 @@ public class DatabaseDataStore extends DataStore } @Override - synchronized PlayerData getPlayerDataFromStorage(String playerName) + synchronized PlayerData getPlayerDataFromStorage(UUID playerID) { PlayerData playerData = new PlayerData(); - playerData.playerName = playerName; + playerData.playerID = playerID; try { this.refreshDataConnection(); Statement statement = this.databaseConnection.createStatement(); - ResultSet results = statement.executeQuery("SELECT * FROM griefprevention_playerdata WHERE name='" + playerName + "';"); + ResultSet results = statement.executeQuery("SELECT * FROM griefprevention_playerdata WHERE name='" + playerID.toString() + "';"); //if there's no data for this player, create it with defaults if(!results.next()) { - this.savePlayerData(playerName, playerData); + this.savePlayerData(playerID, playerData); } //otherwise, just read from the database @@ -386,7 +481,7 @@ public class DatabaseDataStore extends DataStore } catch(SQLException e) { - GriefPrevention.AddLogEntry("Unable to retrieve data for player " + playerName + ". Details:"); + GriefPrevention.AddLogEntry("Unable to retrieve data for player " + playerID.toString() + ". Details:"); GriefPrevention.AddLogEntry(e.getMessage()); } @@ -395,11 +490,16 @@ public class DatabaseDataStore extends DataStore //saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them @Override - synchronized public void savePlayerData(String playerName, PlayerData playerData) + synchronized public void savePlayerData(UUID playerID, PlayerData playerData) { //never save data for the "administrative" account. an empty string for player name indicates administrative account - if(playerName.length() == 0) return; + if(playerID == null) return; + this.savePlayerData(playerID.toString(), playerData); + } + + private void savePlayerData(String playerID, PlayerData playerData) + { try { this.refreshDataConnection(); @@ -408,12 +508,12 @@ public class DatabaseDataStore extends DataStore String dateString = sqlFormat.format(playerData.lastLogin); Statement statement = databaseConnection.createStatement(); - statement.execute("DELETE FROM griefprevention_playerdata WHERE name='" + playerName + "';"); - statement.execute("INSERT INTO griefprevention_playerdata VALUES ('" + playerName + "', '" + dateString + "', " + playerData.accruedClaimBlocks + ", " + playerData.bonusClaimBlocks + ");"); + statement.execute("DELETE FROM griefprevention_playerdata WHERE name='" + playerID.toString() + "';"); + statement.execute("INSERT INTO griefprevention_playerdata VALUES ('" + playerID.toString() + "', '" + dateString + "', " + playerData.accruedClaimBlocks + ", " + playerData.bonusClaimBlocks + ");"); } catch(SQLException e) { - GriefPrevention.AddLogEntry("Unable to save data for player " + playerName + ". Details:"); + GriefPrevention.AddLogEntry("Unable to save data for player " + playerID.toString() + ". Details:"); GriefPrevention.AddLogEntry(e.getMessage()); } } @@ -487,4 +587,54 @@ public class DatabaseDataStore extends DataStore this.databaseConnection = DriverManager.getConnection(this.databaseUrl, connectionProps); } } + + @Override + protected int getSchemaVersionFromStorage() + { + try + { + this.refreshDataConnection(); + + Statement statement = this.databaseConnection.createStatement(); + ResultSet results = statement.executeQuery("SELECT * FROM griefprevention_schemaversion;"); + + //if there's nothing yet, assume 0 and add it + if(!results.next()) + { + this.setSchemaVersion(0); + return 0; + } + + //otherwise return the value that's in the table + else + { + return results.getInt(0); + } + + } + catch(SQLException e) + { + GriefPrevention.AddLogEntry("Unable to retrieve schema version from database. Details:"); + GriefPrevention.AddLogEntry(e.getMessage()); + return 0; + } + } + + @Override + protected void updateSchemaVersionInStorage(int versionToSet) + { + try + { + this.refreshDataConnection(); + + Statement statement = databaseConnection.createStatement(); + statement.execute("DELETE FROM griefprevention_schemaversion;"); + statement.execute("INSERT INTO griefprevention_schemaversion VALUES (" + versionToSet + ");"); + } + catch(SQLException e) + { + GriefPrevention.AddLogEntry("Unable to set next schema version to " + versionToSet + ". Details:"); + GriefPrevention.AddLogEntry(e.getMessage()); + } + } } diff --git a/src/me/ryanhamshire/GriefPrevention/DeliverClaimBlocksTask.java b/src/me/ryanhamshire/GriefPrevention/DeliverClaimBlocksTask.java index 828a9a0..b26acdf 100644 --- a/src/me/ryanhamshire/GriefPrevention/DeliverClaimBlocksTask.java +++ b/src/me/ryanhamshire/GriefPrevention/DeliverClaimBlocksTask.java @@ -40,7 +40,7 @@ class DeliverClaimBlocksTask implements Runnable { Player player = players[i]; DataStore dataStore = GriefPrevention.instance.dataStore; - PlayerData playerData = dataStore.getPlayerData(player.getName()); + PlayerData playerData = dataStore.getPlayerData(player.getUniqueId()); Location lastLocation = playerData.lastAfkCheckLocation; try //distance squared will throw an exception if the player has changed worlds diff --git a/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java b/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java index fce1e7c..cc3ccd2 100644 --- a/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/EntityEventHandler.java @@ -236,7 +236,7 @@ class EntityEventHandler implements Listener if(!(entity instanceof Player)) return; //only tracking players Player player = (Player)entity; - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); //if involved in a siege if(playerData.siegeData != null) @@ -327,7 +327,7 @@ class EntityEventHandler implements Listener //otherwise, apply entity-count limitations for creative worlds else if(GriefPrevention.instance.creativeRulesApply(event.getEntity().getLocation())) { - PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getName()); + PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getUniqueId()); Claim claim = this.dataStore.getClaimAt(event.getBlock().getLocation(), false, playerData.lastClaim); if(claim == null) return; @@ -389,8 +389,8 @@ class EntityEventHandler implements Listener Player defender = (Player)(event.getEntity()); - PlayerData defenderData = this.dataStore.getPlayerData(((Player)event.getEntity()).getName()); - PlayerData attackerData = this.dataStore.getPlayerData(attacker.getName()); + PlayerData defenderData = this.dataStore.getPlayerData(((Player)event.getEntity()).getUniqueId()); + PlayerData attackerData = this.dataStore.getPlayerData(attacker.getUniqueId()); //otherwise if protecting spawning players if(GriefPrevention.instance.config_pvp_protectFreshSpawns) @@ -461,7 +461,7 @@ class EntityEventHandler implements Listener PlayerData playerData = null; if(attacker != null) { - playerData = this.dataStore.getPlayerData(attacker.getName()); + playerData = this.dataStore.getPlayerData(attacker.getUniqueId()); cachedClaim = playerData.lastClaim; } @@ -495,7 +495,7 @@ class EntityEventHandler implements Listener PlayerData playerData = null; if(attacker != null) { - playerData = this.dataStore.getPlayerData(attacker.getName()); + playerData = this.dataStore.getPlayerData(attacker.getUniqueId()); cachedClaim = playerData.lastClaim; } @@ -583,7 +583,7 @@ class EntityEventHandler implements Listener PlayerData playerData = null; if(attacker != null) { - playerData = this.dataStore.getPlayerData(attacker.getName()); + playerData = this.dataStore.getPlayerData(attacker.getUniqueId()); cachedClaim = playerData.lastClaim; } diff --git a/src/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java b/src/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java index 771e585..9c267fa 100644 --- a/src/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java +++ b/src/me/ryanhamshire/GriefPrevention/EquipShovelProcessingTask.java @@ -42,7 +42,7 @@ class EquipShovelProcessingTask implements Runnable //if he's not holding the golden shovel anymore, do nothing if(player.getItemInHand().getType() != GriefPrevention.instance.config_claims_modificationTool) return; - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getName()); + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); int remainingBlocks = playerData.getRemainingClaimBlocks(); diff --git a/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java b/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java index d1b1b1e..953cfc4 100644 --- a/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java @@ -24,8 +24,6 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import java.util.regex.Matcher; -import java.util.regex.Pattern; - import org.bukkit.*; //manages data stored in the file system @@ -34,9 +32,8 @@ 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"; + private final static String schemaVersionFilePath = dataLayerFolderPath + File.separator + "_schemaVersion"; - private final static Pattern uuidpattern = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); - static boolean hasData() { File playerDataFolder = new File(playerDataFolderPath); @@ -55,11 +52,23 @@ public class FlatFileDataStore extends DataStore void initialize() throws Exception { //ensure data folders exist - new File(playerDataFolderPath).mkdirs(); - new File(claimDataFolderPath).mkdirs(); + boolean newDataStore = false; + File playerDataFolder = new File(playerDataFolderPath); + File claimDataFolder = new File(claimDataFolderPath); + if(!playerDataFolder.exists() || !claimDataFolder.exists()) + { + newDataStore = true; + playerDataFolder.mkdirs(); + claimDataFolder.mkdirs(); + } + + //if there's no data yet, then anything written will use the schema implemented by this code + if(newDataStore) + { + this.setSchemaVersion(DataStore.latestSchemaVersion); + } //load group data into memory - File playerDataFolder = new File(playerDataFolderPath); File [] files = playerDataFolder.listFiles(); for(int i = 0; i < files.length; i++) { @@ -120,14 +129,13 @@ public class FlatFileDataStore extends DataStore //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. + //skip any file starting with an underscore, to avoid special files not representing land claims if(files[i].getName().startsWith("_")) continue; //the filename is the claim ID. try to parse it @@ -182,23 +190,52 @@ public class FlatFileDataStore extends DataStore //third line is owner name line = inStream.readLine(); String ownerName = line; + UUID ownerID = null; + if(ownerName.isEmpty() || ownerName.startsWith("--")) + { + ownerID = null; //administrative land claim or subdivision + } + else if(this.getSchemaVersion() == 0) + { + try + { + ownerID = UUIDFetcher.getUUIDOf(ownerName); + } + catch(Exception ex){ } //if UUID not found, use NULL + } + else + { + try + { + ownerID = UUID.fromString(ownerName); + } + catch(Exception ex) + { + GriefPrevention.AddLogEntry("Failed to look up UUID for player " + ownerName + "."); + GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString()); + } + } //fourth line is list of builders line = inStream.readLine(); String [] builderNames = line.split(";"); + builderNames = this.convertNameListToUUIDList(builderNames); //fifth line is list of players who can access containers line = inStream.readLine(); String [] containerNames = line.split(";"); + containerNames = this.convertNameListToUUIDList(containerNames); //sixth line is list of players who can use buttons and switches line = inStream.readLine(); String [] accessorNames = line.split(";"); + accessorNames = this.convertNameListToUUIDList(accessorNames); //seventh line is list of players who can grant permissions line = inStream.readLine(); if(line == null) line = ""; String [] managerNames = line.split(";"); + managerNames = this.convertNameListToUUIDList(managerNames); //skip any remaining extra lines, until the "===" string, indicating the end of this claim or subdivision line = inStream.readLine(); @@ -210,7 +247,7 @@ public class FlatFileDataStore extends DataStore if(topLevelClaim == null) { //instantiate - topLevelClaim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerName, builderNames, containerNames, accessorNames, managerNames, claimID); + topLevelClaim = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, ownerID, builderNames, containerNames, accessorNames, managerNames, claimID); //search for another claim overlapping this one Claim conflictClaim = this.getClaimAt(topLevelClaim.lesserBoundaryCorner, true, null); @@ -241,7 +278,7 @@ public class FlatFileDataStore extends DataStore //otherwise there's already a top level claim, so this must be a subdivision of that top level claim else { - Claim subdivision = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, "--subdivision--", builderNames, containerNames, accessorNames, managerNames, null); + Claim subdivision = new Claim(lesserBoundaryCorner, greaterBoundaryCorner, null, builderNames, containerNames, accessorNames, managerNames, null); subdivision.modifiedDate = new Date(files[i].lastModified()); subdivision.parent = topLevelClaim; @@ -259,7 +296,7 @@ public class FlatFileDataStore extends DataStore //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()); + GriefPrevention.AddLogEntry("Unable to load data for claim \"" + files[i].getName() + "\": " + e.toString()); } try @@ -270,6 +307,42 @@ public class FlatFileDataStore extends DataStore } } + //if converting up from schema version 0, rename files using UUIDs instead of player names + //get a list of all the files in the claims data folder + if(this.getSchemaVersion() == 0) + { + files = playerDataFolder.listFiles(); + for(File playerFile : files) + { + //anything starting with an underscore or dollar sign isn't a player, ignore those + String currentFilename = playerFile.getName(); + if(currentFilename.startsWith("$") || currentFilename.startsWith("_")) continue; + + //try to convert player name to UUID + UUID playerID = null; + try + { + playerID = UUIDFetcher.getUUIDOf(currentFilename); + + //if successful, rename the file using the UUID + if(playerID != null) + { + playerFile.renameTo(new File(playerDataFolder, playerID.toString())); + } + + //otherwise hide it from the data store for the future + else + { + playerFile.renameTo(new File(playerDataFolder, "__" + currentFilename)); + } + } + catch(Exception ex) + { + playerFile.renameTo(new File(playerDataFolder, "__" + currentFilename)); + } + } + } + super.initialize(); } @@ -301,7 +374,8 @@ public class FlatFileDataStore extends DataStore //if any problem, log it catch(Exception e) { - GriefPrevention.AddLogEntry("Unexpected exception saving data for claim \"" + claimID + "\": " + e.getMessage()); + GriefPrevention.AddLogEntry("Unexpected exception saving data for claim \"" + claimID + "\": " + e.toString()); + e.printStackTrace(); } //close the file @@ -324,7 +398,9 @@ public class FlatFileDataStore extends DataStore outStream.newLine(); //third line is owner name - outStream.write(claim.ownerName); + String lineToWrite = ""; + if(claim.ownerID != null) lineToWrite = claim.ownerID.toString(); + outStream.write(lineToWrite); outStream.newLine(); ArrayList builders = new ArrayList(); @@ -382,18 +458,18 @@ public class FlatFileDataStore extends DataStore } @Override - synchronized PlayerData getPlayerDataFromStorage(String playerName) + synchronized PlayerData getPlayerDataFromStorage(UUID playerID) { - File playerFile = new File(playerDataFolderPath + File.separator + playerName); + File playerFile = new File(playerDataFolderPath + File.separator + playerID.toString()); PlayerData playerData = new PlayerData(); - playerData.playerName = playerName; + playerData.playerID = playerID; //if it doesn't exist as a file if(!playerFile.exists()) { //create a file with defaults - this.savePlayerData(playerName, playerData); + this.savePlayerData(playerID, playerData); } //otherwise, read the file @@ -441,7 +517,7 @@ public class FlatFileDataStore extends DataStore //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()); + GriefPrevention.AddLogEntry("Unable to load data for player \"" + playerID.toString() + "\": " + e.getMessage()); } try @@ -456,16 +532,16 @@ public class FlatFileDataStore extends DataStore //saves changes to player data. MUST be called after you're done making changes, otherwise a reload will lose them @Override - synchronized public void savePlayerData(String playerName, PlayerData playerData) + synchronized public void savePlayerData(UUID playerID, PlayerData playerData) { - //never save data for the "administrative" account. an empty string for claim owner indicates administrative account - if(playerName.length() == 0) return; + //never save data for the "administrative" account. null for claim owner ID indicates administrative account + if(playerID == null) return; BufferedWriter outStream = null; try { //open the player's file - File playerDataFile = new File(playerDataFolderPath + File.separator + playerName); + File playerDataFile = new File(playerDataFolderPath + File.separator + playerID.toString()); playerDataFile.createNewFile(); outStream = new BufferedWriter(new FileWriter(playerDataFile)); @@ -498,7 +574,7 @@ public class FlatFileDataStore extends DataStore //if any problem, log it catch(Exception e) { - GriefPrevention.AddLogEntry("GriefPrevention: Unexpected exception saving data for player \"" + playerName + "\": " + e.getMessage()); + GriefPrevention.AddLogEntry("GriefPrevention: Unexpected exception saving data for player \"" + playerID.toString() + "\": " + e.getMessage()); } try @@ -607,9 +683,12 @@ public class FlatFileDataStore extends DataStore //all group data files start with a dollar sign. ignoring those, already handled above if(file.getName().startsWith("$")) continue; - String playerName = file.getName(); - databaseStore.savePlayerData(playerName, this.getPlayerData(playerName)); - this.clearCachedPlayerData(playerName); + //ignore special files (claimID) + if(file.getName().startsWith("_")) continue; + + UUID playerID = UUID.fromString(file.getName()); + databaseStore.savePlayerData(playerID, this.getPlayerData(playerID)); + this.clearCachedPlayerData(playerID); } //migrate next claim ID @@ -647,4 +726,69 @@ public class FlatFileDataStore extends DataStore @Override synchronized void close() { } + + @Override + int getSchemaVersionFromStorage() + { + File schemaVersionFile = new File(schemaVersionFilePath); + if(schemaVersionFile.exists()) + { + BufferedReader inStream = null; + int schemaVersion = 0; + try + { + inStream = new BufferedReader(new FileReader(schemaVersionFile.getAbsolutePath())); + + //read the version number + String line = inStream.readLine(); + + //try to parse into an int value + schemaVersion = Integer.parseInt(line); + } + catch(Exception e){ } + + try + { + if(inStream != null) inStream.close(); + } + catch(IOException exception) {} + + return schemaVersion; + } + else + { + this.updateSchemaVersionInStorage(0); + return 0; + } + } + + @Override + void updateSchemaVersionInStorage(int versionToSet) + { + BufferedWriter outStream = null; + + try + { + //open the file and write the new value + File schemaVersionFile = new File(schemaVersionFilePath); + schemaVersionFile.createNewFile(); + outStream = new BufferedWriter(new FileWriter(schemaVersionFile)); + + outStream.write(String.valueOf(versionToSet)); + } + + //if any problem, log it + catch(Exception e) + { + GriefPrevention.AddLogEntry("Unexpected exception saving schema version: " + e.getMessage()); + } + + //close the file + try + { + 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 14a4949..3148c96 100644 --- a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java +++ b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.List; +import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Logger; @@ -802,7 +803,7 @@ public class GriefPrevention extends JavaPlugin //ignoreclaims if(cmd.getName().equalsIgnoreCase("ignoreclaims") && player != null) { - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); playerData.ignoreClaims = !playerData.ignoreClaims; @@ -831,7 +832,7 @@ public class GriefPrevention extends JavaPlugin } //count claims - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); int originalClaimCount = playerData.claims.size(); //check count @@ -842,7 +843,7 @@ public class GriefPrevention extends JavaPlugin } //delete them - this.dataStore.deleteClaimsForPlayer(player.getName(), false); + this.dataStore.deleteClaimsForPlayer(player.getUniqueId(), false); //inform the player int remainingBlocks = playerData.getRemainingClaimBlocks(); @@ -858,7 +859,7 @@ public class GriefPrevention extends JavaPlugin else if(cmd.getName().equalsIgnoreCase("restorenature") && player != null) { //change shovel mode - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); playerData.shovelMode = ShovelMode.RestoreNature; GriefPrevention.sendMessage(player, TextMode.Instr, Messages.RestoreNatureActivate); return true; @@ -868,7 +869,7 @@ public class GriefPrevention extends JavaPlugin else if(cmd.getName().equalsIgnoreCase("restorenatureaggressive") && player != null) { //change shovel mode - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); playerData.shovelMode = ShovelMode.RestoreNatureAggressive; GriefPrevention.sendMessage(player, TextMode.Warn, Messages.RestoreNatureAggressiveActivate); return true; @@ -878,7 +879,7 @@ public class GriefPrevention extends JavaPlugin else if(cmd.getName().equalsIgnoreCase("restorenaturefill") && player != null) { //change shovel mode - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); playerData.shovelMode = ShovelMode.RestoreNatureFill; //set radius based on arguments @@ -931,7 +932,7 @@ public class GriefPrevention extends JavaPlugin return true; } - OfflinePlayer targetPlayer = this.resolvePlayer(args[0]); + OfflinePlayer targetPlayer = this.resolvePlayerByName(args[0]); if(targetPlayer == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound); @@ -941,7 +942,7 @@ public class GriefPrevention extends JavaPlugin //change ownerhsip try { - this.dataStore.changeClaimOwner(claim, targetPlayer.getName()); + this.dataStore.changeClaimOwner(claim, targetPlayer.getUniqueId()); } catch(Exception e) { @@ -992,7 +993,7 @@ public class GriefPrevention extends JavaPlugin if(managers.size() > 0) { for(int i = 0; i < managers.size(); i++) - permissions.append(managers.get(i) + " "); + permissions.append(this.trustEntryToPlayerName(managers.get(i)) + " "); } player.sendMessage(permissions.toString()); @@ -1002,7 +1003,7 @@ public class GriefPrevention extends JavaPlugin if(builders.size() > 0) { for(int i = 0; i < builders.size(); i++) - permissions.append(builders.get(i) + " "); + permissions.append(this.trustEntryToPlayerName(builders.get(i)) + " "); } player.sendMessage(permissions.toString()); @@ -1012,7 +1013,7 @@ public class GriefPrevention extends JavaPlugin if(containers.size() > 0) { for(int i = 0; i < containers.size(); i++) - permissions.append(containers.get(i) + " "); + permissions.append(this.trustEntryToPlayerName(containers.get(i)) + " "); } player.sendMessage(permissions.toString()); @@ -1022,7 +1023,7 @@ public class GriefPrevention extends JavaPlugin if(accessors.size() > 0) { for(int i = 0; i < accessors.size(); i++) - permissions.append(accessors.get(i) + " "); + permissions.append(this.trustEntryToPlayerName(accessors.get(i)) + " "); } player.sendMessage(permissions.toString()); @@ -1042,7 +1043,7 @@ public class GriefPrevention extends JavaPlugin Claim claim = this.dataStore.getClaimAt(player.getLocation(), true /*ignore height*/, null); //bracket any permissions - if(args[0].contains(".")) + if(args[0].contains(".") && !args[0].startsWith("[") && !args[0].endsWith("]")) { args[0] = "[" + args[0] + "]"; } @@ -1068,7 +1069,7 @@ public class GriefPrevention extends JavaPlugin //validate player argument or group argument if(!args[0].startsWith("[") || !args[0].endsWith("]")) { - otherPlayer = this.resolvePlayer(args[0]); + otherPlayer = this.resolvePlayerByName(args[0]); if(!clearPermissions && otherPlayer == null && !args[0].equals("public")) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound); @@ -1084,7 +1085,7 @@ public class GriefPrevention extends JavaPlugin //if no claim here, apply changes to all his claims if(claim == null) { - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); for(int i = 0; i < playerData.claims.size(); i++) { claim = playerData.claims.get(i); @@ -1098,8 +1099,13 @@ public class GriefPrevention extends JavaPlugin //otherwise drop individual permissions else { - claim.dropPermission(args[0]); - claim.managers.remove(args[0]); + String idToDrop = args[0]; + if(otherPlayer != null) + { + idToDrop = otherPlayer.getUniqueId().toString(); + } + claim.dropPermission(idToDrop); + claim.managers.remove(idToDrop); } //save changes @@ -1140,10 +1146,15 @@ public class GriefPrevention extends JavaPlugin //otherwise individual permission drop else { - claim.dropPermission(args[0]); + String idToDrop = args[0]; + if(otherPlayer != null) + { + idToDrop = otherPlayer.getUniqueId().toString(); + } + claim.dropPermission(idToDrop); if(claim.allowEdit(player) == null) { - claim.managers.remove(args[0]); + claim.managers.remove(idToDrop); //beautify for output if(args[0].equals("public")) @@ -1225,14 +1236,14 @@ public class GriefPrevention extends JavaPlugin //if no parameter, just tell player cost per block and balance if(args.length != 1) { - GriefPrevention.sendMessage(player, TextMode.Info, Messages.BlockPurchaseCost, String.valueOf(GriefPrevention.instance.config_economy_claimBlocksPurchaseCost), String.valueOf(GriefPrevention.economy.getBalance(player.getName()))); + GriefPrevention.sendMessage(player, TextMode.Info, Messages.BlockPurchaseCost, String.valueOf(GriefPrevention.instance.config_economy_claimBlocksPurchaseCost), String.valueOf(GriefPrevention.economy.getBalance(player))); return false; } else { //determine max purchasable blocks - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); int maxPurchasable = GriefPrevention.instance.config_claims_maxAccruedBlocks - playerData.accruedClaimBlocks; //if the player is at his max, tell him so @@ -1265,7 +1276,7 @@ public class GriefPrevention extends JavaPlugin } //if the player can't afford his purchase, send error message - double balance = economy.getBalance(player.getName()); + double balance = economy.getBalance(player); double totalCost = blockCount * GriefPrevention.instance.config_economy_claimBlocksPurchaseCost; if(totalCost > balance) { @@ -1276,11 +1287,11 @@ public class GriefPrevention extends JavaPlugin else { //withdraw cost - economy.withdrawPlayer(player.getName(), totalCost); + economy.withdrawPlayer(player, totalCost); //add blocks playerData.accruedClaimBlocks += blockCount; - this.dataStore.savePlayerData(player.getName(), playerData); + this.dataStore.savePlayerData(player.getUniqueId(), playerData); //inform player GriefPrevention.sendMessage(player, TextMode.Success, Messages.PurchaseConfirmation, String.valueOf(totalCost), String.valueOf(playerData.getRemainingClaimBlocks())); @@ -1314,7 +1325,7 @@ public class GriefPrevention extends JavaPlugin } //load player data - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); int availableBlocks = playerData.getRemainingClaimBlocks(); //if no amount provided, just tell player value per block sold, and how many he can sell @@ -1351,11 +1362,11 @@ public class GriefPrevention extends JavaPlugin { //compute value and deposit it double totalValue = blockCount * GriefPrevention.instance.config_economy_claimBlocksSellValue; - economy.depositPlayer(player.getName(), totalValue); + economy.depositPlayer(player, totalValue); //subtract blocks playerData.accruedClaimBlocks -= blockCount; - this.dataStore.savePlayerData(player.getName(), playerData); + this.dataStore.savePlayerData(player.getUniqueId(), playerData); //inform player GriefPrevention.sendMessage(player, TextMode.Success, Messages.BlockSaleConfirmation, String.valueOf(totalValue), String.valueOf(playerData.getRemainingClaimBlocks())); @@ -1367,7 +1378,7 @@ public class GriefPrevention extends JavaPlugin //adminclaims else if(cmd.getName().equalsIgnoreCase("adminclaims") && player != null) { - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); playerData.shovelMode = ShovelMode.Admin; GriefPrevention.sendMessage(player, TextMode.Success, Messages.AdminClaimsMode); @@ -1377,7 +1388,7 @@ public class GriefPrevention extends JavaPlugin //basicclaims else if(cmd.getName().equalsIgnoreCase("basicclaims") && player != null) { - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); playerData.shovelMode = ShovelMode.Basic; playerData.claimSubdividing = null; GriefPrevention.sendMessage(player, TextMode.Success, Messages.BasicClaimsMode); @@ -1388,7 +1399,7 @@ public class GriefPrevention extends JavaPlugin //subdivideclaims else if(cmd.getName().equalsIgnoreCase("subdivideclaims") && player != null) { - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); playerData.shovelMode = ShovelMode.Subdivide; playerData.claimSubdividing = null; GriefPrevention.sendMessage(player, TextMode.Instr, Messages.SubdivisionMode); @@ -1413,7 +1424,7 @@ public class GriefPrevention extends JavaPlugin //deleting an admin claim additionally requires the adminclaims permission if(!claim.isAdminClaim() || player.hasPermission("griefprevention.adminclaims")) { - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); if(claim.children.size() > 0 && !playerData.warnedAboutMajorDeletion) { GriefPrevention.sendMessage(player, TextMode.Warn, Messages.DeletionSubdivisionWarning); @@ -1489,7 +1500,7 @@ public class GriefPrevention extends JavaPlugin if(args.length != 1) return false; //try to find that player - OfflinePlayer otherPlayer = this.resolvePlayer(args[0]); + OfflinePlayer otherPlayer = this.resolvePlayerByName(args[0]); if(otherPlayer == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound); @@ -1497,7 +1508,7 @@ public class GriefPrevention extends JavaPlugin } //delete all that player's claims - this.dataStore.deleteClaimsForPlayer(otherPlayer.getName(), true); + this.dataStore.deleteClaimsForPlayer(otherPlayer.getUniqueId(), true); GriefPrevention.sendMessage(player, TextMode.Success, Messages.DeleteAllSuccess, otherPlayer.getName()); if(player != null) @@ -1539,7 +1550,7 @@ public class GriefPrevention extends JavaPlugin //otherwise try to find the specified player else { - otherPlayer = this.resolvePlayer(args[0]); + otherPlayer = this.resolvePlayerByName(args[0]); if(otherPlayer == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound); @@ -1548,8 +1559,8 @@ public class GriefPrevention extends JavaPlugin } //load the target player's data - PlayerData playerData = this.dataStore.getPlayerData(otherPlayer.getName()); - GriefPrevention.sendMessage(player, TextMode.Instr, " " + playerData.accruedClaimBlocks + "(+" + (playerData.bonusClaimBlocks + this.dataStore.getGroupBonusBlocks(otherPlayer.getName())) + ")=" + (playerData.accruedClaimBlocks + playerData.bonusClaimBlocks + this.dataStore.getGroupBonusBlocks(otherPlayer.getName()))); + PlayerData playerData = this.dataStore.getPlayerData(otherPlayer.getUniqueId()); + GriefPrevention.sendMessage(player, TextMode.Instr, " " + playerData.accruedClaimBlocks + "(+" + (playerData.bonusClaimBlocks + this.dataStore.getGroupBonusBlocks(otherPlayer.getUniqueId())) + ")=" + (playerData.accruedClaimBlocks + playerData.bonusClaimBlocks + this.dataStore.getGroupBonusBlocks(otherPlayer.getUniqueId()))); for(int i = 0; i < playerData.claims.size(); i++) { Claim claim = playerData.claims.get(i); @@ -1561,7 +1572,7 @@ public class GriefPrevention extends JavaPlugin //drop the data we just loaded, if the player isn't online if(!otherPlayer.isOnline()) - this.dataStore.clearCachedPlayerData(otherPlayer.getName()); + this.dataStore.clearCachedPlayerData(otherPlayer.getUniqueId()); return true; } @@ -1636,7 +1647,7 @@ public class GriefPrevention extends JavaPlugin } //delete all admin claims - this.dataStore.deleteClaimsForPlayer("", true); //empty string for owner name indicates an administrative claim + this.dataStore.deleteClaimsForPlayer(null, true); //null for owner id indicates an administrative claim GriefPrevention.sendMessage(player, TextMode.Success, Messages.AllAdminDeleted); if(player != null) @@ -1680,7 +1691,7 @@ public class GriefPrevention extends JavaPlugin } //otherwise, find the specified player - OfflinePlayer targetPlayer = this.resolvePlayer(args[0]); + OfflinePlayer targetPlayer = this.resolvePlayerByName(args[0]); if(targetPlayer == null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound); @@ -1688,9 +1699,9 @@ public class GriefPrevention extends JavaPlugin } //give blocks to player - PlayerData playerData = this.dataStore.getPlayerData(targetPlayer.getName()); + PlayerData playerData = this.dataStore.getPlayerData(targetPlayer.getUniqueId()); playerData.bonusClaimBlocks += adjustment; - this.dataStore.savePlayerData(targetPlayer.getName(), playerData); + this.dataStore.savePlayerData(targetPlayer.getUniqueId(), playerData); GriefPrevention.sendMessage(player, TextMode.Success, Messages.AdjustBlocksSuccess, targetPlayer.getName(), String.valueOf(adjustment), String.valueOf(playerData.bonusClaimBlocks)); if(player != null) GriefPrevention.AddLogEntry(player.getName() + " adjusted " + targetPlayer.getName() + "'s bonus claim blocks by " + adjustment + "."); @@ -1703,7 +1714,7 @@ public class GriefPrevention extends JavaPlugin { //FEATURE: empower players who get "stuck" in an area where they don't have permission to build to save themselves - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); Claim claim = this.dataStore.getClaimAt(player.getLocation(), false, playerData.lastClaim); //if another /trapped is pending, ignore this slash command @@ -1771,7 +1782,7 @@ public class GriefPrevention extends JavaPlugin //can't start a siege when you're already involved in one Player attacker = player; - PlayerData attackerData = this.dataStore.getPlayerData(attacker.getName()); + PlayerData attackerData = this.dataStore.getPlayerData(attacker.getUniqueId()); if(attackerData.siegeData != null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.AlreadySieging); @@ -1813,7 +1824,7 @@ public class GriefPrevention extends JavaPlugin } //victim must not be under siege already - PlayerData defenderData = this.dataStore.getPlayerData(defender.getName()); + PlayerData defenderData = this.dataStore.getPlayerData(defender.getUniqueId()); if(defenderData.siegeData != null) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.AlreadyUnderSiegePlayer); @@ -1875,14 +1886,26 @@ public class GriefPrevention extends JavaPlugin return false; } - public static String getfriendlyLocationString(Location location) + private String trustEntryToPlayerName(String entry) + { + if(entry.startsWith("[") || entry.equals("public")) + { + return entry; + } + else + { + return GriefPrevention.lookupPlayerName(entry); + } + } + + public static String getfriendlyLocationString(Location location) { return location.getWorld().getName() + "(" + location.getBlockX() + "," + location.getBlockY() + "," + location.getBlockZ() + ")"; } private boolean abandonClaimHandler(Player player, boolean deleteTopLevelClaim) { - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); //which claim is being abandoned? Claim claim = this.dataStore.getClaimAt(player.getLocation(), true /*ignore height*/, null); @@ -1954,6 +1977,7 @@ public class GriefPrevention extends JavaPlugin //validate player or group argument String permission = null; OfflinePlayer otherPlayer = null; + UUID recipientID = null; if(recipientName.startsWith("[") && recipientName.endsWith("]")) { permission = recipientName.substring(1, recipientName.length() - 1); @@ -1971,7 +1995,7 @@ public class GriefPrevention extends JavaPlugin else { - otherPlayer = this.resolvePlayer(recipientName); + otherPlayer = this.resolvePlayerByName(recipientName); if(otherPlayer == null && !recipientName.equals("public") && !recipientName.equals("all")) { GriefPrevention.sendMessage(player, TextMode.Err, Messages.PlayerNotFound); @@ -1981,6 +2005,7 @@ public class GriefPrevention extends JavaPlugin if(otherPlayer != null) { recipientName = otherPlayer.getName(); + recipientID = otherPlayer.getUniqueId(); } else { @@ -1992,7 +2017,7 @@ public class GriefPrevention extends JavaPlugin ArrayList targetClaims = new ArrayList(); if(claim == null) { - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); for(int i = 0; i < playerData.claims.size(); i++) { targetClaims.add(playerData.claims.get(i)); @@ -2057,16 +2082,26 @@ public class GriefPrevention extends JavaPlugin for(int i = 0; i < targetClaims.size(); i++) { Claim currentClaim = targetClaims.get(i); + String identifierToAdd = recipientName; + if(permission != null) + { + identifierToAdd = "[" + permission + "]"; + } + else if(recipientID != null) + { + identifierToAdd = recipientID.toString(); + } + if(permissionLevel == null) { - if(!currentClaim.managers.contains(recipientName)) + if(!currentClaim.managers.contains(identifierToAdd)) { - currentClaim.managers.add(recipientName); + currentClaim.managers.add(identifierToAdd); } } else { - currentClaim.setPermission(recipientName, permissionLevel); + currentClaim.setPermission(identifierToAdd, permissionLevel); } this.dataStore.saveClaim(currentClaim); } @@ -2105,26 +2140,75 @@ public class GriefPrevention extends JavaPlugin } //helper method to resolve a player by name - private OfflinePlayer resolvePlayer(String name) + private OfflinePlayer resolvePlayerByName(String name) { //try online players first - Player player = this.getServer().getPlayer(name); - if(player != null) return player; + OfflinePlayer [] players = this.getServer().getOnlinePlayers(); + for(int i = 0; i < players.length; i++) + { + if(players[i].getName().equalsIgnoreCase(name)) + { + return players[i]; + } + } //then search offline players - OfflinePlayer [] offlinePlayers = this.getServer().getOfflinePlayers(); - for(int i = 0; i < offlinePlayers.length; i++) - { - if(offlinePlayers[i].getName().equalsIgnoreCase(name)) - { - return offlinePlayers[i]; - } - } + players = this.getServer().getOfflinePlayers(); + for(int i = 0; i < players.length; i++) + { + if(players[i].getName().equalsIgnoreCase(name)) + { + return players[i]; + } + } //if none found, return null return null; } + //helper method to resolve a player name from the player's UUID + static String lookupPlayerName(UUID playerID) + { + //parameter validation + if(playerID == null) return "someone"; + + //try online players first + Player player = GriefPrevention.instance.getServer().getPlayer(playerID); + if(player != null) return player.getName(); + + //then search offline players + OfflinePlayer [] players = GriefPrevention.instance.getServer().getOfflinePlayers(); + for(int i = 0; i < players.length; i++) + { + if(players[i].getUniqueId().equals(playerID)) + { + return players[i].getName(); + } + } + + //if none found + GriefPrevention.AddLogEntry("Error: Failed to find a local player with UUID: " + playerID.toString()); + new Exception().printStackTrace(); + return "someone"; + } + + //string overload for above helper + static String lookupPlayerName(String playerID) + { + UUID id; + try + { + id = UUID.fromString(playerID); + } + catch(IllegalArgumentException ex) + { + GriefPrevention.AddLogEntry("Error: Tried to look up a local player name for invalid UUID: " + playerID); + return "someone"; + } + + return lookupPlayerName(id); + } + public void onDisable() { //save data for any online players @@ -2132,9 +2216,9 @@ public class GriefPrevention extends JavaPlugin 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); + UUID playerID = player.getUniqueId(); + PlayerData playerData = this.dataStore.getPlayerData(playerID); + this.dataStore.savePlayerData(playerID, playerData); } this.dataStore.close(); @@ -2175,7 +2259,7 @@ public class GriefPrevention extends JavaPlugin } //otherwise, apply immunity - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); playerData.pvpImmune = true; //inform the player @@ -2471,7 +2555,7 @@ public class GriefPrevention extends JavaPlugin public String allowBuild(Player player, Location location) { - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); Claim claim = this.dataStore.getClaimAt(location, false, playerData.lastClaim); //exception: administrators in ignore claims mode and special player accounts created by server mods @@ -2513,7 +2597,7 @@ public class GriefPrevention extends JavaPlugin public String allowBreak(Player player, Location location) { - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); Claim claim = this.dataStore.getClaimAt(location, false, playerData.lastClaim); //exception: administrators in ignore claims mode, and special player accounts created by server mods diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerData.java b/src/me/ryanhamshire/GriefPrevention/PlayerData.java index f4fe6fe..f430dfd 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerData.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerData.java @@ -20,6 +20,7 @@ package me.ryanhamshire.GriefPrevention; import java.net.InetAddress; import java.util.Calendar; import java.util.Date; +import java.util.UUID; import java.util.Vector; import me.ryanhamshire.GriefPrevention.Claim; @@ -33,8 +34,8 @@ import org.bukkit.Location; //holds all of GriefPrevention's player-tied data public class PlayerData { - //the player's name - public String playerName; + //the player's ID + public UUID playerID; //the player's claims public Vector claims = new Vector(); @@ -151,7 +152,7 @@ public class PlayerData } //add any blocks this player might have based on group membership (permissions) - remainingBlocks += GriefPrevention.instance.dataStore.getGroupBonusBlocks(this.playerName); + remainingBlocks += GriefPrevention.instance.dataStore.getGroupBonusBlocks(this.playerID); return remainingBlocks; } diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java index ca963ee..5f6dcf6 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java @@ -22,6 +22,7 @@ import java.util.Calendar; import java.util.Date; import java.util.HashSet; import java.util.List; +import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -40,7 +41,6 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.Hanging; import org.bukkit.entity.Player; import org.bukkit.entity.Vehicle; -import org.bukkit.entity.minecart.HopperMinecart; import org.bukkit.entity.minecart.PoweredMinecart; import org.bukkit.entity.minecart.StorageMinecart; import org.bukkit.event.EventHandler; @@ -131,7 +131,7 @@ class PlayerEventHandler implements Listener boolean spam = false; boolean muted = false; - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); //remedy any CAPS SPAM, exception for very short messages which could be emoticons like =D or XD if(message.length() > 4 && this.stringsAreSimilar(message.toUpperCase(), message)) @@ -367,7 +367,7 @@ class PlayerEventHandler implements Listener } //if in pvp, block any pvp-banned slash commands - PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getName()); + PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getUniqueId()); if((playerData.inPvpCombat() || playerData.siegeData != null) && GriefPrevention.instance.config_pvp_blockedCommands.contains(command)) { event.setCancelled(true); @@ -410,7 +410,7 @@ class PlayerEventHandler implements Listener if(GriefPrevention.instance.config_spam_loginCooldownMinutes > 0 && event.getResult() == Result.ALLOWED) { //determine how long since last login and cooldown remaining - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); long millisecondsSinceLastLogin = (new Date()).getTime() - playerData.lastLogin.getTime(); long minutesSinceLastLogin = millisecondsSinceLastLogin / 1000 / 60; long cooldownRemaining = GriefPrevention.instance.config_spam_loginCooldownMinutes - minutesSinceLastLogin; @@ -435,7 +435,7 @@ class PlayerEventHandler implements Listener } //remember the player's ip address - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); playerData.ipAddress = event.getAddress(); } @@ -443,7 +443,7 @@ class PlayerEventHandler implements Listener @EventHandler(ignoreCancelled = true) void onPlayerRespawn (PlayerRespawnEvent event) { - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(event.getPlayer().getName()); + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(event.getPlayer().getUniqueId()); playerData.lastSpawn = Calendar.getInstance().getTimeInMillis(); GriefPrevention.instance.checkPvpProtectionNeeded(event.getPlayer()); } @@ -453,14 +453,14 @@ class PlayerEventHandler implements Listener void onPlayerJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); - String playerName = player.getName(); + UUID playerID = player.getUniqueId(); //note login time long now = Calendar.getInstance().getTimeInMillis(); - PlayerData playerData = this.dataStore.getPlayerData(playerName); + PlayerData playerData = this.dataStore.getPlayerData(playerID); playerData.lastSpawn = now; playerData.lastLogin = new Date(); - this.dataStore.savePlayerData(playerName, playerData); + this.dataStore.savePlayerData(playerID, playerData); //if player has never played on the server before, may need pvp protection if(!player.hasPlayedBefore()) @@ -544,7 +544,7 @@ class PlayerEventHandler implements Listener void onPlayerDeath(PlayerDeathEvent event) { //FEATURE: prevent death message spam by implementing a "cooldown period" for death messages - PlayerData playerData = this.dataStore.getPlayerData(event.getEntity().getName()); + PlayerData playerData = this.dataStore.getPlayerData(event.getEntity().getUniqueId()); long now = Calendar.getInstance().getTimeInMillis(); if(now - playerData.lastDeathTimeStamp < GriefPrevention.instance.config_spam_deathMessageCooldownSeconds * 1000) { @@ -559,7 +559,7 @@ class PlayerEventHandler implements Listener void onPlayerQuit(PlayerQuitEvent event) { Player player = event.getPlayer(); - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); //if banned, add IP to the temporary IP ban list if(player.isBanned() && playerData.ipAddress != null) @@ -575,7 +575,7 @@ class PlayerEventHandler implements Listener } //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.dataStore.savePlayerData(player.getUniqueId(), playerData); this.onPlayerDisconnect(event.getPlayer(), event.getQuitMessage()); } @@ -583,8 +583,8 @@ class PlayerEventHandler implements Listener //helper for above private void onPlayerDisconnect(Player player, String notificationMessage) { - String playerName = player.getName(); - PlayerData playerData = this.dataStore.getPlayerData(playerName); + UUID playerID = player.getUniqueId(); + PlayerData playerData = this.dataStore.getPlayerData(playerID); //FEATURE: claims where players have allowed explosions will revert back to not allowing them when the owner logs out for(Claim claim : playerData.claims) @@ -607,7 +607,7 @@ class PlayerEventHandler implements Listener } //drop data about this player - this.dataStore.clearCachedPlayerData(player.getName()); + this.dataStore.clearCachedPlayerData(playerID); } //determines whether or not a login or logout notification should be silenced, depending on how many there have been in the last minute @@ -650,7 +650,7 @@ class PlayerEventHandler implements Listener return; } - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); //FEATURE: players under siege or in PvP combat, can't throw items on the ground to hide //them or give them away to other players before they are defeated @@ -675,7 +675,7 @@ class PlayerEventHandler implements Listener public void onPlayerTeleport(PlayerTeleportEvent event) { Player player = event.getPlayer(); - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); //FEATURE: prevent players from using ender pearls to gain access to secured claims if(event.getCause() == TeleportCause.ENDER_PEARL && GriefPrevention.instance.config_claims_enderPearlsRequireAccessTrust) @@ -723,7 +723,7 @@ class PlayerEventHandler implements Listener { Player player = event.getPlayer(); Entity entity = event.getRightClicked(); - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); //don't allow interaction with item frames in claimed areas without build permission if(entity instanceof Hanging) @@ -825,7 +825,7 @@ class PlayerEventHandler implements Listener if(GriefPrevention.instance.config_pvp_protectFreshSpawns && (player.getItemInHand().getType() == Material.AIR)) { //if that player is currently immune to pvp - PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getName()); + PlayerData playerData = this.dataStore.getPlayerData(event.getPlayer().getUniqueId()); if(playerData.pvpImmune) { //if it's been less than 10 seconds since the last time he spawned, don't pick up the item @@ -854,7 +854,7 @@ class PlayerEventHandler implements Listener ItemStack newItemStack = player.getInventory().getItem(event.getNewSlot()); if(newItemStack != null && newItemStack.getType() == GriefPrevention.instance.config_claims_modificationTool) { - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getName()); + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); //always reset to basic claims mode if(playerData.shovelMode != ShovelMode.Basic) @@ -916,7 +916,7 @@ class PlayerEventHandler implements Listener } //if the bucket is being used in a claim, allow for dumping lava closer to other players - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); Claim claim = this.dataStore.getClaimAt(block.getLocation(), false, playerData.lastClaim); if(claim != null) { @@ -1011,7 +1011,7 @@ class PlayerEventHandler implements Listener Material clickedBlockType = clickedBlock.getType(); //apply rules for putting out fires (requires build permission) - PlayerData playerData = this.dataStore.getPlayerData(player.getName()); + PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); if(event.getClickedBlock() != null && event.getClickedBlock().getRelative(event.getBlockFace()).getType() == Material.FIRE) { Claim claim = this.dataStore.getClaimAt(clickedBlock.getLocation(), false, playerData.lastClaim); @@ -1248,7 +1248,7 @@ class PlayerEventHandler implements Listener //if deleteclaims permission, tell about the player's offline time if(!claim.isAdminClaim() && player.hasPermission("griefprevention.deleteclaims")) { - PlayerData otherPlayerData = this.dataStore.getPlayerData(claim.getOwnerName()); + PlayerData otherPlayerData = this.dataStore.getPlayerData(claim.ownerID); Date lastLogin = otherPlayerData.lastLogin; Date now = new Date(); long daysElapsed = (now.getTime() - lastLogin.getTime()) / (1000 * 60 * 60 * 24); @@ -1256,8 +1256,8 @@ class PlayerEventHandler implements Listener GriefPrevention.sendMessage(player, TextMode.Info, Messages.PlayerOfflineTime, String.valueOf(daysElapsed)); //drop the data we just loaded, if the player isn't online - if(GriefPrevention.instance.getServer().getPlayerExact(claim.getOwnerName()) == null) - this.dataStore.clearCachedPlayerData(claim.getOwnerName()); + if(GriefPrevention.instance.getServer().getPlayer(claim.ownerID) == null) + this.dataStore.clearCachedPlayerData(claim.ownerID); } } @@ -1283,8 +1283,8 @@ class PlayerEventHandler implements Listener } //if the player is in restore nature mode, do only that - String playerName = player.getName(); - playerData = this.dataStore.getPlayerData(player.getName()); + UUID playerID = player.getUniqueId(); + playerData = this.dataStore.getPlayerData(player.getUniqueId()); if(playerData.shovelMode == ShovelMode.RestoreNature || playerData.shovelMode == ShovelMode.RestoreNatureAggressive) { //if the clicked block is in a claim, visualize that claim and deliver an error message @@ -1538,7 +1538,7 @@ class PlayerEventHandler implements Listener Claim newClaim = new Claim( new Location(oldClaim.getLesserBoundaryCorner().getWorld(), newx1, newy1, newz1), new Location(oldClaim.getLesserBoundaryCorner().getWorld(), newx2, newy2, newz2), - "", new String[]{}, new String[]{}, new String[]{}, new String[]{}, null); + null, new String[]{}, new String[]{}, new String[]{}, new String[]{}, null); //if the new claim is smaller if(!newClaim.contains(oldClaim.getLesserBoundaryCorner(), true, false) || !newClaim.contains(oldClaim.getGreaterBoundaryCorner(), true, false)) @@ -1568,9 +1568,9 @@ class PlayerEventHandler implements Listener Visualization.Apply(player, visualization); //if resizing someone else's claim, make a log entry - if(!playerData.claimResizing.ownerName.equals(playerName)) + if(!playerID.equals(playerData.claimResizing.ownerID)) { - GriefPrevention.AddLogEntry(playerName + " resized " + playerData.claimResizing.getOwnerName() + "'s claim at " + GriefPrevention.getfriendlyLocationString(playerData.claimResizing.lesserBoundaryCorner) + "."); + GriefPrevention.AddLogEntry(player.getName() + " resized " + playerData.claimResizing.getOwnerName() + "'s claim at " + GriefPrevention.getfriendlyLocationString(playerData.claimResizing.lesserBoundaryCorner) + "."); } //if in a creative mode world and shrinking an existing claim, restore any unclaimed area @@ -1654,7 +1654,7 @@ class PlayerEventHandler implements Listener playerData.lastShovelLocation.getBlockX(), clickedBlock.getX(), playerData.lastShovelLocation.getBlockY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance, clickedBlock.getY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance, playerData.lastShovelLocation.getBlockZ(), clickedBlock.getZ(), - "--subdivision--", //owner name is not used for subdivisions + null, //owner is not used for subdivisions playerData.claimSubdividing, null); @@ -1720,7 +1720,7 @@ class PlayerEventHandler implements Listener GriefPrevention.sendMessage(player, TextMode.Instr, Messages.ClaimStart); //show him where he's working - Visualization visualization = Visualization.FromClaim(new Claim(clickedBlock.getLocation(), clickedBlock.getLocation(), "", new String[]{}, new String[]{}, new String[]{}, new String[]{}, null), clickedBlock.getY(), VisualizationType.RestoreNature, player.getLocation()); + Visualization visualization = Visualization.FromClaim(new Claim(clickedBlock.getLocation(), clickedBlock.getLocation(), null, new String[]{}, new String[]{}, new String[]{}, new String[]{}, null), clickedBlock.getY(), VisualizationType.RestoreNature, player.getLocation()); Visualization.Apply(player, visualization); } @@ -1759,7 +1759,7 @@ class PlayerEventHandler implements Listener } else { - playerName = ""; + playerID = null; } //try to create a new claim (will return null if this claim overlaps another) @@ -1768,7 +1768,7 @@ class PlayerEventHandler implements Listener lastShovelLocation.getBlockX(), clickedBlock.getX(), lastShovelLocation.getBlockY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance, clickedBlock.getY() - GriefPrevention.instance.config_claims_claimsExtendIntoGroundDistance, lastShovelLocation.getBlockZ(), clickedBlock.getZ(), - playerName, + playerID, null, null); //if it didn't succeed, tell the player why diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerKickBanTask.java b/src/me/ryanhamshire/GriefPrevention/PlayerKickBanTask.java index 0e5a117..1d55d87 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerKickBanTask.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerKickBanTask.java @@ -43,7 +43,7 @@ class PlayerKickBanTask implements Runnable if(this.banReason != null) { //ban - GriefPrevention.instance.getServer().getOfflinePlayer(this.player.getName()).setBanned(true); + GriefPrevention.instance.getServer().getOfflinePlayer(this.player.getUniqueId()).setBanned(true); //kick if(this.player.isOnline()) diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerRescueTask.java b/src/me/ryanhamshire/GriefPrevention/PlayerRescueTask.java index bee9464..1d285f6 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerRescueTask.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerRescueTask.java @@ -47,7 +47,7 @@ class PlayerRescueTask implements Runnable if(!player.isOnline()) return; //he no longer has a pending /trapped slash command, so he can try to use it again now - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getName()); + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); playerData.pendingTrapped = false; //if the player moved three or more blocks from where he used /trapped, admonish him and don't save him diff --git a/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java b/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java index 2ce620d..84a1598 100644 --- a/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java +++ b/src/me/ryanhamshire/GriefPrevention/RestoreNatureExecutionTask.java @@ -112,7 +112,7 @@ class RestoreNatureExecutionTask implements Runnable //show visualization to player who started the restoration if(player != null) { - Claim claim = new Claim(lesserCorner, greaterCorner, "", new String[] {}, new String[] {}, new String[] {}, new String[] {}, null); + Claim claim = new Claim(lesserCorner, greaterCorner, null, new String[] {}, new String[] {}, new String[] {}, new String[] {}, null); Visualization visualization = Visualization.FromClaim(claim, player.getLocation().getBlockY(), VisualizationType.RestoreNature, player.getLocation()); Visualization.Apply(player, visualization); } diff --git a/src/me/ryanhamshire/GriefPrevention/UUIDFetcher.java b/src/me/ryanhamshire/GriefPrevention/UUIDFetcher.java new file mode 100644 index 0000000..e3f413f --- /dev/null +++ b/src/me/ryanhamshire/GriefPrevention/UUIDFetcher.java @@ -0,0 +1,145 @@ +//BIG THANKS to EvilMidget38 for providing this handy UUID lookup tool to the Bukkit community! :) + +package me.ryanhamshire.GriefPrevention; + +import com.google.common.collect.ImmutableList; + +import org.bukkit.OfflinePlayer; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; + +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.concurrent.Callable; + +class UUIDFetcher implements Callable> { + private static final double PROFILES_PER_REQUEST = 100; + private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft"; + private final JSONParser jsonParser = new JSONParser(); + private final List names; + private final boolean rateLimiting; + + //cache for username -> uuid lookups + private static HashMap lookupCache; + + public UUIDFetcher(List names, boolean rateLimiting) { + this.names = ImmutableList.copyOf(names); + this.rateLimiting = rateLimiting; + } + + public UUIDFetcher(List names) { + this(names, true); + } + + public Map call() throws Exception { + Map uuidMap = new HashMap(); + int requests = (int) Math.ceil(names.size() / PROFILES_PER_REQUEST); + for (int i = 0; i < requests; i++) { + HttpURLConnection connection = createConnection(); + String body = JSONArray.toJSONString(names.subList(i * 100, Math.min((i + 1) * 100, names.size()))); + writeBody(connection, body); + JSONArray array = (JSONArray) jsonParser.parse(new InputStreamReader(connection.getInputStream())); + for (Object profile : array) { + JSONObject jsonProfile = (JSONObject) profile; + String id = (String) jsonProfile.get("id"); + String name = (String) jsonProfile.get("name"); + UUID uuid = UUIDFetcher.getUUID(id); + uuidMap.put(name, uuid); + } + if (rateLimiting && i != requests - 1) { + Thread.sleep(100L); + } + } + return uuidMap; + } + + private static void writeBody(HttpURLConnection connection, String body) throws Exception { + OutputStream stream = connection.getOutputStream(); + stream.write(body.getBytes()); + stream.flush(); + stream.close(); + } + + private static HttpURLConnection createConnection() throws Exception { + URL url = new URL(PROFILE_URL); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setUseCaches(false); + connection.setDoInput(true); + connection.setDoOutput(true); + return connection; + } + + private static UUID getUUID(String id) { + return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" +id.substring(20, 32)); + } + + public static byte[] toBytes(UUID uuid) { + ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]); + byteBuffer.putLong(uuid.getMostSignificantBits()); + byteBuffer.putLong(uuid.getLeastSignificantBits()); + return byteBuffer.array(); + } + + public static UUID fromBytes(byte[] array) { + if (array.length != 16) { + throw new IllegalArgumentException("Illegal byte array length: " + array.length); + } + ByteBuffer byteBuffer = ByteBuffer.wrap(array); + long mostSignificant = byteBuffer.getLong(); + long leastSignificant = byteBuffer.getLong(); + return new UUID(mostSignificant, leastSignificant); + } + + public static UUID getUUIDOf(String name) throws Exception + { + if(lookupCache == null) + { + lookupCache = new HashMap(); + } + + UUID result = lookupCache.get(name); + if(result == null) + { + if(lookupCache.containsKey(name)) return null; + + String correctCasingName = getNameWithCasing(name); + + result = new UUIDFetcher(Arrays.asList(name)).call().get(correctCasingName); + if(result == null) + { + GriefPrevention.AddLogEntry(correctCasingName + " --> ???"); + lookupCache.put(name, null); + throw new IllegalArgumentException(name); + } + GriefPrevention.AddLogEntry(correctCasingName + " --> " + result.toString()); + lookupCache.put(name, result); + } + + return result; + } + + private static String getNameWithCasing(String name) + { + OfflinePlayer [] players = GriefPrevention.instance.getServer().getOfflinePlayers(); + for(OfflinePlayer player : players) + { + if(player.getName().equalsIgnoreCase(name)) + { + if(!player.getName().equals(name)) + { + GriefPrevention.AddLogEntry(name + " --> " + player.getName()); + } + return player.getName(); + } + } + + return name; + } +} \ No newline at end of file diff --git a/src/me/ryanhamshire/GriefPrevention/Visualization.java b/src/me/ryanhamshire/GriefPrevention/Visualization.java index e793d60..6e307be 100644 --- a/src/me/ryanhamshire/GriefPrevention/Visualization.java +++ b/src/me/ryanhamshire/GriefPrevention/Visualization.java @@ -37,7 +37,7 @@ public class Visualization //sends a visualization to a player public static void Apply(Player player, Visualization visualization) { - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getName()); + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); //if he has any current visualization, clear it first if(playerData.currentVisualization != null) @@ -55,7 +55,7 @@ public class Visualization //reverts a visualization by sending another block change list, this time with the real world block values public static void Revert(Player player) { - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getName()); + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); Visualization visualization = playerData.currentVisualization;