From 29a2b8e17bef28e70e1f9910b51022e4d034108a Mon Sep 17 00:00:00 2001 From: ryanhamshire Date: Wed, 8 Oct 2014 19:32:43 -0700 Subject: [PATCH] UUID Migration Rework, Bug Fixes Using multi-faceted strategy to better resolve UUIDs, and do it faster. Fixed dispensers putting fluids in a neighboring claim. Automatically deleting claims for worlds which no longer exist. Streamlined visualization code, hopefully will reduce or eliminate weird visualizations for VERY big land claims. Removed option to disallow un-claiming land in creative mode. Better default for last login date for new players or players who've had their data deleted or lost. --- .../GriefPrevention/BlockEventHandler.java | 19 +- .../GriefPrevention/DatabaseDataStore.java | 175 +++++++++++------ .../GriefPrevention/FlatFileDataStore.java | 110 +++++++---- .../GriefPrevention/GriefPrevention.java | 25 +-- .../GriefPrevention/PlayerData.java | 13 +- .../GriefPrevention/PlayerEventHandler.java | 13 +- .../GriefPrevention/UUIDFetcher.java | 180 +++++++++--------- .../GriefPrevention/Visualization.java | 11 +- .../VisualizationApplicationTask.java | 1 + 9 files changed, 309 insertions(+), 238 deletions(-) diff --git a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java index 5f23eee..8d6c1cb 100644 --- a/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/BlockEventHandler.java @@ -51,6 +51,7 @@ import org.bukkit.event.world.StructureGrowEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; +import org.bukkit.material.Dispenser; import org.bukkit.util.Vector; //event handlers related to blocks @@ -763,24 +764,10 @@ public class BlockEventHandler implements Listener { //from where? Block fromBlock = dispenseEvent.getBlock(); + Dispenser dispenser = new Dispenser(fromBlock.getType(), fromBlock.getData()); //to where? - Vector velocity = dispenseEvent.getVelocity(); - int xChange = 0; - int zChange = 0; - if(Math.abs(velocity.getX()) > Math.abs(velocity.getZ())) - { - if(velocity.getX() > 0) xChange = 1; - else xChange = -1; - } - else - { - if(velocity.getZ() > 0) zChange = 1; - else zChange = -1; - } - - Block toBlock = fromBlock.getRelative(xChange, 0, zChange); - + Block toBlock = fromBlock.getRelative(dispenser.getFacing()); Claim fromClaim = this.dataStore.getClaimAt(fromBlock.getLocation(), false, null); Claim toClaim = this.dataStore.getClaimAt(toBlock.getLocation(), false, fromClaim); diff --git a/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java b/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java index 5779807..b303442 100644 --- a/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/DatabaseDataStore.java @@ -95,6 +95,7 @@ public class DatabaseDataStore extends DataStore { GriefPrevention.AddLogEntry("ERROR: Unable to create the necessary database table. Details:"); GriefPrevention.AddLogEntry(e3.getMessage()); + e3.printStackTrace(); throw e3; } @@ -133,6 +134,85 @@ public class DatabaseDataStore extends DataStore this.nextClaimID = results.getLong("nextid"); } + 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(); + + ArrayList namesToConvert = new ArrayList(); + while(results.next()) + { + //get the id + String playerName = results.getString("name"); + + //ignore groups + if(playerName.startsWith("$")) continue; + + //add to list of names to convert to UUID + namesToConvert.add(playerName); + } + + //resolve and cache as many as possible through various means + try + { + UUIDFetcher fetcher = new UUIDFetcher(namesToConvert); + fetcher.call(); + } + catch(Exception e) + { + GriefPrevention.AddLogEntry("Failed to resolve a batch of names to UUIDs. Details:" + e.getMessage()); + e.printStackTrace(); + } + + //reset results cursor + results.beforeFirst(); + + //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(); + } + } + //load claims data into memory results = statement.executeQuery("SELECT * FROM griefprevention_claimdata;"); @@ -148,11 +228,30 @@ public class DatabaseDataStore extends DataStore long claimID = results.getLong("id"); - String lesserCornerString = results.getString("lessercorner"); - Location lesserBoundaryCorner = this.locationFromString(lesserCornerString); - - String greaterCornerString = results.getString("greatercorner"); - Location greaterBoundaryCorner = this.locationFromString(greaterCornerString); + Location lesserBoundaryCorner = null; + Location greaterBoundaryCorner = null; + try + { + String lesserCornerString = results.getString("lessercorner"); + lesserBoundaryCorner = this.locationFromString(lesserCornerString); + + String greaterCornerString = results.getString("greatercorner"); + greaterBoundaryCorner = this.locationFromString(greaterCornerString); + } + catch(Exception e) + { + if(e.getMessage().contains("World not found")) + { + Claim claim = new Claim(); + claim.id = claimID; + claimsToRemove.add(claim); + continue; + } + else + { + throw e; + } + } String ownerName = results.getString("owner"); UUID ownerID = null; @@ -166,7 +265,11 @@ public class DatabaseDataStore extends DataStore { ownerID = UUIDFetcher.getUUIDOf(ownerName); } - catch(Exception ex){ } //if UUID not found, use NULL + catch(Exception ex) + { + GriefPrevention.AddLogEntry("This owner name did not convert to aUUID: " + ownerName + "."); + GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString()); + } } else { @@ -221,10 +324,10 @@ public class DatabaseDataStore extends DataStore while(childResults.next()) { - lesserCornerString = childResults.getString("lessercorner"); + String lesserCornerString = childResults.getString("lessercorner"); lesserBoundaryCorner = this.locationFromString(lesserCornerString); - greaterCornerString = childResults.getString("greatercorner"); + String greaterCornerString = childResults.getString("greatercorner"); greaterBoundaryCorner = this.locationFromString(greaterCornerString); buildersString = childResults.getString("builders"); @@ -248,7 +351,7 @@ public class DatabaseDataStore extends DataStore //add this claim to the list of children of the current top level claim childClaim.parent = topLevelClaim; topLevelClaim.children.add(childClaim); - childClaim.inDataStore = true; + childClaim.inDataStore = true; } } catch(SQLException e) @@ -263,57 +366,6 @@ 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(); } @@ -441,8 +493,9 @@ public class DatabaseDataStore extends DataStore } catch(SQLException e) { - GriefPrevention.AddLogEntry("Unable to delete data for claim at " + this.locationToString(claim.lesserBoundaryCorner) + ". Details:"); + GriefPrevention.AddLogEntry("Unable to delete data for claim " + claim.id + ". Details:"); GriefPrevention.AddLogEntry(e.getMessage()); + e.printStackTrace(); } } diff --git a/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java b/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java index 3e8a0ef..34e2147 100644 --- a/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java +++ b/src/me/ryanhamshire/GriefPrevention/FlatFileDataStore.java @@ -127,6 +127,65 @@ public class FlatFileDataStore extends DataStore catch(IOException exception) {} } + //if converting up from schema version 0, rename player data 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(); + ArrayList namesToConvert = new ArrayList(); + 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; + + namesToConvert.add(currentFilename); + } + + //resolve and cache as many as possible through various means + try + { + UUIDFetcher fetcher = new UUIDFetcher(namesToConvert); + fetcher.call(); + } + catch(Exception e) + { + GriefPrevention.AddLogEntry("Failed to resolve a batch of names to UUIDs. Details:" + e.getMessage()); + e.printStackTrace(); + } + + //rename files + 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)); + } + } + } + //load claims data into memory //get a list of all the files in the claims data folder files = claimDataFolder.listFiles(); @@ -201,7 +260,11 @@ public class FlatFileDataStore extends DataStore { ownerID = UUIDFetcher.getUUIDOf(ownerName); } - catch(Exception ex){ } //if UUID not found, use NULL + catch(Exception ex) + { + GriefPrevention.AddLogEntry("Couldn't resolve this name to a UUID: " + ownerName + "."); + GriefPrevention.AddLogEntry(" Converted land claim to administrative @ " + lesserBoundaryCorner.toString()); + } } else { @@ -290,7 +353,14 @@ 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.toString()); + if(e.getMessage().contains("World not found")) + { + files[i].delete(); + } + else + { + GriefPrevention.AddLogEntry("Unable to load data for claim \"" + files[i].getName() + "\": " + e.toString()); + } } try @@ -301,42 +371,6 @@ 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(); } diff --git a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java index 8c293e6..452e399 100644 --- a/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java +++ b/src/me/ryanhamshire/GriefPrevention/GriefPrevention.java @@ -83,7 +83,6 @@ public class GriefPrevention extends JavaPlugin public boolean config_claims_creationRequiresPermission; //whether creating claims with the shovel requires a permission public int config_claims_claimsExtendIntoGroundDistance; //how far below the shoveled block a new claim will reach public int config_claims_minSize; //minimum width and height for non-admin claims - public boolean config_claims_allowUnclaimInCreative; //whether players may unclaim land (resize or abandon) in creative mode public boolean config_claims_autoRestoreUnclaimedCreativeLand; //whether unclaimed land in creative worlds is automatically /restorenature-d public boolean config_claims_noBuildOutsideClaims; //whether players can build in survival worlds outside their claimed areas @@ -311,7 +310,6 @@ public class GriefPrevention extends JavaPlugin this.config_claims_trappedCooldownHours = config.getInt("GriefPrevention.Claims.TrappedCommandCooldownHours", 8); this.config_claims_noBuildOutsideClaims = config.getBoolean("GriefPrevention.Claims.NoSurvivalBuildingOutsideClaims", false); this.config_claims_warnOnBuildOutside = config.getBoolean("GriefPrevention.Claims.WarnWhenBuildingOutsideClaims", true); - this.config_claims_allowUnclaimInCreative = config.getBoolean("GriefPrevention.Claims.AllowUnclaimingCreativeModeLand", true); this.config_claims_autoRestoreUnclaimedCreativeLand = config.getBoolean("GriefPrevention.Claims.AutoRestoreUnclaimedCreativeLand", true); this.config_claims_chestClaimExpirationDays = config.getInt("GriefPrevention.Claims.Expiration.ChestClaimDays", 7); @@ -571,7 +569,6 @@ public class GriefPrevention extends JavaPlugin outConfig.set("GriefPrevention.Claims.ModificationTool", this.config_claims_modificationTool.name()); outConfig.set("GriefPrevention.Claims.NoSurvivalBuildingOutsideClaims", this.config_claims_noBuildOutsideClaims); outConfig.set("GriefPrevention.Claims.WarnWhenBuildingOutsideClaims", this.config_claims_warnOnBuildOutside); - outConfig.set("GriefPrevention.Claims.AllowUnclaimingCreativeModeLand", this.config_claims_allowUnclaimInCreative); outConfig.set("GriefPrevention.Claims.AutoRestoreUnclaimedCreativeLand", this.config_claims_autoRestoreUnclaimedCreativeLand); outConfig.set("GriefPrevention.Spam.Enabled", this.config_spam_enabled); @@ -841,12 +838,6 @@ public class GriefPrevention extends JavaPlugin { if(args.length != 0) return false; - if(!GriefPrevention.instance.config_claims_allowUnclaimInCreative && creativeRulesApply(player.getLocation())) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoCreativeUnClaim); - return true; - } - //count claims PlayerData playerData = this.dataStore.getPlayerData(player.getUniqueId()); int originalClaimCount = playerData.claims.size(); @@ -1936,12 +1927,6 @@ public class GriefPrevention extends JavaPlugin GriefPrevention.sendMessage(player, TextMode.Err, Messages.NotYourClaim); } - //don't allow abandon of creative mode claims - else if(!GriefPrevention.instance.config_claims_allowUnclaimInCreative && this.creativeRulesApply(player.getLocation())) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoCreativeUnClaim); - } - //warn if has children and we're not explicitly deleting a top level claim else if(claim.children.size() > 0 && !deleteTopLevelClaim) { @@ -2200,8 +2185,6 @@ public class GriefPrevention extends JavaPlugin } //if none found - GriefPrevention.AddLogEntry("Error: Failed to find a local player with UUID: " + playerID.toString()); - new Exception().printStackTrace(); return "someone"; } @@ -2242,15 +2225,15 @@ public class GriefPrevention extends JavaPlugin //called when a player spawns, applies protection for that player if necessary public void checkPvpProtectionNeeded(Player player) { - //if pvp is disabled, do nothing + //if anti spawn camping feature is not enabled, do nothing + if(!this.config_pvp_protectFreshSpawns) return; + + //if pvp is disabled, do nothing if(!this.config_pvp_enabledWorlds.contains(player.getWorld())) return; //if player is in creative mode, do nothing if(player.getGameMode() == GameMode.CREATIVE) return; - //if anti spawn camping feature is not enabled, do nothing - if(!this.config_pvp_protectFreshSpawns) return; - //if the player has the damage any player permission enabled, do nothing if(player.hasPermission("griefprevention.nopvpimmunity")) return; diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerData.java b/src/me/ryanhamshire/GriefPrevention/PlayerData.java index 4bf0b4e..e75204f 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerData.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerData.java @@ -117,13 +117,16 @@ public class PlayerData PlayerData() { - //default last login date value to a year ago to ensure a brand new player can log in + //default last login date value to 5 minutes ago to ensure a brand new player can log in //see login cooldown feature, PlayerEventHandler.onPlayerLogin() //if the player successfully logs in, this value will be overwritten with the current date and time - Calendar lastYear = Calendar.getInstance(); - lastYear.add(Calendar.YEAR, -1); - this.lastLogin = lastYear.getTime(); - this.lastTrappedUsage = lastYear.getTime(); + Calendar fiveMinutesBack = Calendar.getInstance(); + fiveMinutesBack.add(Calendar.MINUTE, -5); + this.lastLogin = fiveMinutesBack.getTime(); + + //default last trapped usage to 5 hours ago, so the command is available right away + fiveMinutesBack.add(Calendar.HOUR, -5); + this.lastTrappedUsage = fiveMinutesBack.getTime(); } //whether or not this player is "in" pvp combat diff --git a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java index d3e45a4..9c10595 100644 --- a/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java +++ b/src/me/ryanhamshire/GriefPrevention/PlayerEventHandler.java @@ -481,7 +481,6 @@ class PlayerEventHandler implements Listener PlayerData playerData = this.dataStore.getPlayerData(playerID); playerData.lastSpawn = now; playerData.lastLogin = new Date(); - this.dataStore.savePlayerData(playerID, playerData); //if player has never played on the server before, may need pvp protection if(!player.hasPlayedBefore()) @@ -1558,9 +1557,8 @@ class PlayerEventHandler implements Listener } } - //special rules for making a top-level claim smaller. to check this, verifying the old claim's corners are inside the new claim's boundaries. - //rule1: in creative mode, top-level claims can't be moved or resized smaller. - //rule2: in any mode, shrinking a claim removes any surface fluids + //special rule for making a top-level claim smaller. to check this, verifying the old claim's corners are inside the new claim's boundaries. + //rule: in any mode, shrinking a claim removes any surface fluids Claim oldClaim = playerData.claimResizing; boolean smaller = false; if(oldClaim.parent == null) @@ -1576,13 +1574,6 @@ class PlayerEventHandler implements Listener { smaller = true; - //enforce creative mode rule - if(!GriefPrevention.instance.config_claims_allowUnclaimInCreative && !player.hasPermission("griefprevention.deleteclaims") && GriefPrevention.instance.creativeRulesApply(player.getLocation())) - { - GriefPrevention.sendMessage(player, TextMode.Err, Messages.NoCreativeUnClaim); - return; - } - //remove surface fluids about to be unclaimed oldClaim.removeSurfaceFluids(newClaim); } diff --git a/src/me/ryanhamshire/GriefPrevention/UUIDFetcher.java b/src/me/ryanhamshire/GriefPrevention/UUIDFetcher.java index 996252a..f027fb5 100644 --- a/src/me/ryanhamshire/GriefPrevention/UUIDFetcher.java +++ b/src/me/ryanhamshire/GriefPrevention/UUIDFetcher.java @@ -18,7 +18,7 @@ import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.Callable; -class UUIDFetcher implements Callable> { +class UUIDFetcher { 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(); @@ -29,7 +29,7 @@ class UUIDFetcher implements Callable> { private static HashMap lookupCache; public UUIDFetcher(List names, boolean rateLimiting) { - this.names = ImmutableList.copyOf(names); + this.names = names; this.rateLimiting = rateLimiting; } @@ -37,26 +37,98 @@ class UUIDFetcher implements Callable> { 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); + public void call() throws Exception + { + if(lookupCache == null) + { + lookupCache = new HashMap(); + } + + GriefPrevention.AddLogEntry("UUID conversion process started. Please be patient - this may take a while."); + + //try to get correct casing from local data + OfflinePlayer [] players = GriefPrevention.instance.getServer().getOfflinePlayers(); + GriefPrevention.AddLogEntry("Checking local server data to get correct casing for player names..."); + for(int i = 0; i < names.size(); i++) + { + String name = names.get(i); + for(OfflinePlayer player : players) + { + if(player.getName().equalsIgnoreCase(name)) + { + if(!player.getName().equals(name)) + { + GriefPrevention.AddLogEntry(name + " --> " + player.getName()); + names.set(i, player.getName()); + continue; + } + } + } + } + + //look for local data first + GriefPrevention.AddLogEntry("Checking local server data for UUIDs already seen..."); + for(int i = 0; i < names.size(); i++) + { + String name = names.get(i); + for(OfflinePlayer player : players) + { + if(player.getName().equalsIgnoreCase(name)) + { + UUID uuid = player.getUniqueId(); + if(uuid != null) + { + GriefPrevention.AddLogEntry(name + " --> " + uuid.toString()); + lookupCache.put(name, uuid); + lookupCache.put(name.toLowerCase(), uuid); + names.remove(i--); + continue; + } + } + } + } + + //for online mode, call Mojang to resolve the rest + if(GriefPrevention.instance.getServer().getOnlineMode()) + { + GriefPrevention.AddLogEntry("Calling Mojang to get UUIDs for remaining unresolved players (this is the slowest step)..."); + + 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); + GriefPrevention.AddLogEntry(name + " --> " + uuid.toString()); + lookupCache.put(name, uuid); + lookupCache.put(name.toLowerCase(), uuid); + } + if (rateLimiting && i != requests - 1) { + Thread.sleep(200L); + } + } + } + + //for offline mode, generate UUIDs for the rest + else + { + GriefPrevention.AddLogEntry("Generating offline mode UUIDs for remaining unresolved players..."); + + for(int i = 0; i < names.size(); i++) + { + String name = names.get(i); + UUID uuid = java.util.UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)); + GriefPrevention.AddLogEntry(name + " --> " + uuid.toString()); + lookupCache.put(name, uuid); + lookupCache.put(name.toLowerCase(), uuid); } } - return uuidMap; } private static void writeBody(HttpURLConnection connection, String body) throws Exception { @@ -100,76 +172,14 @@ class UUIDFetcher implements Callable> { 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; - - //use local minecraft player data to try correcting a name to the correct casing - String correctCasingName = getNameWithCasing(name); - - //online mode: look it up by calling Mojang's web service - if(GriefPrevention.instance.getServer().getOnlineMode() == true) - { - result = new UUIDFetcher(Arrays.asList(name)).call().get(correctCasingName); - } - - //offline mode best guess - else - { - //search server's minecraft player data to find a UUID - OfflinePlayer [] players = GriefPrevention.instance.getServer().getOfflinePlayers(); - for(OfflinePlayer player : players) - { - if(player.getName().equals(correctCasingName)) - { - result = player.getUniqueId(); - break; - } - } - - //if that doesn't work, make a wild guess by imitating what Mojang reportedly does - if(result == null) - { - result = java.util.UUID.nameUUIDFromBytes(("OfflinePlayer:" + correctCasingName).getBytes(Charsets.UTF_8)); - } - } - - //if none of the above worked, throw up our hands and report the problem in the logs + //throw up our hands and report the problem in the logs //this player will lose his land claim blocks, but claims will stay in place as admin claims - if(result == null) - { - GriefPrevention.AddLogEntry(correctCasingName + " --> ???"); - lookupCache.put(name, null); - throw new IllegalArgumentException(name); - } - GriefPrevention.AddLogEntry(correctCasingName + " --> " + result.toString()); - lookupCache.put(name, result); + throw new IllegalArgumentException(name); } - 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; + return result; } } \ No newline at end of file diff --git a/src/me/ryanhamshire/GriefPrevention/Visualization.java b/src/me/ryanhamshire/GriefPrevention/Visualization.java index 28c70ce..d7ed81d 100644 --- a/src/me/ryanhamshire/GriefPrevention/Visualization.java +++ b/src/me/ryanhamshire/GriefPrevention/Visualization.java @@ -66,6 +66,8 @@ public class Visualization for(int i = 0; i < visualization.elements.size(); i++) { VisualizationElement element = visualization.elements.get(i); + if(!element.location.getChunk().isLoaded()) continue; + if(element.location.distanceSquared(player.getLocation()) > 10000) continue; Block block = element.location.getBlock(); player.sendBlockChange(element.location, block.getType(), block.getData()); } @@ -198,7 +200,14 @@ public class Visualization //finds a block the player can probably see. this is how visualizations "cling" to the ground or ceiling private static Location getVisibleLocation(World world, int x, int y, int z) { - Block block = world.getBlockAt(x, y, z); + //cheap distance check - also avoids loading chunks just for a big visualization + Location location = new Location(world, x, y, z); + if(!location.getChunk().isLoaded()) + { + return location; + } + + Block block = world.getBlockAt(x, y, z); BlockFace direction = (isTransparent(block)) ? BlockFace.DOWN : BlockFace.UP; while( block.getY() >= 1 && diff --git a/src/me/ryanhamshire/GriefPrevention/VisualizationApplicationTask.java b/src/me/ryanhamshire/GriefPrevention/VisualizationApplicationTask.java index 774093e..0380d9b 100644 --- a/src/me/ryanhamshire/GriefPrevention/VisualizationApplicationTask.java +++ b/src/me/ryanhamshire/GriefPrevention/VisualizationApplicationTask.java @@ -43,6 +43,7 @@ class VisualizationApplicationTask implements Runnable VisualizationElement element = visualization.elements.get(i); //send the player a fake block change event + if(!element.location.getChunk().isLoaded()) continue; //cheap distance check player.sendBlockChange(element.location, element.visualizedMaterial, element.visualizedData); }