diff --git a/Spigot-Server-Patches/0003-MC-Dev-fixes.patch b/Spigot-Server-Patches/0003-MC-Dev-fixes.patch index 8ab89c36d3..5159b04579 100644 --- a/Spigot-Server-Patches/0003-MC-Dev-fixes.patch +++ b/Spigot-Server-Patches/0003-MC-Dev-fixes.patch @@ -1,4 +1,4 @@ -From cf11aafeb1a4dbcca311cf32504a70ae4dfed163 Mon Sep 17 00:00:00 2001 +From ea6a8d7b47481375de1dc77526210e0791927114 Mon Sep 17 00:00:00 2001 From: Aikar Date: Wed, 30 Mar 2016 19:36:20 -0400 Subject: [PATCH] MC Dev fixes @@ -470,6 +470,19 @@ index 41a5d1dc29..b3799ab564 100644 acompletablefuture[i] = completablefuture1.whenComplete((object, throwable) -> { if (throwable != null) { completablefuture.completeExceptionally(throwable); +diff --git a/src/main/java/net/minecraft/server/Ticket.java b/src/main/java/net/minecraft/server/Ticket.java +index fc8cd29739..74d6e3d2f5 100644 +--- a/src/main/java/net/minecraft/server/Ticket.java ++++ b/src/main/java/net/minecraft/server/Ticket.java +@@ -24,7 +24,7 @@ public final class Ticket implements Comparable> { + } else { + int j = Integer.compare(System.identityHashCode(this.a), System.identityHashCode(ticket.a)); + +- return j != 0 ? j : this.a.a().compare(this.c, ticket.c); ++ return j != 0 ? j : this.a.a().compare(this.c, (T)ticket.c); // Paper - decompile fix + } + } + diff --git a/src/main/java/net/minecraft/server/VillagerTrades.java b/src/main/java/net/minecraft/server/VillagerTrades.java index 73f9da1fdb..2a4e4f7859 100644 --- a/src/main/java/net/minecraft/server/VillagerTrades.java diff --git a/Spigot-Server-Patches/0398-Chunk-debug-command.patch b/Spigot-Server-Patches/0398-Chunk-debug-command.patch new file mode 100644 index 0000000000..ba2bc28d34 --- /dev/null +++ b/Spigot-Server-Patches/0398-Chunk-debug-command.patch @@ -0,0 +1,403 @@ +From 31b83772019ef71b865f779b6ef7f253a8ea323b Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 1 Jun 2019 13:00:55 -0700 +Subject: [PATCH] Chunk debug command + +Prints all chunk information to a text file into the debug +folder in the root server folder. The format is in JSON, and +the data format is described in MCUtil#dumpChunks(File) + +The command will output server version and all online players to the +file as well. We do not log anything but the location, world and +username of the player. + +Also logs the value of these config values (note not all are paper's): +- keep spawn loaded value +- spawn radius +- view distance + +Each chunk has the following logged: +- Coordinate +- Ticket level & its corresponding state +- Whether it is queued for unload +- Chunk status (may be unloaded) +- All tickets on the chunk + +Example log: +https://gist.githubusercontent.com/Spottedleaf/0131e7710ffd5d531e5fd246c3367380/raw/169ae1b2e240485f99bc7a6bd8e78d90e3af7397/chunks-2019-06-01_19.57.05.txt + +For references on certain keywords (ticket, status, etc), please see: + +https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528273&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528273 +https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528577&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528577 + +diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +index 352a39dcb3..4a7939472d 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java ++++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java +@@ -28,14 +28,14 @@ public class PaperCommand extends Command { + public PaperCommand(String name) { + super(name); + this.description = "Paper related commands"; +- this.usageMessage = "/paper [heap | entity | reload | version]"; ++ this.usageMessage = "/paper [heap | entity | reload | version | debug]"; + this.setPermission("bukkit.command.paper"); + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { + if (args.length <= 1) +- return getListMatchingLast(args, "heap", "entity", "reload", "version"); ++ return getListMatchingLast(args, "heap", "entity", "reload", "version", "debug"); + + switch (args[0].toLowerCase(Locale.ENGLISH)) + { +@@ -45,6 +45,10 @@ public class PaperCommand extends Command { + if (args.length == 3) + return getListMatchingLast(args, EntityTypes.getEntityNameList().stream().map(MinecraftKey::toString).sorted().toArray(String[]::new)); + break; ++ case "debug": ++ if (args.length == 2) { ++ return getListMatchingLast(args, "help", "chunks"); ++ } + } + return Collections.emptyList(); + } +@@ -109,6 +113,9 @@ public class PaperCommand extends Command { + case "reload": + doReload(sender); + break; ++ case "debug": ++ doDebug(sender, args); ++ break; + case "ver": + case "version": + Command ver = org.bukkit.Bukkit.getServer().getCommandMap().getCommand("version"); +@@ -125,6 +132,39 @@ public class PaperCommand extends Command { + return true; + } + ++ private void doDebug(CommandSender sender, String[] args) { ++ if (args.length < 2) { ++ sender.sendMessage(ChatColor.RED + "Use /paper debug [chunks] help for more information on a specific command"); ++ return; ++ } ++ ++ String debugType = args[1].toLowerCase(Locale.ENGLISH); ++ switch (debugType) { ++ case "chunks": ++ if (args.length >= 3 && args[2].toLowerCase(Locale.ENGLISH).equals("help")) { ++ sender.sendMessage(ChatColor.RED + "Use /paper debug chunks to dump loaded chunk information to a file"); ++ break; ++ } ++ File file = new File(new File(new File("."), "debug"), ++ "chunks-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); ++ sender.sendMessage(ChatColor.GREEN + "Writing chunk information dump to " + file.toString()); ++ try { ++ MCUtil.dumpChunks(file); ++ sender.sendMessage(ChatColor.GREEN + "Successfully written chunk information!"); ++ } catch (Throwable thr) { ++ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); ++ sender.sendMessage(ChatColor.RED + "Failed to dump chunk information, see console"); ++ } ++ ++ break; ++ case "help": ++ // fall through to default ++ default: ++ sender.sendMessage(ChatColor.RED + "Use /paper debug [chunks] help for more information on a specific command"); ++ return; ++ } ++ } ++ + /* + * Ported from MinecraftForge - author: LexManos - License: LGPLv2.1 + */ +diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java +index d3c2ad3c40..705ca68798 100644 +--- a/src/main/java/net/minecraft/server/ChunkMapDistance.java ++++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java +@@ -33,7 +33,7 @@ public abstract class ChunkMapDistance { + private static final int b = 33 + ChunkStatus.a(ChunkStatus.FULL) - 2; + private final Long2ObjectMap> c = new Long2ObjectOpenHashMap(); + private final Long2ObjectMap> d = new Long2ObjectOpenHashMap(); +- private final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); ++ final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); // Paper -> private -> package + private final ChunkMapDistance.a f = new ChunkMapDistance.a(); + private final ChunkMapDistance.c g = new ChunkMapDistance.c(); + private int entitydistance; +diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java +index 0e941b520f..a6a9f1923f 100644 +--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java ++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java +@@ -23,7 +23,7 @@ import org.apache.logging.log4j.Logger; + public class ChunkProviderServer extends IChunkProvider { + + private static final int b = (int) Math.pow(17.0D, 2.0D); +- private static final List c = ChunkStatus.a(); ++ private static final List c = ChunkStatus.a(); static final List getPossibleChunkStatuses() { return ChunkProviderServer.c; } // Paper - OBFHELPER + private final ChunkMapDistance chunkMapDistance; + public final ChunkGenerator chunkGenerator; + private final WorldServer world; +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index ec3732193f..fa0763cd9c 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -4,7 +4,13 @@ import com.destroystokyo.paper.block.TargetBlockInfo; + import com.destroystokyo.paper.profile.CraftPlayerProfile; + import com.destroystokyo.paper.profile.PlayerProfile; + import com.google.common.util.concurrent.ThreadFactoryBuilder; ++import com.google.gson.JsonArray; ++import com.google.gson.JsonObject; ++import com.google.gson.internal.Streams; ++import com.google.gson.stream.JsonWriter; + import com.mojang.authlib.GameProfile; ++import com.mojang.datafixers.util.Either; ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; + import org.apache.commons.lang.exception.ExceptionUtils; + import org.bukkit.Location; + import org.bukkit.block.BlockFace; +@@ -14,7 +20,11 @@ import org.spigotmc.AsyncCatcher; + + import javax.annotation.Nonnull; + import javax.annotation.Nullable; ++import java.io.*; ++import java.util.ArrayList; ++import java.util.List; + import java.util.Queue; ++import java.util.Set; + import java.util.concurrent.CompletableFuture; + import java.util.concurrent.ExecutionException; + import java.util.concurrent.Executor; +@@ -354,4 +364,170 @@ public final class MCUtil { + + return null; + } ++ ++ public static ChunkStatus getChunkStatus(PlayerChunk chunk) { ++ List statuses = ChunkProviderServer.getPossibleChunkStatuses(); ++ for (int i = statuses.size() - 1; i >= 0; --i) { ++ ChunkStatus curr = statuses.get(i); ++ CompletableFuture> future = chunk.getStatusFutureUnchecked(curr); ++ if (future != PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE) { ++ return curr; ++ } ++ } ++ return null; // unloaded ++ } ++ ++ public static void dumpChunks(File file) throws IOException { ++ file.getParentFile().mkdirs(); ++ file.createNewFile(); ++ /* ++ * Json format: ++ * ++ * Main data format: ++ * -server-version: ++ * -data-version: ++ * -worlds: ++ * -name: ++ * -view-distance: ++ * -keep-spawn-loaded: ++ * -keep-spawn-loaded-range: ++ * -visible-chunk-count: ++ * -loaded-chunk-count: ++ * -verified-fully-loaded-chunks: ++ * -players: ++ * -chunk-data: ++ * ++ * Player format: ++ * -name: ++ * -x: ++ * -y: ++ * -z: ++ * ++ * Chunk Format: ++ * -x: ++ * -z: ++ * -ticket-level: ++ * -state: ++ * -queued-for-unload: ++ * -status: ++ * -tickets: ++ * ++ * ++ * Ticket format: ++ * -ticket-type: ++ * -ticket-level: ++ * -add-tick: ++ * -object-reason: // This depends on the type of ticket. ie POST_TELEPORT -> entity id ++ */ ++ List worlds = org.bukkit.Bukkit.getWorlds(); ++ JsonObject data = new JsonObject(); ++ ++ data.addProperty("server-version", org.bukkit.Bukkit.getVersion()); ++ data.addProperty("data-version", 0); ++ ++ JsonArray worldsData = new JsonArray(); ++ ++ for (org.bukkit.World bukkitWorld : worlds) { ++ JsonObject worldData = new JsonObject(); ++ ++ WorldServer world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle(); ++ PlayerChunkMap chunkMap = world.getChunkProvider().playerChunkMap; ++ Long2ObjectLinkedOpenHashMap visibleChunks = chunkMap.visibleChunks; ++ ChunkMapDistance chunkMapDistance = chunkMap.getChunkMapDistanceManager(); ++ List allChunks = new ArrayList<>(visibleChunks.values()); ++ List players = world.players; ++ ++ int fullLoadedChunks = 0; ++ ++ for (PlayerChunk chunk : allChunks) { ++ if (chunk.getFullChunk() != null) { ++ ++fullLoadedChunks; ++ } ++ } ++ ++ // sorting by coordinate makes the log easier to read ++ allChunks.sort((PlayerChunk v1, PlayerChunk v2) -> { ++ if (v1.location.x != v2.location.x) { ++ return Integer.compare(v1.location.x, v2.location.x); ++ } ++ return Integer.compare(v1.location.z, v2.location.z); ++ }); ++ ++ worldData.addProperty("name", world.getWorldData().getName()); ++ worldData.addProperty("view-distance", world.spigotConfig.viewDistance); ++ worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory); ++ worldData.addProperty("keep-spawn-loaded-range", world.paperConfig.keepLoadedRange); ++ worldData.addProperty("visible-chunk-count", visibleChunks.size()); ++ worldData.addProperty("loaded-chunk-count", chunkMap.loadedChunks.size()); ++ worldData.addProperty("verified-fully-loaded-chunks", fullLoadedChunks); ++ ++ JsonArray playersData = new JsonArray(); ++ ++ for (EntityPlayer player : players) { ++ JsonObject playerData = new JsonObject(); ++ ++ playerData.addProperty("name", player.getName()); ++ playerData.addProperty("x", player.locX); ++ playerData.addProperty("y", player.locY); ++ playerData.addProperty("z", player.locZ); ++ ++ playersData.add(playerData); ++ ++ } ++ ++ worldData.add("players", playersData); ++ ++ JsonArray chunksData = new JsonArray(); ++ ++ for (PlayerChunk playerChunk : allChunks) { ++ JsonObject chunkData = new JsonObject(); ++ ++ Set> tickets = chunkMapDistance.tickets.get(playerChunk.location.pair()); ++ ChunkStatus status = getChunkStatus(playerChunk); ++ ++ chunkData.addProperty("x", playerChunk.location.x); ++ chunkData.addProperty("z", playerChunk.location.z); ++ chunkData.addProperty("ticket-level", playerChunk.getTicketLevel()); ++ chunkData.addProperty("state", PlayerChunk.getChunkState(playerChunk.getTicketLevel()).toString()); ++ chunkData.addProperty("queued-for-unload", chunkMap.unloadQueue.contains(playerChunk.location.pair())); ++ chunkData.addProperty("status", status == null ? "unloaded" : status.toString()); ++ ++ JsonArray ticketsData = new JsonArray(); ++ ++ if (tickets != null) { ++ for (Ticket ticket : tickets) { ++ JsonObject ticketData = new JsonObject(); ++ ++ ticketData.addProperty("ticket-type", ticket.getTicketType().toString()); ++ ticketData.addProperty("ticket-level", ticket.getTicketLevel()); ++ ticketData.addProperty("object-reason", String.valueOf(ticket.getObjectReason())); ++ ticketData.addProperty("add-tick", ticket.getCreationTick()); ++ ++ ticketsData.add(ticketData); ++ } ++ } ++ ++ chunkData.add("tickets", ticketsData); ++ chunksData.add(chunkData); ++ } ++ ++ ++ worldData.add("chunk-data", chunksData); ++ worldsData.add(worldData); ++ } ++ ++ data.add("worlds", worldsData); ++ ++ StringWriter stringWriter = new StringWriter(); ++ JsonWriter jsonWriter = new JsonWriter(stringWriter); ++ jsonWriter.setIndent(" "); ++ jsonWriter.setLenient(false); ++ Streams.write(data, jsonWriter); ++ ++ String fileData = stringWriter.toString(); ++ ++ try (PrintStream out = new PrintStream(new FileOutputStream(file), false, "UTF-8")) { ++ out.print(fileData); ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java +index 0d8cddeb62..22739ad8a1 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunk.java ++++ b/src/main/java/net/minecraft/server/PlayerChunk.java +@@ -26,7 +26,7 @@ public class PlayerChunk { + public int oldTicketLevel; + private int ticketLevel; + private int n; +- private final ChunkCoordIntPair location; ++ final ChunkCoordIntPair location; // Paper - private -> package + private final short[] dirtyBlocks; + private int dirtyCount; + private int r; +diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java +index 276a365ff5..ef921005c5 100644 +--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java +@@ -53,7 +53,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + public final Long2ObjectLinkedOpenHashMap updatingChunks = new Long2ObjectLinkedOpenHashMap(); + public volatile Long2ObjectLinkedOpenHashMap visibleChunks; + private final Long2ObjectLinkedOpenHashMap pendingUnload; +- private final LongSet loadedChunks; ++ final LongSet loadedChunks; // Paper - private -> package + public final WorldServer world; + private final LightEngineThreaded lightEngine; + private final IAsyncTaskHandler executor; +@@ -66,7 +66,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + private final Mailbox> mailboxWorldGen; + private final Mailbox> mailboxMain; + public final WorldLoadListener worldLoadListener; +- private final PlayerChunkMap.a u; ++ private final PlayerChunkMap.a u; public final PlayerChunkMap.a getChunkMapDistanceManager() { return this.u; } // Paper - OBFHELPER + private final AtomicInteger v; + private final DefinedStructureManager definedStructureManager; + private final File x; +diff --git a/src/main/java/net/minecraft/server/Ticket.java b/src/main/java/net/minecraft/server/Ticket.java +index 74d6e3d2f5..4a1ba0dd80 100644 +--- a/src/main/java/net/minecraft/server/Ticket.java ++++ b/src/main/java/net/minecraft/server/Ticket.java +@@ -6,8 +6,8 @@ public final class Ticket implements Comparable> { + + private final TicketType a; + private final int b; +- private final T c; +- private final long d; ++ private final T c; public final T getObjectReason() { return this.c; } // Paper - OBFHELPER ++ private final long d; public final long getCreationTick() { return this.d; } // Paper - OBFHELPER + + protected Ticket(TicketType tickettype, int i, T t0, long j) { + this.a = tickettype; +@@ -52,6 +52,7 @@ public final class Ticket implements Comparable> { + return this.a; + } + ++ public final int getTicketLevel() { return this.b(); } // Paper - OBFHELPER + public int b() { + return this.b; + } +-- +2.21.0 +