From e29480297798ed8bb5deff95bdb20f1b2594713c Mon Sep 17 00:00:00 2001 From: Jason <11360596+jpenilla@users.noreply.github.com> Date: Fri, 8 Jul 2022 16:01:42 -0700 Subject: [PATCH] Refactor paper command (#8112) * Refactor paper command * Improve paper dumpitem output * Register paper command permissions Would be nice to add descriptions for these too, but that's an enhancement for another time * Update MobcapsCommandTest fail message * Notify on bad radius for fix light * fixup rebase --- patches/server/0009-Paper-command.patch | 678 ++++++++++++------ ...ault-permission-message-configurable.patch | 20 +- patches/server/0322-Chunk-debug-command.patch | 198 +++-- patches/server/0352-Fix-Light-Command.patch | 166 +++-- .../0361-Add-debug-for-sync-chunk-loads.patch | 221 +++--- ...-Add-tick-times-API-and-mspt-command.patch | 28 +- .../server/0443-Paper-dumpitem-command.patch | 130 ++-- ...aper-mobcaps-and-paper-playermobcaps.patch | 202 +++--- .../0738-Do-not-copy-visible-chunks.patch | 14 +- .../0791-Rewrite-the-light-engine.patch | 124 ++-- patches/server/0895-Don-t-tick-markers.patch | 26 +- 11 files changed, 1035 insertions(+), 772 deletions(-) diff --git a/patches/server/0009-Paper-command.patch b/patches/server/0009-Paper-command.patch index 23dbd7cd2..62c835c15 100644 --- a/patches/server/0009-Paper-command.patch +++ b/patches/server/0009-Paper-command.patch @@ -4,102 +4,50 @@ Date: Mon, 29 Feb 2016 21:02:09 -0600 Subject: [PATCH] Paper command -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java +diff --git a/src/main/java/io/papermc/paper/command/CommandUtil.java b/src/main/java/io/papermc/paper/command/CommandUtil.java new file mode 100644 -index 0000000000000000000000000000000000000000..b506cd11b76901827cbe66f46db8df400f7015de +index 0000000000000000000000000000000000000000..953c30500892e5f0c55b8597bc708ea85bf56d6e --- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -0,0 +1,287 @@ -+package com.destroystokyo.paper; ++++ b/src/main/java/io/papermc/paper/command/CommandUtil.java +@@ -0,0 +1,69 @@ ++package io.papermc.paper.command; + +import com.google.common.base.Functions; -+import com.google.common.base.Joiner; -+import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; -+import com.google.common.collect.Maps; -+import net.minecraft.core.Registry; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerChunkCache; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.level.ChunkPos; -+import org.apache.commons.lang3.tuple.MutablePair; -+import org.apache.commons.lang3.tuple.Pair; -+import org.bukkit.Bukkit; -+import org.bukkit.Location; -+import org.bukkit.World; -+import org.bukkit.command.Command; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.CraftServer; -+import org.bukkit.craftbukkit.CraftWorld; -+import org.bukkit.entity.Player; -+ -+import java.io.File; -+import java.time.LocalDateTime; -+import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; -+import java.util.Collections; +import java.util.Iterator; +import java.util.List; -+import java.util.Locale; -+import java.util.Map; -+import java.util.Set; -+import java.util.stream.Collectors; ++import net.minecraft.resources.ResourceLocation; ++import org.bukkit.command.CommandSender; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; + -+import static net.kyori.adventure.text.Component.text; -+import static net.kyori.adventure.text.format.NamedTextColor.GREEN; -+import static net.kyori.adventure.text.format.NamedTextColor.RED; -+import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; -+ -+public class PaperCommand extends Command { -+ private static final String BASE_PERM = "bukkit.command.paper."; -+ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version").build(); -+ -+ public PaperCommand(String name) { -+ super(name); -+ this.description = "Paper related commands"; -+ this.usageMessage = "/paper [" + Joiner.on(" | ").join(SUBCOMMANDS) + "]"; -+ this.setPermission("bukkit.command.paper;" + Joiner.on(';').join(SUBCOMMANDS.stream().map(s -> BASE_PERM + s).collect(Collectors.toSet()))); -+ } -+ -+ private static boolean testPermission(CommandSender commandSender, String permission) { -+ if (commandSender.hasPermission(BASE_PERM + permission) || commandSender.hasPermission("bukkit.command.paper")) return true; -+ commandSender.sendMessage(text("I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.", RED)); -+ return false; -+ } -+ -+ @Override -+ public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { -+ if (args.length <= 1) -+ return getListMatchingLast(sender, args, SUBCOMMANDS); -+ -+ switch (args[0].toLowerCase(Locale.ENGLISH)) -+ { -+ case "entity": -+ if (args.length == 2) -+ return getListMatchingLast(sender, args, "help", "list"); -+ if (args.length == 3) -+ return getListMatchingLast(sender, args, Registry.ENTITY_TYPE.keySet().stream().map(ResourceLocation::toString).sorted().toArray(String[]::new)); -+ break; -+ } -+ return Collections.emptyList(); ++@DefaultQualifier(NonNull.class) ++public final class CommandUtil { ++ private CommandUtil() { + } + + // Code from Mojang - copyright them -+ public static List getListMatchingLast(CommandSender sender, String[] args, String... matches) { -+ return getListMatchingLast(sender, args, (Collection) Arrays.asList(matches)); ++ public static List getListMatchingLast( ++ final CommandSender sender, ++ final String[] args, ++ final String... matches ++ ) { ++ return getListMatchingLast(sender, args, Arrays.asList(matches)); + } + -+ public static boolean matches(String s, String s1) { ++ public static boolean matches(final String s, final String s1) { + return s1.regionMatches(true, 0, s, 0, s.length()); + } + -+ public static List getListMatchingLast(CommandSender sender, String[] strings, Collection collection) { ++ public static List getListMatchingLast( ++ final CommandSender sender, ++ final String[] strings, ++ final Collection collection ++ ) { + String last = strings[strings.length - 1]; + ArrayList results = Lists.newArrayList(); + @@ -109,7 +57,7 @@ index 0000000000000000000000000000000000000000..b506cd11b76901827cbe66f46db8df40 + while (iterator.hasNext()) { + String s1 = (String) iterator.next(); + -+ if (matches(last, s1) && (sender.hasPermission(BASE_PERM + s1) || sender.hasPermission("bukkit.command.paper"))) { ++ if (matches(last, s1) && (sender.hasPermission(PaperCommand.BASE_PERM + s1) || sender.hasPermission("bukkit.command.paper"))) { + results.add(s1); + } + } @@ -130,188 +78,175 @@ index 0000000000000000000000000000000000000000..b506cd11b76901827cbe66f46db8df40 + return results; + } + // end copy stuff ++} +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b3a58bf4b654e336826dc04da9e2f80ff8b9a9a7 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -0,0 +1,145 @@ ++package io.papermc.paper.command; ++ ++import io.papermc.paper.command.subcommands.EntityCommand; ++import io.papermc.paper.command.subcommands.HeapDumpCommand; ++import io.papermc.paper.command.subcommands.ReloadCommand; ++import io.papermc.paper.command.subcommands.VersionCommand; ++import it.unimi.dsi.fastutil.Pair; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++import java.util.Set; ++import java.util.stream.Collectors; ++import net.minecraft.Util; ++import org.bukkit.Bukkit; ++import org.bukkit.Location; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.bukkit.permissions.Permission; ++import org.bukkit.permissions.PermissionDefault; ++import org.bukkit.plugin.PluginManager; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class PaperCommand extends Command { ++ static final String BASE_PERM = "bukkit.command.paper."; ++ // subcommand label -> subcommand ++ private static final Map SUBCOMMANDS = Util.make(() -> { ++ final Map, PaperSubcommand> commands = new HashMap<>(); ++ ++ commands.put(Set.of("heap"), new HeapDumpCommand()); ++ commands.put(Set.of("entity"), new EntityCommand()); ++ commands.put(Set.of("reload"), new ReloadCommand()); ++ commands.put(Set.of("version"), new VersionCommand()); ++ ++ return commands.entrySet().stream() ++ .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) ++ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); ++ }); ++ // alias -> subcommand label ++ private static final Map ALIASES = Util.make(() -> { ++ final Map> aliases = new HashMap<>(); ++ ++ aliases.put("version", Set.of("ver")); ++ ++ return aliases.entrySet().stream() ++ .flatMap(entry -> entry.getValue().stream().map(s -> Map.entry(s, entry.getKey()))) ++ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); ++ }); ++ ++ public PaperCommand(final String name) { ++ super(name); ++ this.description = "Paper related commands"; ++ this.usageMessage = "/paper [" + String.join(" | ", SUBCOMMANDS.keySet()) + "]"; ++ final List permissions = new ArrayList<>(); ++ permissions.add("bukkit.command.paper"); ++ permissions.addAll(SUBCOMMANDS.keySet().stream().map(s -> BASE_PERM + s).toList()); ++ this.setPermission(String.join(";", permissions)); ++ final PluginManager pluginManager = Bukkit.getServer().getPluginManager(); ++ for (final String perm : permissions) { ++ pluginManager.addPermission(new Permission(perm, PermissionDefault.OP)); ++ } ++ } ++ ++ private static boolean testPermission(final CommandSender sender, final String permission) { ++ if (sender.hasPermission(BASE_PERM + permission) || sender.hasPermission("bukkit.command.paper")) { ++ return true; ++ } ++ sender.sendMessage(text("I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.", RED)); ++ return false; ++ } + + @Override -+ public boolean execute(CommandSender sender, String commandLabel, String[] args) { -+ if (!testPermission(sender)) return true; ++ public List tabComplete( ++ final CommandSender sender, ++ final String alias, ++ final String[] args, ++ final @Nullable Location location ++ ) throws IllegalArgumentException { ++ if (args.length <= 1) { ++ return CommandUtil.getListMatchingLast(sender, args, SUBCOMMANDS.keySet()); ++ } ++ ++ final @Nullable Pair subCommand = resolveCommand(args[0]); ++ if (subCommand != null) { ++ return subCommand.second().tabComplete(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length)); ++ } ++ ++ return Collections.emptyList(); ++ } ++ ++ @Override ++ public boolean execute( ++ final CommandSender sender, ++ final String commandLabel, ++ final String[] args ++ ) { ++ if (!testPermission(sender)) { ++ return true; ++ } + + if (args.length == 0) { + sender.sendMessage(text("Usage: " + this.usageMessage, RED)); + return false; + } -+ if (SUBCOMMANDS.contains(args[0].toLowerCase(Locale.ENGLISH))) { -+ if (!testPermission(sender, args[0].toLowerCase(Locale.ENGLISH))) return true; -+ } -+ switch (args[0].toLowerCase(Locale.ENGLISH)) { -+ case "heap": -+ dumpHeap(sender); -+ break; -+ case "entity": -+ listEntities(sender, args); -+ break; -+ case "reload": -+ doReload(sender); -+ break; -+ case "ver": -+ if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) -+ case "version": -+ Command ver = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); -+ if (ver != null) { -+ ver.execute(sender, commandLabel, new String[0]); -+ break; -+ } -+ // else - fall through to default -+ default: -+ sender.sendMessage(text("Usage: " + this.usageMessage, RED)); -+ return false; ++ final @Nullable Pair subCommand = resolveCommand(args[0]); ++ ++ if (subCommand == null) { ++ sender.sendMessage(text("Usage: " + this.usageMessage, RED)); ++ return false; + } + -+ return true; ++ if (!testPermission(sender, subCommand.first())) { ++ return true; ++ } ++ final String[] choppedArgs = Arrays.copyOfRange(args, 1, args.length); ++ return subCommand.second().execute(sender, subCommand.first(), choppedArgs); + } + -+ /* -+ * Ported from MinecraftForge - author: LexManos - License: LGPLv2.1 -+ */ -+ private void listEntities(CommandSender sender, String[] args) { -+ if (args.length < 2 || args[1].toLowerCase(Locale.ENGLISH).equals("help")) { -+ sender.sendMessage(text("Use /paper entity [list] help for more information on a specific command", RED)); -+ return; ++ private static @Nullable Pair resolveCommand(String label) { ++ label = label.toLowerCase(Locale.ENGLISH); ++ @Nullable PaperSubcommand subCommand = SUBCOMMANDS.get(label); ++ if (subCommand == null) { ++ final @Nullable String command = ALIASES.get(label); ++ if (command != null) { ++ label = command; ++ subCommand = SUBCOMMANDS.get(command); ++ } + } + -+ switch (args[1].toLowerCase(Locale.ENGLISH)) { -+ case "list": -+ String filter = "*"; -+ if (args.length > 2) { -+ if (args[2].toLowerCase(Locale.ENGLISH).equals("help")) { -+ sender.sendMessage(text("Use /paper entity list [filter] [worldName] to get entity info that matches the optional filter.", RED)); -+ return; -+ } -+ filter = args[2]; -+ } -+ final String cleanfilter = filter.replace("?", ".?").replace("*", ".*?"); -+ Set names = Registry.ENTITY_TYPE.keySet().stream() -+ .filter(n -> n.toString().matches(cleanfilter)) -+ .collect(Collectors.toSet()); -+ -+ if (names.isEmpty()) { -+ sender.sendMessage(text("Invalid filter, does not match any entities. Use /paper entity list for a proper list", RED)); -+ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED)); -+ return; -+ } -+ -+ String worldName; -+ if (args.length > 3) { -+ worldName = args[3]; -+ } else if (sender instanceof Player) { -+ worldName = ((Player) sender).getWorld().getName(); -+ } else { -+ sender.sendMessage(text("Please specify the name of a world", RED)); -+ sender.sendMessage(text("To do so without a filter, specify '*' as the filter", RED)); -+ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED)); -+ return; -+ } -+ -+ Map>> list = Maps.newHashMap(); -+ World bukkitWorld = Bukkit.getWorld(worldName); -+ if (bukkitWorld == null) { -+ sender.sendMessage(text("Could not load world for " + worldName + ". Please select a valid world.", RED)); -+ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED)); -+ return; -+ } -+ ServerLevel world = ((CraftWorld) Bukkit.getWorld(worldName)).getHandle(); -+ -+ Map nonEntityTicking = Maps.newHashMap(); -+ ServerChunkCache chunkProviderServer = world.getChunkSource(); -+ -+ world.getAllEntities().forEach(e -> { -+ ResourceLocation key = EntityType.getKey(e.getType()); -+ -+ MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap())); -+ ChunkPos chunk = e.chunkPosition(); -+ info.left++; -+ info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); -+ if (!chunkProviderServer.isPositionTicking(e)) { -+ nonEntityTicking.merge(key, Integer.valueOf(1), Integer::sum); -+ } -+ }); -+ -+ if (names.size() == 1) { -+ ResourceLocation name = names.iterator().next(); -+ Pair> info = list.get(name); -+ int nonTicking = nonEntityTicking.getOrDefault(name, Integer.valueOf(0)).intValue(); -+ if (info == null) { -+ sender.sendMessage(text("No entities found.", RED)); -+ return; -+ } -+ sender.sendMessage("Entity: " + name + " Total Ticking: " + (info.getLeft() - nonTicking) + ", Total Non-Ticking: " + nonTicking); -+ info.getRight().entrySet().stream() -+ .sorted((a, b) -> !a.getValue().equals(b.getValue()) ? b.getValue() - a.getValue() : a.getKey().toString().compareTo(b.getKey().toString())) -+ .limit(10).forEach(e -> sender.sendMessage(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z + (chunkProviderServer.isPositionTicking(e.getKey().toLong()) ? " (Ticking)" : " (Non-Ticking)"))); -+ } else { -+ List> info = list.entrySet().stream() -+ .filter(e -> names.contains(e.getKey())) -+ .map(e -> Pair.of(e.getKey(), e.getValue().left)) -+ .sorted((a, b) -> !a.getRight().equals(b.getRight()) ? b.getRight() - a.getRight() : a.getKey().toString().compareTo(b.getKey().toString())) -+ .collect(Collectors.toList()); -+ -+ if (info == null || info.size() == 0) { -+ sender.sendMessage(text("No entities found.", RED)); -+ return; -+ } -+ -+ int count = info.stream().mapToInt(Pair::getRight).sum(); -+ int nonTickingCount = nonEntityTicking.values().stream().mapToInt(Integer::intValue).sum(); -+ sender.sendMessage("Total Ticking: " + (count - nonTickingCount) + ", Total Non-Ticking: " + nonTickingCount); -+ info.forEach(e -> { -+ int nonTicking = nonEntityTicking.getOrDefault(e.getKey(), Integer.valueOf(0)).intValue(); -+ sender.sendMessage(" " + (e.getValue() - nonTicking) + " (" + nonTicking + ") " + ": " + e.getKey()); -+ }); -+ sender.sendMessage("* First number is ticking entities, second number is non-ticking entities"); -+ } -+ break; ++ if (subCommand != null) { ++ return Pair.of(label, subCommand); + } -+ } + -+ private void dumpHeap(CommandSender sender) { -+ java.nio.file.Path dir = java.nio.file.Paths.get("./dumps"); -+ String name = "heap-dump-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()); -+ -+ Command.broadcastCommandMessage(sender, text("Writing JVM heap data...", YELLOW)); -+ -+ java.nio.file.Path file = CraftServer.dumpHeap(dir, name); -+ if (file != null) { -+ Command.broadcastCommandMessage(sender, text("Heap dump saved to " + file, GREEN)); -+ } else { -+ Command.broadcastCommandMessage(sender, text("Failed to write heap dump, see server log for details", RED)); -+ } -+ } -+ -+ private void doReload(CommandSender sender) { -+ Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", RED)); -+ Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", RED)); -+ -+ MinecraftServer server = ((CraftServer) sender.getServer()).getServer(); -+ server.paperConfigurations.reloadConfigs(server); -+ server.server.reloadCount++; -+ -+ Command.broadcastCommandMessage(sender, text("Paper config reload complete.", GREEN)); ++ return null; + } +} diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java new file mode 100644 -index 0000000000000000000000000000000000000000..82bce89dc174c8c4a4ab8efeffaaf22ddeb377fb +index 0000000000000000000000000000000000000000..6a00f3d38da8107825ab1d405f337fd077b09f72 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/PaperCommands.java -@@ -0,0 +1,25 @@ +@@ -0,0 +1,27 @@ +package io.papermc.paper.command; + -+import com.destroystokyo.paper.PaperCommand; +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; + +import java.util.HashMap; +import java.util.Map; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; + ++@DefaultQualifier(NonNull.class) +public final class PaperCommands { + + private PaperCommands() { @@ -328,6 +263,289 @@ index 0000000000000000000000000000000000000000..82bce89dc174c8c4a4ab8efeffaaf22d + }); + } +} +diff --git a/src/main/java/io/papermc/paper/command/PaperSubcommand.java b/src/main/java/io/papermc/paper/command/PaperSubcommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6ff5d42a866d2752c73a766815aa190b2b0dc36f +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/PaperSubcommand.java +@@ -0,0 +1,16 @@ ++package io.papermc.paper.command; ++ ++import java.util.Collections; ++import java.util.List; ++import org.bukkit.command.CommandSender; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public interface PaperSubcommand { ++ boolean execute(CommandSender sender, String subCommand, String[] args); ++ ++ default List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ return Collections.emptyList(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..68f99e93ed3e843b4001a7a27620f88a48b85e67 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +@@ -0,0 +1,145 @@ ++package io.papermc.paper.command.subcommands; ++ ++import com.google.common.collect.Maps; ++import io.papermc.paper.command.CommandUtil; ++import io.papermc.paper.command.PaperSubcommand; ++import java.util.Collections; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++import java.util.Set; ++import java.util.stream.Collectors; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.level.ServerChunkCache; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.level.ChunkPos; ++import org.apache.commons.lang3.tuple.MutablePair; ++import org.apache.commons.lang3.tuple.Pair; ++import org.bukkit.Bukkit; ++import org.bukkit.World; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class EntityCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.listEntities(sender, args); ++ return true; ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ if (args.length == 1) { ++ return CommandUtil.getListMatchingLast(sender, args, "help", "list"); ++ } else if (args.length == 2) { ++ return CommandUtil.getListMatchingLast(sender, args, Registry.ENTITY_TYPE.keySet().stream().map(ResourceLocation::toString).sorted().toArray(String[]::new)); ++ } ++ return Collections.emptyList(); ++ } ++ ++ /* ++ * Ported from MinecraftForge - author: LexManos - License: LGPLv2.1 ++ */ ++ private void listEntities(final CommandSender sender, final String[] args) { ++ // help ++ if (args.length < 1 || !args[0].toLowerCase(Locale.ENGLISH).equals("list")) { ++ sender.sendMessage(text("Use /paper entity [list] help for more information on a specific command", RED)); ++ return; ++ } ++ ++ if ("list".equals(args[0].toLowerCase(Locale.ENGLISH))) { ++ String filter = "*"; ++ if (args.length > 1) { ++ if (args[1].toLowerCase(Locale.ENGLISH).equals("help")) { ++ sender.sendMessage(text("Use /paper entity list [filter] [worldName] to get entity info that matches the optional filter.", RED)); ++ return; ++ } ++ filter = args[1]; ++ } ++ final String cleanfilter = filter.replace("?", ".?").replace("*", ".*?"); ++ Set names = Registry.ENTITY_TYPE.keySet().stream() ++ .filter(n -> n.toString().matches(cleanfilter)) ++ .collect(Collectors.toSet()); ++ if (names.isEmpty()) { ++ sender.sendMessage(text("Invalid filter, does not match any entities. Use /paper entity list for a proper list", RED)); ++ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED)); ++ return; ++ } ++ String worldName; ++ if (args.length > 2) { ++ worldName = args[2]; ++ } else if (sender instanceof Player) { ++ worldName = ((Player) sender).getWorld().getName(); ++ } else { ++ sender.sendMessage(text("Please specify the name of a world", RED)); ++ sender.sendMessage(text("To do so without a filter, specify '*' as the filter", RED)); ++ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED)); ++ return; ++ } ++ Map>> list = Maps.newHashMap(); ++ @Nullable World bukkitWorld = Bukkit.getWorld(worldName); ++ if (bukkitWorld == null) { ++ sender.sendMessage(text("Could not load world for " + worldName + ". Please select a valid world.", RED)); ++ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED)); ++ return; ++ } ++ ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); ++ Map nonEntityTicking = Maps.newHashMap(); ++ ServerChunkCache chunkProviderServer = world.getChunkSource(); ++ world.getAllEntities().forEach(e -> { ++ ResourceLocation key = EntityType.getKey(e.getType()); ++ ++ MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap())); ++ ChunkPos chunk = e.chunkPosition(); ++ info.left++; ++ info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); ++ if (!chunkProviderServer.isPositionTicking(e)) { ++ nonEntityTicking.merge(key, 1, Integer::sum); ++ } ++ }); ++ if (names.size() == 1) { ++ ResourceLocation name = names.iterator().next(); ++ Pair> info = list.get(name); ++ int nonTicking = nonEntityTicking.getOrDefault(name, 0); ++ if (info == null) { ++ sender.sendMessage(text("No entities found.", RED)); ++ return; ++ } ++ sender.sendMessage("Entity: " + name + " Total Ticking: " + (info.getLeft() - nonTicking) + ", Total Non-Ticking: " + nonTicking); ++ info.getRight().entrySet().stream() ++ .sorted((a, b) -> !a.getValue().equals(b.getValue()) ? b.getValue() - a.getValue() : a.getKey().toString().compareTo(b.getKey().toString())) ++ .limit(10).forEach(e -> sender.sendMessage(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z + (chunkProviderServer.isPositionTicking(e.getKey().toLong()) ? " (Ticking)" : " (Non-Ticking)"))); ++ } else { ++ List> info = list.entrySet().stream() ++ .filter(e -> names.contains(e.getKey())) ++ .map(e -> Pair.of(e.getKey(), e.getValue().left)) ++ .sorted((a, b) -> !a.getRight().equals(b.getRight()) ? b.getRight() - a.getRight() : a.getKey().toString().compareTo(b.getKey().toString())) ++ .toList(); ++ ++ if (info.isEmpty()) { ++ sender.sendMessage(text("No entities found.", RED)); ++ return; ++ } ++ ++ int count = info.stream().mapToInt(Pair::getRight).sum(); ++ int nonTickingCount = nonEntityTicking.values().stream().mapToInt(Integer::intValue).sum(); ++ sender.sendMessage("Total Ticking: " + (count - nonTickingCount) + ", Total Non-Ticking: " + nonTickingCount); ++ info.forEach(e -> { ++ int nonTicking = nonEntityTicking.getOrDefault(e.getKey(), 0); ++ sender.sendMessage(" " + (e.getValue() - nonTicking) + " (" + nonTicking + ") " + ": " + e.getKey()); ++ }); ++ sender.sendMessage("* First number is ticking entities, second number is non-ticking entities"); ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java b/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cd2e4d792e972b8bf1e07b8961594a670ae949cf +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java +@@ -0,0 +1,38 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.command.PaperSubcommand; ++import java.time.LocalDateTime; ++import java.time.format.DateTimeFormatter; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftServer; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; ++ ++@DefaultQualifier(NonNull.class) ++public final class HeapDumpCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.dumpHeap(sender); ++ return true; ++ } ++ ++ private void dumpHeap(final CommandSender sender) { ++ java.nio.file.Path dir = java.nio.file.Paths.get("./dumps"); ++ String name = "heap-dump-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()); ++ ++ Command.broadcastCommandMessage(sender, text("Writing JVM heap data...", YELLOW)); ++ ++ java.nio.file.Path file = CraftServer.dumpHeap(dir, name); ++ if (file != null) { ++ Command.broadcastCommandMessage(sender, text("Heap dump saved to " + file, GREEN)); ++ } else { ++ Command.broadcastCommandMessage(sender, text("Failed to write heap dump, see server log for details", RED)); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bd68139ae635f2ad7ec8e7a21e0056a139c4c62e +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java +@@ -0,0 +1,33 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.command.PaperSubcommand; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftServer; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class ReloadCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.doReload(sender); ++ return true; ++ } ++ ++ private void doReload(final CommandSender sender) { ++ Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", RED)); ++ Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", RED)); ++ ++ MinecraftServer server = ((CraftServer) sender.getServer()).getServer(); ++ server.paperConfigurations.reloadConfigs(server); ++ server.server.reloadCount++; ++ ++ Command.broadcastCommandMessage(sender, text("Paper config reload complete.", GREEN)); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java b/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ae60bd96b5284d54676d8e7e4dd5d170b526ec1e +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java +@@ -0,0 +1,21 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.command.PaperSubcommand; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class VersionCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ final @Nullable Command ver = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); ++ if (ver != null) { ++ ver.execute(sender, "paper", new String[0]); ++ } ++ return true; ++ } ++} diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java index e476f93547f386ded0174693a6218d793ccc450b..393e465b0bac55d407f2ec66d7b11ed0537c9641 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java diff --git a/patches/server/0285-Make-the-default-permission-message-configurable.patch b/patches/server/0285-Make-the-default-permission-message-configurable.patch index ed59bcf3d..53af80fe9 100644 --- a/patches/server/0285-Make-the-default-permission-message-configurable.patch +++ b/patches/server/0285-Make-the-default-permission-message-configurable.patch @@ -4,16 +4,16 @@ Date: Sun, 18 Nov 2018 19:49:56 +0000 Subject: [PATCH] Make the default permission message configurable -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java -index b506cd11b76901827cbe66f46db8df400f7015de..13a5062e539f6f43e6fe582318c36302bd6520ee 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -57,7 +57,7 @@ public class PaperCommand extends Command { - - private static boolean testPermission(CommandSender commandSender, String permission) { - if (commandSender.hasPermission(BASE_PERM + permission) || commandSender.hasPermission("bukkit.command.paper")) return true; -- commandSender.sendMessage(text("I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.", RED)); -+ commandSender.sendMessage(Bukkit.permissionMessage()); +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index b3a58bf4b654e336826dc04da9e2f80ff8b9a9a7..cd4936ef114b504df8649fba8f1823d94a4bb2a2 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -74,7 +74,7 @@ public final class PaperCommand extends Command { + if (sender.hasPermission(BASE_PERM + permission) || sender.hasPermission("bukkit.command.paper")) { + return true; + } +- sender.sendMessage(text("I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.", RED)); ++ sender.sendMessage(Bukkit.permissionMessage()); return false; } diff --git a/patches/server/0322-Chunk-debug-command.patch b/patches/server/0322-Chunk-debug-command.patch index 4a74e40f9..6d993b8a5 100644 --- a/patches/server/0322-Chunk-debug-command.patch +++ b/patches/server/0322-Chunk-debug-command.patch @@ -31,90 +31,102 @@ 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 13a5062e539f6f43e6fe582318c36302bd6520ee..527b37e2740d6ca0d8d7695f069111d156c74b66 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -9,11 +9,13 @@ import com.google.common.collect.Maps; - import net.minecraft.core.Registry; - import net.minecraft.resources.ResourceLocation; - import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ChunkHolder; - import net.minecraft.server.level.ServerChunkCache; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.entity.EntityType; - import net.minecraft.world.level.ChunkPos; -+import net.minecraft.server.MCUtil; - import org.apache.commons.lang3.tuple.MutablePair; - import org.apache.commons.lang3.tuple.Pair; - import org.bukkit.Bukkit; -@@ -40,13 +42,15 @@ import java.util.Set; - import java.util.stream.Collectors; +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index 12c5b6b740eeb986e4b1acc4b2eda6c2c77b63ac..b7b0d134e3a99a630e493aceb06f6188b2c538c3 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -1,5 +1,6 @@ + package io.papermc.paper.command; - import static net.kyori.adventure.text.Component.text; ++import io.papermc.paper.command.subcommands.ChunkDebugCommand; + import io.papermc.paper.command.subcommands.EntityCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; + import io.papermc.paper.command.subcommands.ReloadCommand; +@@ -40,6 +41,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("entity"), new EntityCommand()); + commands.put(Set.of("reload"), new ReloadCommand()); + commands.put(Set.of("version"), new VersionCommand()); ++ commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..029ad37df71e74d9feb57e4b31b3602e55d49113 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java +@@ -0,0 +1,166 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.command.CommandUtil; ++import io.papermc.paper.command.PaperSubcommand; ++import java.io.File; ++import java.time.LocalDateTime; ++import java.time.format.DateTimeFormatter; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++import java.util.Locale; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ServerLevel; ++import org.bukkit.Bukkit; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.BLUE; +import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; - import static net.kyori.adventure.text.format.NamedTextColor.GREEN; - import static net.kyori.adventure.text.format.NamedTextColor.RED; - import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; - - public class PaperCommand extends Command { - private static final String BASE_PERM = "bukkit.command.paper."; -- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version").build(); -+ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo").build(); - - public PaperCommand(String name) { - super(name); -@@ -74,6 +78,21 @@ public class PaperCommand extends Command { - if (args.length == 3) - return getListMatchingLast(sender, args, Registry.ENTITY_TYPE.keySet().stream().map(ResourceLocation::toString).sorted().toArray(String[]::new)); - break; -+ case "debug": -+ if (args.length == 2) { -+ return getListMatchingLast(sender, args, "help", "chunks"); ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class ChunkDebugCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ switch (subCommand) { ++ case "debug" -> this.doDebug(sender, args); ++ case "chunkinfo" -> this.doChunkInfo(sender, args); ++ } ++ return true; ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ switch (subCommand) { ++ case "debug" -> { ++ if (args.length == 1) { ++ return CommandUtil.getListMatchingLast(sender, args, "help", "chunks"); + } -+ break; -+ case "chunkinfo": ++ } ++ case "chunkinfo" -> { + List worldNames = new ArrayList<>(); + worldNames.add("*"); + for (org.bukkit.World world : Bukkit.getWorlds()) { + worldNames.add(world.getName()); + } -+ if (args.length == 2) { -+ return getListMatchingLast(sender, args, worldNames); ++ if (args.length == 1) { ++ return CommandUtil.getListMatchingLast(sender, args, worldNames); + } -+ break; - } - return Collections.emptyList(); - } -@@ -140,6 +159,12 @@ public class PaperCommand extends Command { - case "reload": - doReload(sender); - break; -+ case "debug": -+ doDebug(sender, args); -+ break; -+ case "chunkinfo": -+ doChunkInfo(sender, args); -+ break; - case "ver": - if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) - case "version": -@@ -157,6 +182,122 @@ public class PaperCommand extends Command { - return true; - } - -+ private void doChunkInfo(CommandSender sender, String[] args) { ++ } ++ } ++ return Collections.emptyList(); ++ } ++ ++ private void doChunkInfo(final CommandSender sender, final String[] args) { + List worlds; -+ if (args.length < 2 || args[1].equals("*")) { ++ if (args.length < 1 || args[0].equals("*")) { + worlds = Bukkit.getWorlds(); + } else { -+ worlds = new ArrayList<>(args.length - 1); -+ for (int i = 1; i < args.length; ++i) { -+ org.bukkit.World world = Bukkit.getWorld(args[i]); ++ worlds = new ArrayList<>(args.length); ++ for (final String arg : args) { ++ org.bukkit.@Nullable World world = Bukkit.getWorld(arg); + if (world == null) { -+ sender.sendMessage(text("World '" + args[i] + "' is invalid", RED)); ++ sender.sendMessage(text("World '" + arg + "' is invalid", RED)); + return; + } + worlds.add(world); @@ -127,8 +139,8 @@ index 13a5062e539f6f43e6fe582318c36302bd6520ee..527b37e2740d6ca0d8d7695f069111d1 + int accumulatedTicking = 0; + int accumulatedEntityTicking = 0; + -+ for (org.bukkit.World bukkitWorld : worlds) { -+ ServerLevel world = ((CraftWorld)bukkitWorld).getHandle(); ++ for (final org.bukkit.World bukkitWorld : worlds) { ++ final ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); + + int total = 0; + int inactive = 0; @@ -136,7 +148,7 @@ index 13a5062e539f6f43e6fe582318c36302bd6520ee..527b37e2740d6ca0d8d7695f069111d1 + int ticking = 0; + int entityTicking = 0; + -+ for (ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunkMap.values()) { ++ for (final ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunkMap.values()) { + if (chunk.getFullChunkNowUnchecked() == null) { + continue; + } @@ -146,18 +158,10 @@ index 13a5062e539f6f43e6fe582318c36302bd6520ee..527b37e2740d6ca0d8d7695f069111d1 + ChunkHolder.FullChunkStatus state = ChunkHolder.getFullChunkStatus(chunk.getTicketLevel()); + + switch (state) { -+ case INACCESSIBLE: -+ ++inactive; -+ continue; -+ case BORDER: -+ ++border; -+ continue; -+ case TICKING: -+ ++ticking; -+ continue; -+ case ENTITY_TICKING: -+ ++entityTicking; -+ continue; ++ case INACCESSIBLE -> ++inactive; ++ case BORDER -> ++border; ++ case TICKING -> ++ticking; ++ case ENTITY_TICKING -> ++entityTicking; + } + } + @@ -188,16 +192,16 @@ index 13a5062e539f6f43e6fe582318c36302bd6520ee..527b37e2740d6ca0d8d7695f069111d1 + } + } + -+ private void doDebug(CommandSender sender, String[] args) { -+ if (args.length < 2) { ++ private void doDebug(final CommandSender sender, final String[] args) { ++ if (args.length < 1) { + sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); + return; + } + -+ String debugType = args[1].toLowerCase(Locale.ENGLISH); ++ final String debugType = args[0].toLowerCase(Locale.ENGLISH); + switch (debugType) { -+ case "chunks": -+ if (args.length >= 3 && args[2].toLowerCase(Locale.ENGLISH).equals("help")) { ++ case "chunks" -> { ++ if (args.length >= 2 && args[1].toLowerCase(Locale.ENGLISH).equals("help")) { + sender.sendMessage(text("Use /paper debug chunks [world] to dump loaded chunk information to a file", RED)); + break; + } @@ -211,19 +215,13 @@ index 13a5062e539f6f43e6fe582318c36302bd6520ee..527b37e2740d6ca0d8d7695f069111d1 + MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); + sender.sendMessage(text("Failed to dump chunk information, see console", RED)); + } -+ -+ break; -+ case "help": -+ // fall through to default -+ default: -+ sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); -+ return; ++ } ++ // "help" & default ++ default -> sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); + } + } + - /* - * Ported from MinecraftForge - author: LexManos - License: LGPLv2.1 - */ ++} diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java index 162aa7718488a74980843944e0d026ccfd5a65a5..4e29c0a983727fc839a4bcde01d3286396b3587d 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java diff --git a/patches/server/0352-Fix-Light-Command.patch b/patches/server/0352-Fix-Light-Command.patch index 94702f615..e7baf6951 100644 --- a/patches/server/0352-Fix-Light-Command.patch +++ b/patches/server/0352-Fix-Light-Command.patch @@ -6,76 +6,87 @@ Subject: [PATCH] Fix Light Command This lets you run /paper fixlight (max 5) to automatically fix all light data in the chunks. -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java -index 527b37e2740d6ca0d8d7695f069111d156c74b66..c1c95d6abfc7f1698a74387319ff90ad3a7e39af 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -12,7 +12,8 @@ import net.minecraft.server.MinecraftServer; - import net.minecraft.server.level.ChunkHolder; - import net.minecraft.server.level.ServerChunkCache; - import net.minecraft.server.level.ServerLevel; --import net.minecraft.world.entity.Entity; +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index 395c43f6440c1e0e47919eef096ea8a8d552ccec..f44ab1d71210e84328661c0feb662989a5635b6d 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -2,6 +2,7 @@ package io.papermc.paper.command; + + import io.papermc.paper.command.subcommands.ChunkDebugCommand; + import io.papermc.paper.command.subcommands.EntityCommand; ++import io.papermc.paper.command.subcommands.FixLightCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; + import io.papermc.paper.command.subcommands.ReloadCommand; + import io.papermc.paper.command.subcommands.VersionCommand; +@@ -42,6 +43,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("reload"), new ReloadCommand()); + commands.put(Set.of("version"), new VersionCommand()); + commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand()); ++ commands.put(Set.of("fixlight"), new FixLightCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..190df802cb24aa360f6cf4d291e38b4b3fe4a2ac +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +@@ -0,0 +1,121 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.command.PaperSubcommand; ++import java.util.ArrayDeque; ++import java.util.Deque; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.ThreadedLevelLightEngine; - import net.minecraft.world.entity.EntityType; - import net.minecraft.world.level.ChunkPos; - import net.minecraft.server.MCUtil; -@@ -25,15 +26,18 @@ import org.bukkit.command.Command; - import org.bukkit.command.CommandSender; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.craftbukkit.CraftWorld; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.LevelChunk; ++import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.entity.CraftPlayer; - import org.bukkit.entity.Player; - - import java.io.File; - import java.time.LocalDateTime; - import java.time.format.DateTimeFormatter; -+import java.util.ArrayDeque; - import java.util.ArrayList; - import java.util.Arrays; - import java.util.Collection; - import java.util.Collections; -+import java.util.Deque; - import java.util.Iterator; - import java.util.List; - import java.util.Locale; -@@ -50,7 +54,7 @@ import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; - - public class PaperCommand extends Command { - private static final String BASE_PERM = "bukkit.command.paper."; -- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo").build(); -+ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight").build(); - - public PaperCommand(String name) { - super(name); -@@ -165,6 +169,9 @@ public class PaperCommand extends Command { - case "chunkinfo": - doChunkInfo(sender, args); - break; -+ case "fixlight": -+ this.doFixLight(sender, args); -+ break; - case "ver": - if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) - case "version": -@@ -425,4 +432,74 @@ public class PaperCommand extends Command { - - Command.broadcastCommandMessage(sender, text("Paper config reload complete.", GREEN)); - } -+ private void doFixLight(CommandSender sender, String[] args) { ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class FixLightCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.doFixLight(sender, args); ++ return true; ++ } ++ ++ private void doFixLight(final CommandSender sender, final String[] args) { + if (!(sender instanceof Player)) { -+ sender.sendMessage("Only players can use this command"); ++ sender.sendMessage(text("Only players can use this command", RED)); + return; + } ++ @Nullable Runnable post = null; + int radius = 2; -+ if (args.length > 1) { ++ if (args.length > 0) { + try { -+ radius = Math.min(5, Integer.parseInt(args[1])); -+ } catch (Exception e) { -+ sender.sendMessage("Not a number"); ++ final int parsed = Integer.parseInt(args[0]); ++ if (parsed < 0) { ++ sender.sendMessage(text("Radius cannot be negative!", RED)); ++ return; ++ } ++ final int maxRadius = 5; ++ radius = Math.min(maxRadius, parsed); ++ if (radius != parsed) { ++ post = () -> sender.sendMessage(text("Radius '" + parsed + "' was not in the required range [0, " + maxRadius + "], it was lowered to the maximum (" + maxRadius + " chunks).", RED)); ++ } ++ } catch (final Exception e) { ++ sender.sendMessage(text("'" + args[0] + "' is not a valid number.", RED)); + return; + } -+ + } + + CraftPlayer player = (CraftPlayer) sender; @@ -85,28 +96,37 @@ index 527b37e2740d6ca0d8d7695f069111d156c74b66..c1c95d6abfc7f1698a74387319ff90ad + + net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation()); + Deque queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius)); -+ updateLight(sender, world, lightengine, queue); ++ updateLight(sender, world, lightengine, queue, post); + } + -+ private void updateLight(CommandSender sender, ServerLevel world, ThreadedLevelLightEngine lightengine, Deque queue) { -+ ChunkPos coord = queue.poll(); ++ private void updateLight( ++ final CommandSender sender, ++ final ServerLevel world, ++ final ThreadedLevelLightEngine lightengine, ++ final Deque queue, ++ final @Nullable Runnable done ++ ) { ++ @Nullable ChunkPos coord = queue.poll(); + if (coord == null) { -+ sender.sendMessage("All Chunks Light updated"); ++ sender.sendMessage(text("All Chunks Light updated", GREEN)); ++ if (done != null) { ++ done.run(); ++ } + return; + } + world.getChunkSource().getChunkAtAsynchronously(coord.x, coord.z, false, false).whenCompleteAsync((either, ex) -> { + if (ex != null) { -+ sender.sendMessage("Error loading chunk " + coord); -+ updateLight(sender, world, lightengine, queue); ++ sender.sendMessage(text("Error loading chunk " + coord, RED)); ++ updateLight(sender, world, lightengine, queue, done); + return; + } -+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null); ++ @Nullable LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null); + if (chunk == null) { -+ updateLight(sender, world, lightengine, queue); ++ updateLight(sender, world, lightengine, queue, done); + return; + } + lightengine.setTaskPerBatch(world.paperConfig().misc.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue -+ sender.sendMessage("Updating Light " + coord); ++ sender.sendMessage(text("Updating Light " + coord)); + int cx = chunk.getPos().x << 4; + int cz = chunk.getPos().z << 4; + for (int y = 0; y < world.getHeight(); y++) { @@ -118,21 +138,21 @@ index 527b37e2740d6ca0d8d7695f069111d156c74b66..c1c95d6abfc7f1698a74387319ff90ad + } + } + lightengine.tryScheduleUpdate(); -+ ChunkHolder visibleChunk = world.getChunkSource().chunkMap.getVisibleChunkIfPresent(chunk.coordinateKey); ++ @Nullable ChunkHolder visibleChunk = world.getChunkSource().chunkMap.getVisibleChunkIfPresent(chunk.coordinateKey); + if (visibleChunk != null) { + world.getChunkSource().chunkMap.addLightTask(visibleChunk, () -> { + MinecraftServer.getServer().processQueue.add(() -> { + visibleChunk.broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunk.getPos(), lightengine, null, null, true), false); -+ updateLight(sender, world, lightengine, queue); ++ updateLight(sender, world, lightengine, queue, done); + }); + }); + } else { -+ updateLight(sender, world, lightengine, queue); ++ updateLight(sender, world, lightengine, queue, done); + } + lightengine.setTaskPerBatch(world.paperConfig().misc.lightQueueSize); + }, MinecraftServer.getServer()); + } - } ++} diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java index 375f66bf1617e255b3465b736076a65e080eb36e..d95db45e21861eb9f1623c44dd797429ae158760 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java diff --git a/patches/server/0361-Add-debug-for-sync-chunk-loads.patch b/patches/server/0361-Add-debug-for-sync-chunk-loads.patch index b6c8ef573..d4a016c0f 100644 --- a/patches/server/0361-Add-debug-for-sync-chunk-loads.patch +++ b/patches/server/0361-Add-debug-for-sync-chunk-loads.patch @@ -12,123 +12,6 @@ chunks, however it must be enabled by setting the startup flag - To clear clear the currently stored sync load info, use /paper syncloadinfo clear -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java -index c1c95d6abfc7f1698a74387319ff90ad3a7e39af..fb2cb76e4954d9d69b4a631d6e20bdcc511b6b3f 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -1,5 +1,6 @@ - package com.destroystokyo.paper; - -+import com.destroystokyo.paper.io.SyncLoadFinder; - import com.google.common.base.Functions; - import com.google.common.base.Joiner; - import com.google.common.collect.ImmutableSet; -@@ -8,6 +9,9 @@ import com.google.common.collect.Lists; - import com.google.common.collect.Maps; - import net.minecraft.core.Registry; - import net.minecraft.resources.ResourceLocation; -+import com.google.gson.JsonObject; -+import com.google.gson.internal.Streams; -+import com.google.gson.stream.JsonWriter; - import net.minecraft.server.MinecraftServer; - import net.minecraft.server.level.ChunkHolder; - import net.minecraft.server.level.ServerChunkCache; -@@ -30,6 +34,9 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; - import org.bukkit.entity.Player; - - import java.io.File; -+import java.io.FileOutputStream; -+import java.io.PrintStream; -+import java.io.StringWriter; - import java.time.LocalDateTime; - import java.time.format.DateTimeFormatter; - import java.util.ArrayDeque; -@@ -48,13 +55,14 @@ import java.util.stream.Collectors; - import static net.kyori.adventure.text.Component.text; - import static net.kyori.adventure.text.format.NamedTextColor.BLUE; - import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; -+import static net.kyori.adventure.text.format.NamedTextColor.GRAY; - import static net.kyori.adventure.text.format.NamedTextColor.GREEN; - import static net.kyori.adventure.text.format.NamedTextColor.RED; - import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; - - public class PaperCommand extends Command { - private static final String BASE_PERM = "bukkit.command.paper."; -- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight").build(); -+ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight", "syncloadinfo").build(); - - public PaperCommand(String name) { - super(name); -@@ -97,6 +105,11 @@ public class PaperCommand extends Command { - return getListMatchingLast(sender, args, worldNames); - } - break; -+ case "syncloadinfo": -+ if (args.length == 2) { -+ return getListMatchingLast(sender, args, "clear"); -+ } -+ break; - } - return Collections.emptyList(); - } -@@ -172,6 +185,9 @@ public class PaperCommand extends Command { - case "fixlight": - this.doFixLight(sender, args); - break; -+ case "syncloadinfo": -+ this.doSyncLoadInfo(sender, args); -+ break; - case "ver": - if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) - case "version": -@@ -189,6 +205,47 @@ public class PaperCommand extends Command { - return true; - } - -+ private void doSyncLoadInfo(CommandSender sender, String[] args) { -+ if (!SyncLoadFinder.ENABLED) { -+ sender.sendMessage(text("This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set.", RED)); -+ return; -+ } -+ -+ if (args.length > 1 && args[1].equals("clear")) { -+ SyncLoadFinder.clear(); -+ sender.sendMessage(text("Sync load data cleared.", GRAY)); -+ return; -+ } -+ -+ File file = new File(new File(new File("."), "debug"), -+ "sync-load-info" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); -+ file.getParentFile().mkdirs(); -+ sender.sendMessage(text("Writing sync load info to " + file, GREEN)); -+ -+ -+ try { -+ final JsonObject data = SyncLoadFinder.serialize(); -+ -+ 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); -+ } -+ sender.sendMessage(text("Successfully written sync load information!", GREEN)); -+ } catch (Throwable thr) { -+ sender.sendMessage(text("Failed to write sync load information!", RED)); -+ thr.printStackTrace(); -+ } -+ } -+ - private void doChunkInfo(CommandSender sender, String[] args) { - List worlds; - if (args.length < 2 || args[1].equals("*")) { diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java new file mode 100644 index 0000000000000000000000000000000000000000..0bb4aaa546939b67a5d22865190f30478a9337c1 @@ -310,6 +193,110 @@ index 0000000000000000000000000000000000000000..0bb4aaa546939b67a5d22865190f3047 + } + } +} +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index b5b4025c0ec07fbd0ec9bf6f6812d6422f549289..40cd55e610cbee25a7cff0e6001bad40b4418551 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -5,6 +5,7 @@ import io.papermc.paper.command.subcommands.EntityCommand; + import io.papermc.paper.command.subcommands.FixLightCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; + import io.papermc.paper.command.subcommands.ReloadCommand; ++import io.papermc.paper.command.subcommands.SyncLoadInfoCommand; + import io.papermc.paper.command.subcommands.VersionCommand; + import it.unimi.dsi.fastutil.Pair; + import java.util.ArrayList; +@@ -44,6 +45,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("version"), new VersionCommand()); + commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand()); + commands.put(Set.of("fixlight"), new FixLightCommand()); ++ commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java b/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1120aef5b0dd983c467167f77245884e1198552a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java +@@ -0,0 +1,78 @@ ++package io.papermc.paper.command.subcommands; ++ ++import com.destroystokyo.paper.io.SyncLoadFinder; ++import com.google.gson.JsonObject; ++import com.google.gson.internal.Streams; ++import com.google.gson.stream.JsonWriter; ++import io.papermc.paper.command.CommandUtil; ++import io.papermc.paper.command.PaperSubcommand; ++import java.io.File; ++import java.io.FileOutputStream; ++import java.io.PrintStream; ++import java.io.StringWriter; ++import java.time.LocalDateTime; ++import java.time.format.DateTimeFormatter; ++import java.util.List; ++import org.bukkit.command.CommandSender; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GRAY; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class SyncLoadInfoCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.doSyncLoadInfo(sender, args); ++ return true; ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ return CommandUtil.getListMatchingLast(sender, args, "clear"); ++ } ++ ++ private void doSyncLoadInfo(final CommandSender sender, final String[] args) { ++ if (!SyncLoadFinder.ENABLED) { ++ sender.sendMessage(text("This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set.", RED)); ++ return; ++ } ++ ++ if (args.length > 0 && args[0].equals("clear")) { ++ SyncLoadFinder.clear(); ++ sender.sendMessage(text("Sync load data cleared.", GRAY)); ++ return; ++ } ++ ++ File file = new File(new File(new File("."), "debug"), ++ "sync-load-info" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); ++ file.getParentFile().mkdirs(); ++ sender.sendMessage(text("Writing sync load info to " + file, GREEN)); ++ ++ ++ try { ++ final JsonObject data = SyncLoadFinder.serialize(); ++ ++ 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); ++ } ++ sender.sendMessage(text("Successfully written sync load information!", GREEN)); ++ } catch (Throwable thr) { ++ sender.sendMessage(text("Failed to write sync load information!", RED)); ++ thr.printStackTrace(); ++ } ++ } ++} diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java index c12c03b9e79f264ee593373f8a72ed37c0ae8514..509b2ee115584ce80717cc12a7ab548d103b4b92 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java diff --git a/patches/server/0373-Add-tick-times-API-and-mspt-command.patch b/patches/server/0373-Add-tick-times-API-and-mspt-command.patch index 3a7d03f4c..264335449 100644 --- a/patches/server/0373-Add-tick-times-API-and-mspt-command.patch +++ b/patches/server/0373-Add-tick-times-API-and-mspt-command.patch @@ -4,13 +4,13 @@ Date: Sun, 5 Apr 2020 22:23:14 -0500 Subject: [PATCH] Add tick times API and /mspt command -diff --git a/src/main/java/com/destroystokyo/paper/MSPTCommand.java b/src/main/java/com/destroystokyo/paper/MSPTCommand.java +diff --git a/src/main/java/io/papermc/paper/command/MSPTCommand.java b/src/main/java/io/papermc/paper/command/MSPTCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..874f0c2a071994c2145848886caa385e0e0bfb9b +index 0000000000000000000000000000000000000000..8b5293b0c696ef21d0101493ffa41b60bf0bc86b --- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/MSPTCommand.java -@@ -0,0 +1,99 @@ -+package com.destroystokyo.paper; ++++ b/src/main/java/io/papermc/paper/command/MSPTCommand.java +@@ -0,0 +1,102 @@ ++package io.papermc.paper.command; + +import net.kyori.adventure.text.Component; +import net.minecraft.server.MinecraftServer; @@ -23,6 +23,8 @@ index 0000000000000000000000000000000000000000..874f0c2a071994c2145848886caa385e +import java.util.Arrays; +import java.util.Collections; +import java.util.List; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.GOLD; @@ -31,11 +33,12 @@ index 0000000000000000000000000000000000000000..874f0c2a071994c2145848886caa385e +import static net.kyori.adventure.text.format.NamedTextColor.RED; +import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; + -+public class MSPTCommand extends Command { ++@DefaultQualifier(NonNull.class) ++public final class MSPTCommand extends Command { + private static final DecimalFormat DF = new DecimalFormat("########0.0"); + private static final Component SLASH = text("/"); + -+ public MSPTCommand(String name) { ++ public MSPTCommand(final String name) { + super(name); + this.description = "View server tick times"; + this.usageMessage = "/mspt"; @@ -110,17 +113,10 @@ index 0000000000000000000000000000000000000000..874f0c2a071994c2145848886caa385e + } +} diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java -index 82bce89dc174c8c4a4ab8efeffaaf22ddeb377fb..37d28625b9528bbe0cd6d9623e702bbbc2b07883 100644 +index 6a00f3d38da8107825ab1d405f337fd077b09f72..d31b5ed47cffc61c90c926a0cd2005b72ebddfc5 100644 --- a/src/main/java/io/papermc/paper/command/PaperCommands.java +++ b/src/main/java/io/papermc/paper/command/PaperCommands.java -@@ -1,5 +1,6 @@ - package io.papermc.paper.command; - -+import com.destroystokyo.paper.MSPTCommand; - import com.destroystokyo.paper.PaperCommand; - import net.minecraft.server.MinecraftServer; - import org.bukkit.command.Command; -@@ -15,6 +16,7 @@ public final class PaperCommands { +@@ -17,6 +17,7 @@ public final class PaperCommands { private static final Map COMMANDS = new HashMap<>(); static { COMMANDS.put("paper", new PaperCommand("paper")); diff --git a/patches/server/0443-Paper-dumpitem-command.patch b/patches/server/0443-Paper-dumpitem-command.patch index a889ec06a..cb1c8dc58 100644 --- a/patches/server/0443-Paper-dumpitem-command.patch +++ b/patches/server/0443-Paper-dumpitem-command.patch @@ -5,60 +5,88 @@ Subject: [PATCH] Paper dumpitem command Let's you quickly view the item in your hands NBT data -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java -index fb2cb76e4954d9d69b4a631d6e20bdcc511b6b3f..27c350c123b4349b8b401a06f204de46c88da7ac 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -31,7 +31,9 @@ import org.bukkit.command.CommandSender; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.craftbukkit.CraftWorld; - import org.bukkit.craftbukkit.entity.CraftPlayer; +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index 40cd55e610cbee25a7cff0e6001bad40b4418551..4c85d0a61ce508e4a0e3770f46242fb2b1d17420 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -1,6 +1,7 @@ + package io.papermc.paper.command; + + import io.papermc.paper.command.subcommands.ChunkDebugCommand; ++import io.papermc.paper.command.subcommands.DumpItemCommand; + import io.papermc.paper.command.subcommands.EntityCommand; + import io.papermc.paper.command.subcommands.FixLightCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; +@@ -46,6 +47,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand()); + commands.put(Set.of("fixlight"), new FixLightCommand()); + commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); ++ commands.put(Set.of("dumpitem"), new DumpItemCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java b/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..771503ff637fea10d4d8be0f37f3f146c41791d9 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java +@@ -0,0 +1,59 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.command.PaperSubcommand; ++import java.util.Objects; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.event.ClickEvent; ++import net.minecraft.core.Registry; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.world.item.ItemStack; ++import org.bukkit.Bukkit; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; - import org.bukkit.entity.Player; -+import org.bukkit.inventory.ItemStack; - - import java.io.File; - import java.io.FileOutputStream; -@@ -62,7 +64,7 @@ import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; - - public class PaperCommand extends Command { - private static final String BASE_PERM = "bukkit.command.paper."; -- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight", "syncloadinfo").build(); -+ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight", "syncloadinfo", "dumpitem").build(); - - public PaperCommand(String name) { - super(name); -@@ -176,6 +178,9 @@ public class PaperCommand extends Command { - case "reload": - doReload(sender); - break; -+ case "dumpitem": -+ doDumpItem(sender); -+ break; - case "debug": - doDebug(sender, args); - break; -@@ -489,6 +494,23 @@ public class PaperCommand extends Command { - - Command.broadcastCommandMessage(sender, text("Paper config reload complete.", GREEN)); - } -+ private void doDumpItem(CommandSender sender) { ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GRAY; ++import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; ++import static net.kyori.adventure.text.format.TextDecoration.ITALIC; ++ ++@DefaultQualifier(NonNull.class) ++public final class DumpItemCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.doDumpItem(sender); ++ return true; ++ } ++ ++ private void doDumpItem(final CommandSender sender) { + if (!(sender instanceof Player)) { + sender.sendMessage("Only players can use this command"); + return; + } -+ ItemStack itemInHand = ((CraftPlayer) sender).getItemInHand(); -+ net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(itemInHand); -+ net.minecraft.nbt.CompoundTag tag = itemStack.getTag(); -+ if (tag != null) { -+ net.kyori.adventure.text.Component nbtComponent = io.papermc.paper.adventure.PaperAdventure.asAdventure(net.minecraft.nbt.NbtUtils.toPrettyComponent(tag)); -+ Bukkit.getConsoleSender().sendMessage(nbtComponent); -+ sender.sendMessage(nbtComponent); -+ } else { -+ sender.sendMessage("Item does not have NBT"); -+ } ++ final ItemStack itemStack = CraftItemStack.asNMSCopy(((CraftPlayer) sender).getItemInHand()); ++ final @Nullable CompoundTag tag = itemStack.getTag(); ++ final @Nullable Component nbtComponent = tag == null ? null : PaperAdventure.asAdventure(net.minecraft.nbt.NbtUtils.toPrettyComponent(tag)); ++ final String itemId = Objects.requireNonNull(((CraftWorld) ((CraftPlayer) sender).getWorld()).getHandle().registryAccess() ++ .registryOrThrow(Registry.ITEM_REGISTRY).getKey(itemStack.getItem())).toString(); ++ final Component message = text() ++ .append(text(itemId, YELLOW)) ++ .apply(b -> { ++ if (nbtComponent != null) { ++ b.append(nbtComponent); ++ } ++ }) ++ .build(); ++ Bukkit.getConsoleSender().sendMessage(message); ++ sender.sendMessage(message); ++ sender.sendMessage(text().content(" Click to copy item to clipboard") ++ .color(GRAY) ++ .decorate(ITALIC) ++ .clickEvent(ClickEvent.copyToClipboard(tag == null ? itemId : (itemId + tag)))); + } -+ - private void doFixLight(CommandSender sender, String[] args) { - if (!(sender instanceof Player)) { - sender.sendMessage("Only players can use this command"); ++} diff --git a/patches/server/0729-Add-paper-mobcaps-and-paper-playermobcaps.patch b/patches/server/0729-Add-paper-mobcaps-and-paper-playermobcaps.patch index d40a2598a..6cd599f45 100644 --- a/patches/server/0729-Add-paper-mobcaps-and-paper-playermobcaps.patch +++ b/patches/server/0729-Add-paper-mobcaps-and-paper-playermobcaps.patch @@ -9,88 +9,66 @@ each player when per-player mob spawning is enabled. Also has a hover text on each mob category listing what entity types are in said category -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java -index 27c350c123b4349b8b401a06f204de46c88da7ac..008bb8896657892fcaf64e134684cc49e62f23f6 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -3,6 +3,7 @@ package com.destroystokyo.paper; - import com.destroystokyo.paper.io.SyncLoadFinder; - import com.google.common.base.Functions; - import com.google.common.base.Joiner; +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index d2536f4ffae721f4df714b5345fa3329c3b8e3f5..60b0ce4557390ee7030efe4c90933402c57bab59 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -5,6 +5,7 @@ import io.papermc.paper.command.subcommands.DumpItemCommand; + import io.papermc.paper.command.subcommands.EntityCommand; + import io.papermc.paper.command.subcommands.FixLightCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; ++import io.papermc.paper.command.subcommands.MobcapsCommand; + import io.papermc.paper.command.subcommands.ReloadCommand; + import io.papermc.paper.command.subcommands.SyncLoadInfoCommand; + import io.papermc.paper.command.subcommands.VersionCommand; +@@ -48,6 +49,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("fixlight"), new FixLightCommand()); + commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); + commands.put(Set.of("dumpitem"), new DumpItemCommand()); ++ commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2e02d94e2903c48f6d08e743c1cf8bad9f9662df +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java +@@ -0,0 +1,229 @@ ++package io.papermc.paper.command.subcommands; ++ +import com.google.common.collect.ImmutableMap; - import com.google.common.collect.ImmutableSet; - import com.google.common.collect.Iterables; - import com.google.common.collect.Lists; -@@ -12,6 +13,12 @@ import net.minecraft.resources.ResourceLocation; - import com.google.gson.JsonObject; - import com.google.gson.internal.Streams; - import com.google.gson.stream.JsonWriter; ++import io.papermc.paper.command.CommandUtil; ++import io.papermc.paper.command.PaperSubcommand; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++import java.util.Map; ++import java.util.function.ToIntFunction; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.JoinConfiguration; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; - import net.minecraft.server.MinecraftServer; - import net.minecraft.server.level.ChunkHolder; - import net.minecraft.server.level.ServerChunkCache; -@@ -19,8 +26,10 @@ import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; - import net.minecraft.server.level.ThreadedLevelLightEngine; - import net.minecraft.world.entity.EntityType; ++import net.minecraft.core.Registry; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.MobCategory; - import net.minecraft.world.level.ChunkPos; - import net.minecraft.server.MCUtil; +import net.minecraft.world.level.NaturalSpawner; - import org.apache.commons.lang3.tuple.MutablePair; - import org.apache.commons.lang3.tuple.Pair; - import org.bukkit.Bukkit; -@@ -52,6 +61,7 @@ import java.util.List; - import java.util.Locale; - import java.util.Map; - import java.util.Set; -+import java.util.function.ToIntFunction; - import java.util.stream.Collectors; - - import static net.kyori.adventure.text.Component.text; -@@ -64,7 +74,7 @@ import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; - - public class PaperCommand extends Command { - private static final String BASE_PERM = "bukkit.command.paper."; -- private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight", "syncloadinfo", "dumpitem").build(); -+ private static final ImmutableSet SUBCOMMANDS = ImmutableSet.builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight", "syncloadinfo", "dumpitem", "mobcaps", "playermobcaps").build(); - - public PaperCommand(String name) { - super(name); -@@ -97,6 +107,10 @@ public class PaperCommand extends Command { - return getListMatchingLast(sender, args, "help", "chunks"); - } - break; -+ case "mobcaps": -+ return getListMatchingLast(sender, args, this.suggestMobcaps(sender, args)); -+ case "playermobcaps": -+ return getListMatchingLast(sender, args, this.suggestPlayerMobcaps(sender, args)); - case "chunkinfo": - List worldNames = new ArrayList<>(); - worldNames.add("*"); -@@ -193,6 +207,12 @@ public class PaperCommand extends Command { - case "syncloadinfo": - this.doSyncLoadInfo(sender, args); - break; -+ case "mobcaps": -+ this.printMobcaps(sender, args); -+ break; -+ case "playermobcaps": -+ this.printPlayerMobcaps(sender, args); -+ break; - case "ver": - if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set) - case "version": -@@ -251,6 +271,184 @@ public class PaperCommand extends Command { - } - } - -+ public static final Map MOB_CATEGORY_COLORS = ImmutableMap.builder() ++import org.bukkit.Bukkit; ++import org.bukkit.World; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class MobcapsCommand implements PaperSubcommand { ++ static final Map MOB_CATEGORY_COLORS = ImmutableMap.builder() + .put(MobCategory.MONSTER, NamedTextColor.RED) + .put(MobCategory.CREATURE, NamedTextColor.GREEN) + .put(MobCategory.AMBIENT, NamedTextColor.GRAY) @@ -101,8 +79,26 @@ index 27c350c123b4349b8b401a06f204de46c88da7ac..008bb8896657892fcaf64e134684cc49 + .put(MobCategory.MISC, TextColor.color(0x636363)) + .build(); + -+ private List suggestMobcaps(CommandSender sender, String[] args) { -+ if (args.length == 2) { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ switch (subCommand) { ++ case "mobcaps" -> this.printMobcaps(sender, args); ++ case "playermobcaps" -> this.printPlayerMobcaps(sender, args); ++ } ++ return true; ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ return switch (subCommand) { ++ case "mobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestMobcaps(args)); ++ case "playermobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestPlayerMobcaps(sender, args)); ++ default -> throw new IllegalArgumentException(); ++ }; ++ } ++ ++ private List suggestMobcaps(final String[] args) { ++ if (args.length == 1) { + final List worlds = new ArrayList<>(Bukkit.getWorlds().stream().map(World::getName).toList()); + worlds.add("*"); + return worlds; @@ -111,8 +107,8 @@ index 27c350c123b4349b8b401a06f204de46c88da7ac..008bb8896657892fcaf64e134684cc49 + return Collections.emptyList(); + } + -+ private List suggestPlayerMobcaps(CommandSender sender, String[] args) { -+ if (args.length == 2) { ++ private List suggestPlayerMobcaps(final CommandSender sender, final String[] args) { ++ if (args.length == 1) { + final List list = new ArrayList<>(); + for (final Player player : Bukkit.getOnlinePlayers()) { + if (!(sender instanceof Player senderPlayer) || senderPlayer.canSee(player)) { @@ -125,21 +121,21 @@ index 27c350c123b4349b8b401a06f204de46c88da7ac..008bb8896657892fcaf64e134684cc49 + return Collections.emptyList(); + } + -+ private void printMobcaps(CommandSender sender, String[] args) { ++ private void printMobcaps(final CommandSender sender, final String[] args) { + final List worlds; -+ if (args.length == 1) { ++ if (args.length == 0) { + if (sender instanceof Player player) { + worlds = List.of(player.getWorld()); + } else { + sender.sendMessage(Component.text("Must specify a world! ex: '/paper mobcaps world'", NamedTextColor.RED)); + return; + } -+ } else if (args.length == 2) { -+ final String input = args[1]; ++ } else if (args.length == 1) { ++ final String input = args[0]; + if (input.equals("*")) { + worlds = Bukkit.getWorlds(); + } else { -+ final World world = Bukkit.getWorld(input); ++ final @Nullable World world = Bukkit.getWorld(input); + if (world == null) { + sender.sendMessage(Component.text("'" + input + "' is not a valid world!", NamedTextColor.RED)); + return; @@ -154,7 +150,7 @@ index 27c350c123b4349b8b401a06f204de46c88da7ac..008bb8896657892fcaf64e134684cc49 + + for (final World world : worlds) { + final ServerLevel level = ((CraftWorld) world).getHandle(); -+ final NaturalSpawner.SpawnState state = level.getChunkSource().getLastSpawnState(); ++ final NaturalSpawner.@Nullable SpawnState state = level.getChunkSource().getLastSpawnState(); + + final int chunks; + if (state == null) { @@ -168,7 +164,7 @@ index 27c350c123b4349b8b401a06f204de46c88da7ac..008bb8896657892fcaf64e134684cc49 + Component.text(" (" + chunks + " spawnable chunks)") + )); + -+ sender.sendMessage(this.buildMobcapsComponent( ++ sender.sendMessage(createMobcapsComponent( + category -> { + if (state == null) { + return 0; @@ -181,17 +177,17 @@ index 27c350c123b4349b8b401a06f204de46c88da7ac..008bb8896657892fcaf64e134684cc49 + } + } + -+ private void printPlayerMobcaps(CommandSender sender, String[] args) { -+ final Player player; -+ if (args.length == 1) { ++ private void printPlayerMobcaps(final CommandSender sender, final String[] args) { ++ final @Nullable Player player; ++ if (args.length == 0) { + if (sender instanceof Player pl) { + player = pl; + } else { + sender.sendMessage(Component.text("Must specify a player! ex: '/paper playermobcount playerName'", NamedTextColor.RED)); + return; + } -+ } else if (args.length == 2) { -+ final String input = args[1]; ++ } else if (args.length == 1) { ++ final String input = args[0]; + player = Bukkit.getPlayerExact(input); + if (player == null) { + sender.sendMessage(Component.text("Could not find player named '" + input + "'", NamedTextColor.RED)); @@ -211,13 +207,13 @@ index 27c350c123b4349b8b401a06f204de46c88da7ac..008bb8896657892fcaf64e134684cc49 + } + + sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), Component.text("Mobcaps for player: "), Component.text(player.getName(), NamedTextColor.GREEN))); -+ sender.sendMessage(this.buildMobcapsComponent( ++ sender.sendMessage(createMobcapsComponent( + category -> level.chunkSource.chunkMap.getMobCountNear(serverPlayer, category), + category -> level.getWorld().getSpawnLimitUnsafe(org.bukkit.craftbukkit.util.CraftSpawnCategory.toBukkit(category)) + )); + } + -+ private Component buildMobcapsComponent(final ToIntFunction countGetter, final ToIntFunction limitGetter) { ++ private static Component createMobcapsComponent(final ToIntFunction countGetter, final ToIntFunction limitGetter) { + return MOB_CATEGORY_COLORS.entrySet().stream() + .map(entry -> { + final MobCategory category = entry.getKey(); @@ -267,10 +263,7 @@ index 27c350c123b4349b8b401a06f204de46c88da7ac..008bb8896657892fcaf64e134684cc49 + .map(ComponentLike::asComponent) + .collect(Component.toComponent(Component.newline())); + } -+ - private void doChunkInfo(CommandSender sender, String[] args) { - List worlds; - if (args.length < 2 || args[1].equals("*")) { ++} diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java index fa23e9c476d4edc6176d8b8a6cb13c52d2f66a87..4150e8cd7197eac53042d56f0a53a4951f8824ce 100644 --- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java @@ -293,7 +286,7 @@ index fa23e9c476d4edc6176d8b8a6cb13c52d2f66a87..4150e8cd7197eac53042d56f0a53a495 // Paper start - add parameters and int ret type spawnCategoryForChunk(group, world, chunk, checker, runner, Integer.MAX_VALUE, null); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 8417136c411c91e4c163446d7e1c2ced5f0084e1..874f317ebae64ba9f42573e3a412dedbeeb90cc7 100644 +index b84d0c8225a9d5f64dda7ba91a3836e1e21a525c..268a70912722a9ae25b7f88a90daf83f5444781a 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -2154,6 +2154,11 @@ public final class CraftServer implements Server { @@ -328,30 +321,29 @@ index 9d3f7f196c4331662c4c78cac0b047bcd2ff5e77..21bf39ef5b20ecc989881b07c4e6b90c } return limit; } -diff --git a/src/test/java/io/papermc/paper/PaperCommandTest.java b/src/test/java/io/papermc/paper/PaperCommandTest.java +diff --git a/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java new file mode 100644 -index 0000000000000000000000000000000000000000..4b5b368ef17bdb90f50e6ccc1f814cf93c7c0590 +index 0000000000000000000000000000000000000000..f1dd3bca7fa0df8b6ed177bb435877229af1c0c5 --- /dev/null -+++ b/src/test/java/io/papermc/paper/PaperCommandTest.java -@@ -0,0 +1,21 @@ -+package io.papermc.paper; ++++ b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java +@@ -0,0 +1,20 @@ ++package io.papermc.paper.command.subcommands; + -+import com.destroystokyo.paper.PaperCommand; +import java.util.HashSet; +import java.util.Set; +import net.minecraft.world.entity.MobCategory; +import org.junit.Assert; +import org.junit.Test; + -+public class PaperCommandTest { ++public class MobcapsCommandTest { + @Test + public void testMobCategoryColors() { + final Set missing = new HashSet<>(); + for (final MobCategory value : MobCategory.values()) { -+ if (!PaperCommand.MOB_CATEGORY_COLORS.containsKey(value)) { ++ if (!MobcapsCommand.MOB_CATEGORY_COLORS.containsKey(value)) { + missing.add(value.getName()); + } + } -+ Assert.assertTrue("PaperCommand.MOB_CATEGORY_COLORS map missing TextColors for [" + String.join(", ", missing + "]"), missing.isEmpty()); ++ Assert.assertTrue("MobcapsCommand.MOB_CATEGORY_COLORS map missing TextColors for [" + String.join(", ", missing + "]"), missing.isEmpty()); + } +} diff --git a/patches/server/0738-Do-not-copy-visible-chunks.patch b/patches/server/0738-Do-not-copy-visible-chunks.patch index b9782d00c..70eb848f4 100644 --- a/patches/server/0738-Do-not-copy-visible-chunks.patch +++ b/patches/server/0738-Do-not-copy-visible-chunks.patch @@ -8,16 +8,16 @@ tickDistanceManager call can take up quite a bit in the function. I saw approximately 1/3rd of the function on the copy. -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java -index 008bb8896657892fcaf64e134684cc49e62f23f6..a538473f4a75791c7c657f9f1e3ddf96042ab071 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -480,7 +480,7 @@ public class PaperCommand extends Command { +diff --git a/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java +index 029ad37df71e74d9feb57e4b31b3602e55d49113..4b367982fae4662c326aa247824e9c4185896de1 100644 +--- a/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java ++++ b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java +@@ -90,7 +90,7 @@ public final class ChunkDebugCommand implements PaperSubcommand { int ticking = 0; int entityTicking = 0; -- for (ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunkMap.values()) { -+ for (ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunks.getUpdatingMap().values()) { // Paper - change updating chunks map +- for (final ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunkMap.values()) { ++ for (final ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunks.getUpdatingMap().values()) { // Paper - change updating chunks map if (chunk.getFullChunkNowUnchecked() == null) { continue; } diff --git a/patches/server/0791-Rewrite-the-light-engine.patch b/patches/server/0791-Rewrite-the-light-engine.patch index ab6f47251..d93a071f1 100644 --- a/patches/server/0791-Rewrite-the-light-engine.patch +++ b/patches/server/0791-Rewrite-the-light-engine.patch @@ -4357,24 +4357,68 @@ index 0000000000000000000000000000000000000000..dd995e25ae620ae36cd5eecb2fe10ad0 + } + +} -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java -index a538473f4a75791c7c657f9f1e3ddf96042ab071..86c41f9ad92dc5fb762d56107f58e3acc07d8890 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -709,6 +709,46 @@ public class PaperCommand extends Command { - } +diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +index 190df802cb24aa360f6cf4d291e38b4b3fe4a2ac..68645bbbab9b4225048b647252d8f462028a9c84 100644 +--- a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java ++++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +@@ -10,6 +10,7 @@ import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.server.level.ThreadedLevelLightEngine; + import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.ChunkAccess; + import net.minecraft.world.level.chunk.LevelChunk; + import org.bukkit.command.CommandSender; + import org.bukkit.craftbukkit.entity.CraftPlayer; +@@ -19,6 +20,8 @@ import org.checkerframework.checker.nullness.qual.Nullable; + import org.checkerframework.framework.qual.DefaultQualifier; + + import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.BLUE; ++import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; + import static net.kyori.adventure.text.format.NamedTextColor.GREEN; + import static net.kyori.adventure.text.format.NamedTextColor.RED; + +@@ -44,7 +47,7 @@ public final class FixLightCommand implements PaperSubcommand { + sender.sendMessage(text("Radius cannot be negative!", RED)); + return; + } +- final int maxRadius = 5; ++ final int maxRadius = 32; // Paper - MOOOOOORE + radius = Math.min(maxRadius, parsed); + if (radius != parsed) { + post = () -> sender.sendMessage(text("Radius '" + parsed + "' was not in the required range [0, " + maxRadius + "], it was lowered to the maximum (" + maxRadius + " chunks).", RED)); +@@ -59,12 +62,67 @@ public final class FixLightCommand implements PaperSubcommand { + ServerPlayer handle = player.getHandle(); + ServerLevel world = (ServerLevel) handle.level; + ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine(); ++ // Paper start - rewrite light engine ++ if (true) { ++ this.starlightFixLight(handle, world, lightengine, radius, post); ++ return; ++ } ++ // Paper end - rewrite light engine + + net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation()); + Deque queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius)); + updateLight(sender, world, lightengine, queue, post); } + // Paper start - rewrite light engine -+ private void starlightFixLight(ServerPlayer sender, ServerLevel world, ThreadedLevelLightEngine lightengine, int radius) { ++ private void starlightFixLight( ++ final ServerPlayer sender, ++ final ServerLevel world, ++ final ThreadedLevelLightEngine lightengine, ++ final int radius, ++ final @Nullable Runnable done ++ ) { + long start = System.nanoTime(); + java.util.LinkedHashSet chunks = new java.util.LinkedHashSet<>(MCUtil.getSpiralOutChunks(sender.blockPosition(), radius)); // getChunkCoordinates is actually just bad mappings, this function rets position as blockpos + + int[] pending = new int[1]; -+ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext();) { ++ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext(); ) { + final ChunkPos chunkPos = iterator.next(); + -+ final net.minecraft.world.level.chunk.ChunkAccess chunk = (net.minecraft.world.level.chunk.ChunkAccess) world.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); ++ final @Nullable ChunkAccess chunk = (ChunkAccess) world.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); + if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.LIGHT)) { + // cannot relight this chunk + iterator.remove(); @@ -4386,51 +4430,31 @@ index a538473f4a75791c7c657f9f1e3ddf96042ab071..86c41f9ad92dc5fb762d56107f58e3ac + + int[] relitChunks = new int[1]; + lightengine.relight(chunks, -+ (ChunkPos chunkPos) -> { -+ ++relitChunks[0]; -+ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( -+ text("Relit chunk ", BLUE), text(chunkPos.toString()), -+ text(", progress: ", BLUE), text((int)(Math.round(100.0 * (double)(relitChunks[0])/(double)pending[0])) + "%") -+ )); -+ }, -+ (int totalRelit) -> { -+ final long end = System.nanoTime(); -+ final long diff = Math.round(1.0e-6*(end - start)); -+ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( -+ text("Relit ", BLUE), text(totalRelit), -+ text(" chunks. Took ", BLUE), text(diff + "ms") -+ )); -+ }); ++ (ChunkPos chunkPos) -> { ++ ++relitChunks[0]; ++ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( ++ text("Relit chunk ", BLUE), text(chunkPos.toString()), ++ text(", progress: ", BLUE), text((int) (Math.round(100.0 * (double) (relitChunks[0]) / (double) pending[0])) + "%") ++ )); ++ }, ++ (int totalRelit) -> { ++ final long end = System.nanoTime(); ++ final long diff = Math.round(1.0e-6 * (end - start)); ++ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( ++ text("Relit ", BLUE), text(totalRelit), ++ text(" chunks. Took ", BLUE), text(diff + "ms") ++ )); ++ if (done != null) { ++ done.run(); ++ } ++ }); + sender.getBukkitEntity().sendMessage(text().color(BLUE).append(text("Relighting "), text(pending[0], DARK_AQUA), text(" chunks"))); + } + // Paper end - rewrite light engine + - private void doFixLight(CommandSender sender, String[] args) { - if (!(sender instanceof Player)) { - sender.sendMessage("Only players can use this command"); -@@ -717,7 +757,7 @@ public class PaperCommand extends Command { - int radius = 2; - if (args.length > 1) { - try { -- radius = Math.min(5, Integer.parseInt(args[1])); -+ radius = Math.min(32, Integer.parseInt(args[1])); // Paper - MOOOOOORE - } catch (Exception e) { - sender.sendMessage("Not a number"); - return; -@@ -730,6 +770,13 @@ public class PaperCommand extends Command { - ServerLevel world = (ServerLevel) handle.level; - ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine(); - -+ // Paper start - rewrite light engine -+ if (true) { -+ this.starlightFixLight(handle, world, lightengine, radius); -+ return; -+ } -+ // Paper end - rewrite light engine -+ - net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation()); - Deque queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius)); - updateLight(sender, world, lightengine, queue); + private void updateLight( + final CommandSender sender, + final ServerLevel world, diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java index 5b4c3ca92dffff876af18db106310cb14e8612b1..5482be03a667939ff009b6810d5cc90c8601e983 100644 --- a/src/main/java/net/minecraft/server/level/ChunkHolder.java diff --git a/patches/server/0895-Don-t-tick-markers.patch b/patches/server/0895-Don-t-tick-markers.patch index e6d463d84..7c5f605b7 100644 --- a/patches/server/0895-Don-t-tick-markers.patch +++ b/patches/server/0895-Don-t-tick-markers.patch @@ -8,19 +8,19 @@ tick list at all and ignoring them in Spigot's activation range checks. The enti list is only used in the tick and tickPassenger methods, so we can safely not add the markers to it. -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java -index a96b3f62a7a6aa5c87976dcda93f4b47bc2cd252..8ec20f17a3f8c39ae3ebf3fb630f98b35283ba88 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -626,7 +626,7 @@ public class PaperCommand extends Command { - ChunkPos chunk = e.chunkPosition(); - info.left++; - info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); -- if (!chunkProviderServer.isPositionTicking(e)) { -+ if (!chunkProviderServer.isPositionTicking(e) || e instanceof net.minecraft.world.entity.Marker) { // Markers aren't ticked. - nonEntityTicking.merge(key, Integer.valueOf(1), Integer::sum); - } - }); +diff --git a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +index 68f99e93ed3e843b4001a7a27620f88a48b85e67..0dc96c39151ec4dbeec3947cb17606f53a6392d4 100644 +--- a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java ++++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +@@ -103,7 +103,7 @@ public final class EntityCommand implements PaperSubcommand { + ChunkPos chunk = e.chunkPosition(); + info.left++; + info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); +- if (!chunkProviderServer.isPositionTicking(e)) { ++ if (!chunkProviderServer.isPositionTicking(e) || e instanceof net.minecraft.world.entity.Marker) { // Markers aren't ticked. + nonEntityTicking.merge(key, 1, Integer::sum); + } + }); diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index c89e85c1fe37c8fba54825be76bb1caa15d84dcd..458ebb5dabd1f24ea3d49ef57eea687ce61f0fc0 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java