From 6da42a907798de3bff2e9bbe648311dcb137b745 Mon Sep 17 00:00:00 2001 From: ryanhamshire Date: Mon, 22 Sep 2014 13:46:13 -0700 Subject: [PATCH] Added UUID support. Rewrote and retested parts of the plugin to use UUIDs instead of player names to uniquely identify players. Added data migration code to convert old data to the new (UUID) format. --- .../GriefPrevention/BlockEventHandler.java | 14 +- .../ryanhamshire/GriefPrevention/Claim.java | 95 ++++---- .../CleanupUnusedClaimsTask.java | 8 +- .../GriefPrevention/DataStore.java | 177 ++++++++++---- .../GriefPrevention/DatabaseDataStore.java | 176 ++++++++++++-- .../DeliverClaimBlocksTask.java | 2 +- .../GriefPrevention/EntityEventHandler.java | 14 +- .../EquipShovelProcessingTask.java | 2 +- .../GriefPrevention/FlatFileDataStore.java | 198 +++++++++++++--- .../GriefPrevention/GriefPrevention.java | 220 ++++++++++++------ .../GriefPrevention/PlayerData.java | 7 +- .../GriefPrevention/PlayerEventHandler.java | 68 +++--- .../GriefPrevention/PlayerKickBanTask.java | 2 +- .../GriefPrevention/PlayerRescueTask.java | 2 +- .../RestoreNatureExecutionTask.java | 2 +- .../GriefPrevention/UUIDFetcher.java | 145 ++++++++++++ .../GriefPrevention/Visualization.java | 4 +- 17 files changed, 877 insertions(+), 259 deletions(-) create mode 100644 src/me/ryanhamshire/GriefPrevention/UUIDFetcher.java 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;