Misc 1.14 updates:

* using "force loaded" chunk flag instead of canceling ChunkUnloadEvents, which is no longer possible as of Spigot 1.14
* replaced UUID lookup code with newer and slightly improved implementation
* added new 1.14 wooden sign variations to safe open blocks list
* fix for error if Fill was canceled or finished without actually loading any chunks
* added notice to end of Trim recommending server restart
This commit is contained in:
Brettflan 2019-05-23 19:48:54 -05:00
parent 179d1cfda3
commit e14d526541
9 changed files with 218 additions and 181 deletions

View File

@ -327,8 +327,6 @@ public class BorderData
safeOpenBlocks.add(Material.WALL_TORCH);
safeOpenBlocks.add(Material.REDSTONE_WIRE);
safeOpenBlocks.add(Material.WHEAT);
safeOpenBlocks.add(Material.SIGN);
safeOpenBlocks.add(Material.WALL_SIGN);
safeOpenBlocks.add(Material.LADDER);
safeOpenBlocks.add(Material.LEVER);
safeOpenBlocks.add(Material.LIGHT_WEIGHTED_PRESSURE_PLATE);
@ -376,6 +374,22 @@ public class BorderData
safeOpenBlocks.add(Material.TALL_GRASS);
safeOpenBlocks.add(Material.LARGE_FERN);
safeOpenBlocks.add(Material.BEETROOTS);
try
{ // signs in 1.14 can be different wood types
safeOpenBlocks.add(Material.ACACIA_SIGN);
safeOpenBlocks.add(Material.ACACIA_WALL_SIGN);
safeOpenBlocks.add(Material.BIRCH_SIGN);
safeOpenBlocks.add(Material.BIRCH_WALL_SIGN);
safeOpenBlocks.add(Material.DARK_OAK_SIGN);
safeOpenBlocks.add(Material.DARK_OAK_WALL_SIGN);
safeOpenBlocks.add(Material.JUNGLE_SIGN);
safeOpenBlocks.add(Material.JUNGLE_WALL_SIGN);
safeOpenBlocks.add(Material.OAK_SIGN);
safeOpenBlocks.add(Material.OAK_WALL_SIGN);
safeOpenBlocks.add(Material.SPRUCE_SIGN);
safeOpenBlocks.add(Material.SPRUCE_WALL_SIGN);
}
catch (NoSuchFieldError ex) {}
}
//these material IDs are ones we don't want to drop the player onto, like cactus or lava or fire or activated Ender portal

View File

@ -1,55 +0,0 @@
package com.wimbli.WorldBorder.UUID;
import com.google.common.collect.ImmutableList;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
/*
* code by evilmidget38
* from http://forums.bukkit.org/threads/player-name-uuid-fetcher.250926/
*/
public class NameFetcher implements Callable<Map<UUID, String>>
{
private static final String PROFILE_URL = "https://sessionserver.mojang.com/session/minecraft/profile/";
private final JSONParser jsonParser = new JSONParser();
private final List<UUID> uuids;
public NameFetcher(List<UUID> uuids)
{
this.uuids = ImmutableList.copyOf(uuids);
}
@Override
public Map<UUID, String> call() throws Exception
{
Map<UUID, String> uuidStringMap = new HashMap<UUID, String>();
for (UUID uuid: uuids)
{
HttpURLConnection connection = (HttpURLConnection) new URL(PROFILE_URL+uuid.toString().replace("-", "")).openConnection();
JSONObject response = (JSONObject) jsonParser.parse(new InputStreamReader(connection.getInputStream()));
String name = (String) response.get("name");
if (name == null)
{
continue;
}
String cause = (String) response.get("cause");
String errorMessage = (String) response.get("errorMessage");
if (cause != null && cause.length() > 0)
{
throw new IllegalStateException(errorMessage);
}
uuidStringMap.put(uuid, name);
}
return uuidStringMap;
}
}

View File

@ -1,116 +1,149 @@
package com.wimbli.WorldBorder.UUID;
import com.google.common.collect.ImmutableList;
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;
/*
* code by evilmidget38
* from http://forums.bukkit.org/threads/player-name-uuid-fetcher.250926/
* slightly modified to fix name case mismatches for single name lookup
* This code mostly taken from https://gist.github.com/Jofkos/d0c469528b032d820f42
*/
public class UUIDFetcher implements Callable<Map<String, UUID>>
{
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<String> names;
private final boolean rateLimiting;
package com.wimbli.WorldBorder.UUID;
public UUIDFetcher(List<String> names, boolean rateLimiting)
{
this.names = ImmutableList.copyOf(names);
this.rateLimiting = rateLimiting;
}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
public UUIDFetcher(List<String> names)
{
this(names, true);
}
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public Map<String, UUID> call() throws Exception
{
Map<String, UUID> uuidMap = new HashMap<String, UUID>();
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.toLowerCase(), 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();
}
public class UUIDFetcher {
/**
* Date when name changes were introduced
* @see UUIDFetcher#getUUIDAt(String, long)
*/
public static final long FEBRUARY_2015 = 1422748800000L;
private static Gson gson = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).create();
private static final String UUID_URL = "https://api.mojang.com/users/profiles/minecraft/%s?at=%d";
private static final String NAME_URL = "https://api.mojang.com/user/profiles/%s/names";
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 Map<String, UUID> uuidCache = new HashMap<String, UUID>();
private static Map<UUID, String> nameCache = new HashMap<UUID, String>();
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));
}
private static ExecutorService pool = Executors.newCachedThreadPool();
private String name;
private UUID id;
/**
* Fetches the uuid asynchronously and passes it to the consumer
*
* @param name The name
* @param action Do what you want to do with the uuid her
*/
public static void getUUID(String name, Consumer<UUID> action) {
pool.execute(() -> action.accept(getUUID(name)));
}
/**
* Fetches the uuid synchronously and returns it
*
* @param name The name
* @return The uuid
*/
public static UUID getUUID(String name) {
return getUUIDAt(name, System.currentTimeMillis());
}
/**
* Fetches the uuid synchronously for a specified name and time and passes the result to the consumer
*
* @param name The name
* @param timestamp Time when the player had this name in milliseconds
* @param action Do what you want to do with the uuid her
*/
public static void getUUIDAt(String name, long timestamp, Consumer<UUID> action) {
pool.execute(() -> action.accept(getUUIDAt(name, timestamp)));
}
/**
* Fetches the uuid synchronously for a specified name and time
*
* @param name The name
* @param timestamp Time when the player had this name in milliseconds
* @see UUIDFetcher#FEBRUARY_2015
*/
public static UUID getUUIDAt(String name, long timestamp) {
name = name.toLowerCase();
if (uuidCache.containsKey(name)) {
return uuidCache.get(name);
}
try {
HttpURLConnection connection = (HttpURLConnection) new URL(String.format(UUID_URL, name, timestamp/1000)).openConnection();
connection.setReadTimeout(5000);
UUIDFetcher data = gson.fromJson(new BufferedReader(new InputStreamReader(connection.getInputStream())), UUIDFetcher.class);
uuidCache.put(name, data.id);
nameCache.put(data.id, data.name);
public static byte[] toBytes(UUID uuid)
{
ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]);
byteBuffer.putLong(uuid.getMostSignificantBits());
byteBuffer.putLong(uuid.getLeastSignificantBits());
return byteBuffer.array();
}
return data.id;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Fetches the name asynchronously and passes it to the consumer
*
* @param uuid The uuid
* @param action Do what you want to do with the name her
*/
public static void getName(UUID uuid, Consumer<String> action) {
pool.execute(() -> action.accept(getName(uuid)));
}
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);
}
/**
* Fetches the name synchronously and returns it
*
* @param uuid The uuid
* @return The name
*/
public static String getName(UUID uuid) {
if (nameCache.containsKey(uuid)) {
return nameCache.get(uuid);
}
try {
HttpURLConnection connection = (HttpURLConnection) new URL(String.format(NAME_URL, UUIDTypeAdapter.fromUUID(uuid))).openConnection();
connection.setReadTimeout(5000);
UUIDFetcher[] nameHistory = gson.fromJson(new BufferedReader(new InputStreamReader(connection.getInputStream())), UUIDFetcher[].class);
UUIDFetcher currentNameData = nameHistory[nameHistory.length - 1];
public static UUID getUUIDOf(String name) throws Exception
{
return new UUIDFetcher(Arrays.asList(name)).call().get(name.toLowerCase());
}
uuidCache.put(currentNameData.name.toLowerCase(), uuid);
nameCache.put(uuid, currentNameData.name);
return currentNameData.name;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static Map<UUID, String> getNameList(ArrayList<UUID> uuids) {
Map<UUID, String> uuidStringMap = new HashMap<>();
for (UUID uuid: uuids)
{
uuidStringMap.put(uuid, getName(uuid));
}
return uuidStringMap;
}
}

View File

@ -0,0 +1,32 @@
/*
* This code from: https://github.com/eitetu/minecraft-server/blob/master/src/main/java/com/eitetu/minecraft/server/util/UUIDTypeAdapter.java
*/
package com.wimbli.WorldBorder.UUID;
import java.io.IOException;
import java.util.UUID;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
public class UUIDTypeAdapter extends TypeAdapter<UUID> {
public void write(JsonWriter out, UUID value) throws IOException {
out.value(fromUUID(value));
}
public UUID read(JsonReader in) throws IOException {
return fromString(in.nextString());
}
public static String fromUUID(UUID value) {
return value.toString().replace("-", "");
}
public static UUID fromString(String input) {
return UUID.fromString(input.replaceFirst(
"(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"));
}
}

View File

@ -7,8 +7,8 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.player.PlayerPortalEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.Location;
import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.Location;
public class WBListener implements Listener
@ -73,21 +73,21 @@ public class WBListener implements Listener
/*
* Check if there is a fill task running, and if yes, if it's for the
* world that the unload event refers to and if the chunk should be
* kept in memory because generation still needs it.
* world that the unload event refers to, set "force loaded" flag off
* and track if chunk was somehow on unload prevention list
*/
@EventHandler
public void onChunkUnload(ChunkUnloadEvent e)
{
if (Config.fillTask!=null)
{
Chunk chunk=e.getChunk();
if (e.getWorld() == Config.fillTask.getWorld()
&& Config.fillTask.chunkOnUnloadPreventionList(chunk.getX(), chunk.getZ()))
{
e.setCancelled(true);
}
}
if (Config.fillTask == null)
return;
Chunk chunk = e.getChunk();
if (e.getWorld() != Config.fillTask.getWorld())
return;
// just to be on the safe side, in case it's still set at this point somehow
chunk.setForceLoaded(false);
}
}

View File

@ -247,7 +247,10 @@ public class WorldFillTask implements Runnable
for (CoordXZ unload: chunksToUnload)
{
if (!chunkOnUnloadPreventionList(unload.x, unload.z))
{
world.setChunkForceLoaded(unload.x, unload.z, false);
world.unloadChunkRequest(unload.x, unload.z);
}
}
// Put some damper on chunksPerRun. We don't want the queue to be too
@ -312,6 +315,7 @@ public class WorldFillTask implements Runnable
}
}
world.setChunkForceLoaded(x, z, true); // toggle "force loaded" flag on for chunk to prevent it from being unloaded while we need it
pendingChunks.put(PaperLib.getChunkAtAsync(world, x, z, true), new CoordXZ(x, z));
// There need to be enough nearby chunks loaded to make the server populate a chunk with trees, snow, etc.
@ -319,10 +323,12 @@ public class WorldFillTask implements Runnable
int popX = !isZLeg ? x : (x + (isNeg ? -1 : 1));
int popZ = isZLeg ? z : (z + (!isNeg ? -1 : 1));
world.setChunkForceLoaded(popX, popZ, true);
pendingChunks.put(PaperLib.getChunkAtAsync(world, popX, popZ, false), new CoordXZ(popX, popZ));
preventUnload.add(new UnloadDependency(popX, popZ, x, z));
// make sure the previous chunk in our spiral is loaded as well (might have already existed and been skipped over)
world.setChunkForceLoaded(lastChunk.x, lastChunk.z, true);
pendingChunks.put(PaperLib.getChunkAtAsync(world, lastChunk.x, lastChunk.z, false), new CoordXZ(lastChunk.x, lastChunk.z)); // <-- new CoordXZ as lastChunk isn't immutable
preventUnload.add(new UnloadDependency(lastChunk.x, lastChunk.z, x, z));
@ -437,13 +443,17 @@ public class WorldFillTask implements Runnable
server = null;
// go ahead and unload any chunks we still have loaded
// Set preventUnload to emptry first so the ChunkUnloadEvent Listener
// Set preventUnload to empty first so the ChunkUnloadEvent Listener
// doesn't get in our way
Set<UnloadDependency> tempPreventUnload = preventUnload;
preventUnload = null;
for (UnloadDependency entry: tempPreventUnload)
if (preventUnload != null)
{
world.unloadChunkRequest(entry.neededX, entry.neededZ);
Set<UnloadDependency> tempPreventUnload = preventUnload;
preventUnload = null;
for (UnloadDependency entry: tempPreventUnload)
{
world.setChunkForceLoaded(entry.neededX, entry.neededZ, false);
world.unloadChunkRequest(entry.neededX, entry.neededZ);
}
}
}

View File

@ -363,6 +363,10 @@ public class WorldTrimTask implements Runnable
if (taskID != -1)
server.getScheduler().cancelTask(taskID);
server = null;
sendMessage("NOTICE: it is recommended that you restart your server after a Trim, to be on the safe side.");
if (DynMapFeatures.renderEnabled())
sendMessage("This especially true with DynMap. You should also run a fullrender in DynMap for the trimmed world after restarting, so trimmed chunks are updated on the map.");
}
// is this task still valid/workable?

View File

@ -65,7 +65,7 @@ public class CmdBypass extends WBCmd
// only do UUID lookup using Mojang server if specified player isn't online
try
{
uPlayer = UUIDFetcher.getUUIDOf(sPlayer);
uPlayer = UUIDFetcher.getUUID(sPlayer);
}
catch(Exception ex)
{

View File

@ -10,7 +10,7 @@ import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*;
import com.wimbli.WorldBorder.UUID.NameFetcher;
import com.wimbli.WorldBorder.UUID.UUIDFetcher;
public class CmdBypasslist extends WBCmd
@ -42,8 +42,7 @@ public class CmdBypasslist extends WBCmd
{
try
{
NameFetcher fetcher = new NameFetcher(uuids);
Map<UUID, String> names = fetcher.call();
Map<UUID, String> names = UUIDFetcher.getNameList(uuids);
String nameString = names.values().toString();
sender.sendMessage("Players with border bypass enabled: " + nameString.substring(1, nameString.length() - 1));