From 340efea0b8468b7e7e27f279e5b2dc609759344e Mon Sep 17 00:00:00 2001 From: Strokkur424 Date: Sat, 28 Jun 2025 16:01:19 +0200 Subject: [PATCH 1/9] Convert source file-hosted subcommands to Brigadier --- .../io/papermc/paper/command/CommandUtil.java | 69 ----- .../papermc/paper/command/PaperCommand.java | 166 ++++-------- .../papermc/paper/command/PaperCommands.java | 1 - .../paper/command/PaperSubcommand.java | 20 -- .../command/subcommands/DumpItemCommand.java | 49 ++-- .../subcommands/DumpListenersCommand.java | 92 +++---- .../subcommands/DumpPluginsCommand.java | 44 ++-- .../command/subcommands/EntityCommand.java | 242 ++++++++++-------- .../command/subcommands/HeapDumpCommand.java | 26 +- .../command/subcommands/MobcapsCommand.java | 170 ++++++------ .../command/subcommands/ReloadCommand.java | 26 +- .../subcommands/SyncLoadInfoCommand.java | 49 ++-- .../command/subcommands/VersionCommand.java | 34 +-- .../org/bukkit/craftbukkit/CraftServer.java | 7 +- 14 files changed, 438 insertions(+), 557 deletions(-) delete mode 100644 paper-server/src/main/java/io/papermc/paper/command/CommandUtil.java delete mode 100644 paper-server/src/main/java/io/papermc/paper/command/PaperSubcommand.java diff --git a/paper-server/src/main/java/io/papermc/paper/command/CommandUtil.java b/paper-server/src/main/java/io/papermc/paper/command/CommandUtil.java deleted file mode 100644 index 953c30500892..000000000000 --- a/paper-server/src/main/java/io/papermc/paper/command/CommandUtil.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.papermc.paper.command; - -import com.google.common.base.Functions; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import net.minecraft.resources.ResourceLocation; -import org.bukkit.command.CommandSender; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.DefaultQualifier; - -@DefaultQualifier(NonNull.class) -public final class CommandUtil { - private CommandUtil() { - } - - // Code from Mojang - copyright them - public static List getListMatchingLast( - final CommandSender sender, - final String[] args, - final String... matches - ) { - return getListMatchingLast(sender, args, Arrays.asList(matches)); - } - - public static boolean matches(final String s, final String s1) { - return s1.regionMatches(true, 0, s, 0, s.length()); - } - - public static List getListMatchingLast( - final CommandSender sender, - final String[] strings, - final Collection collection - ) { - String last = strings[strings.length - 1]; - ArrayList results = Lists.newArrayList(); - - if (!collection.isEmpty()) { - Iterator iterator = Iterables.transform(collection, Functions.toStringFunction()).iterator(); - - while (iterator.hasNext()) { - String s1 = (String) iterator.next(); - - if (matches(last, s1) && (sender.hasPermission(PaperCommand.BASE_PERM + s1) || sender.hasPermission("bukkit.command.paper"))) { - results.add(s1); - } - } - - if (results.isEmpty()) { - iterator = collection.iterator(); - - while (iterator.hasNext()) { - Object object = iterator.next(); - - if (object instanceof ResourceLocation && matches(last, ((ResourceLocation) object).getPath())) { - results.add(String.valueOf(object)); - } - } - } - } - - return results; - } - // end copy stuff -} diff --git a/paper-server/src/main/java/io/papermc/paper/command/PaperCommand.java b/paper-server/src/main/java/io/papermc/paper/command/PaperCommand.java index 33b10d4eb634..6e091cde6464 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/PaperCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/PaperCommand.java @@ -1,6 +1,10 @@ package io.papermc.paper.command; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.tree.LiteralCommandNode; import io.papermc.paper.FeatureHooks; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.command.subcommands.DumpItemCommand; import io.papermc.paper.command.subcommands.DumpListenersCommand; import io.papermc.paper.command.subcommands.DumpPluginsCommand; @@ -10,149 +14,73 @@ 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; -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 org.jspecify.annotations.NullMarked; -import static net.kyori.adventure.text.Component.text; -import static net.kyori.adventure.text.format.NamedTextColor.RED; +@NullMarked +public final class PaperCommand { -@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()); - commands.put(Set.of("dumpplugins"), new DumpPluginsCommand()); - commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); - commands.put(Set.of("dumpitem"), new DumpItemCommand()); - commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); - commands.put(Set.of("dumplisteners"), new DumpListenersCommand()); - FeatureHooks.registerPaperCommands(commands); - - 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)); - }); - private static final Set COMPLETABLE_SUBCOMMANDS = SUBCOMMANDS.entrySet().stream().filter(entry -> entry.getValue().tabCompletes()).map(Map.Entry::getKey).collect(Collectors.toSet()); - // alias -> subcommand label - private static final Map ALIASES = Util.make(() -> { - final Map> aliases = new HashMap<>(); + public static final String BASE_PERM = "bukkit.command.paper."; + static final String DESCRIPTION = "Paper related commands"; - aliases.put("version", Set.of("ver")); + public static LiteralCommandNode create() { + // TODO: FIX ISSUES WITH COMMANDS + // TODO: TEST COMMANDS - 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(Bukkit.permissionMessage()); - return false; - } + LiteralArgumentBuilder rootNode = Commands.literal("paper") + .requires(stack -> stack.getSender().hasPermission(String.join(";", permissions))) + .executes(ctx -> { + ctx.getSource().getSender().sendPlainMessage("/paper [" + String.join(" | ", SUBCOMMANDS.keySet()) + "]"); + return com.mojang.brigadier.Command.SINGLE_SUCCESS; + }); - @Override - 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, COMPLETABLE_SUBCOMMANDS); - } - - 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(); + SUBCOMMANDS.values().forEach(subs -> subs.forEach(rootNode::then)); + return rootNode.build(); } - @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; - } - final @Nullable Pair subCommand = resolveCommand(args[0]); - - if (subCommand == null) { - sender.sendMessage(text("Usage: " + this.usageMessage, RED)); - return false; - } + public static void registerPermissions() { + final List permissions = new ArrayList<>(); + permissions.add("bukkit.command.paper"); + permissions.addAll(SUBCOMMANDS.keySet().stream().map(s -> BASE_PERM + s).toList()); - if (!testPermission(sender, subCommand.first())) { - return true; + final PluginManager pluginManager = Bukkit.getServer().getPluginManager(); + for (final String perm : permissions) { + pluginManager.addPermission(new Permission(perm, PermissionDefault.OP)); } - final String[] choppedArgs = Arrays.copyOfRange(args, 1, args.length); - return subCommand.second().execute(sender, subCommand.first(), choppedArgs); } - private static @Nullable Pair resolveCommand(String label) { - label = label.toLowerCase(Locale.ROOT); - @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); - } - } - - if (subCommand != null) { - return Pair.of(label, subCommand); - } + // subcommand label -> subcommand + private static final Map>> SUBCOMMANDS = Util.make(() -> { + final Map>> commands = new HashMap<>(14); + + commands.put("heap", List.of(HeapDumpCommand.create())); + commands.put("entity", List.of(EntityCommand.create())); + commands.put("reload", List.of(ReloadCommand.create())); + commands.put("version", List.of( + VersionCommand.create(BASE_PERM + "version", "version"), + VersionCommand.create(BASE_PERM + "version", "ver")) + ); + commands.put("dumpplugins", List.of(DumpPluginsCommand.create())); + commands.put("syncloadinfo", List.of(SyncLoadInfoCommand.create())); + commands.put("dumpitem", List.of(DumpItemCommand.create())); + commands.put("mobcaps", List.of(MobcapsCommand.createGlobal())); + commands.put("playermobcaps", List.of(MobcapsCommand.createPlayer())); + commands.put("dumplisteners", List.of(DumpListenersCommand.create())); + FeatureHooks.registerPaperCommands(commands); - return null; - } + return commands; + }); } diff --git a/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java b/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java index eeefb915d48c..7d94b90ffa61 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java +++ b/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java @@ -22,7 +22,6 @@ private PaperCommands() { private static final Map COMMANDS = new HashMap<>(); public static void registerCommands(final MinecraftServer server) { - COMMANDS.put("paper", new PaperCommand("paper")); COMMANDS.put("mspt", new MSPTCommand("mspt")); COMMANDS.forEach((s, command) -> { diff --git a/paper-server/src/main/java/io/papermc/paper/command/PaperSubcommand.java b/paper-server/src/main/java/io/papermc/paper/command/PaperSubcommand.java deleted file mode 100644 index 7e9e0ff8639b..000000000000 --- a/paper-server/src/main/java/io/papermc/paper/command/PaperSubcommand.java +++ /dev/null @@ -1,20 +0,0 @@ -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(); - } - - default boolean tabCompletes() { - return true; - } -} diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java index b4b90c1bda72..4fc7443a5cbf 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java @@ -1,8 +1,11 @@ package io.papermc.paper.command.subcommands; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import io.papermc.paper.adventure.PaperAdventure; -import io.papermc.paper.command.CommandUtil; -import io.papermc.paper.command.PaperSubcommand; +import io.papermc.paper.command.PaperCommand; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; @@ -32,9 +35,7 @@ import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.inventory.CraftItemStack; 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 org.jspecify.annotations.NullMarked; import static net.kyori.adventure.text.Component.join; import static net.kyori.adventure.text.Component.text; @@ -48,16 +49,28 @@ import static net.kyori.adventure.text.format.TextColor.color; 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, args.length > 0 && "all".equals(args[0])); - return true; +@NullMarked +public final class DumpItemCommand { + + public static LiteralArgumentBuilder create() { + return Commands.literal("dumpitem") + .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "dumpitem")) + + .then(Commands.literal("all") + .executes(ctx -> { + doDumpItem(ctx.getSource().getSender(), true); + return Command.SINGLE_SUCCESS; + }) + ) + + .executes(ctx -> { + doDumpItem(ctx.getSource().getSender(), false); + return Command.SINGLE_SUCCESS; + }); } @SuppressWarnings({"unchecked", "OptionalAssignedToNull", "rawtypes"}) - private void doDumpItem(final CommandSender sender, final boolean includeAllComponents) { + private static void doDumpItem(final CommandSender sender, final boolean includeAllComponents) { if (!(sender instanceof final Player player)) { sender.sendMessage("Only players can use this command"); return; @@ -83,8 +96,8 @@ private void doDumpItem(final CommandSender sender, final boolean includeAllComp final List commandComponents = new ArrayList<>(); for (final DataComponentType type : referencedComponentTypes) { final String path = registry.getResourceKey(type).orElseThrow().location().getPath(); - final @Nullable Optional patchedValue = patch.get(type); - final @Nullable TypedDataComponent prototypeValue = prototype.getTyped(type); + final Optional patchedValue = patch.get(type); + final TypedDataComponent prototypeValue = prototype.getTyped(type); if (patchedValue != null) { if (patchedValue.isEmpty()) { componentComponents.add(text().append(text('!', RED), text(path, AQUA))); @@ -122,12 +135,4 @@ private static void writeComponentValue(final Consumer visualOutput, )); commandOutput.accept(path + "=" + new SnbtPrinterTagVisitor("", 0, new ArrayList<>()).visit(serialized)); } - - @Override - public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { - if (args.length == 1) { - return CommandUtil.getListMatchingLast(sender, args, "all"); - } - return Collections.emptyList(); - } } diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java index 9c79d8757f14..2c963242bb53 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java @@ -1,7 +1,14 @@ + package io.papermc.paper.command.subcommands; import com.destroystokyo.paper.util.SneakyThrow; -import io.papermc.paper.command.PaperSubcommand; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import io.papermc.paper.command.PaperCommand; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; import java.io.IOException; import java.io.PrintWriter; import java.lang.invoke.MethodHandle; @@ -11,11 +18,9 @@ import java.nio.file.Path; 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 java.util.Set; +import java.util.concurrent.CompletableFuture; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.minecraft.server.MinecraftServer; @@ -24,9 +29,7 @@ import org.bukkit.event.HandlerList; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.RegisteredListener; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.qual.DefaultQualifier; +import org.jspecify.annotations.NullMarked; import static net.kyori.adventure.text.Component.newline; import static net.kyori.adventure.text.Component.space; @@ -36,12 +39,21 @@ import static net.kyori.adventure.text.format.NamedTextColor.RED; import static net.kyori.adventure.text.format.NamedTextColor.WHITE; -@DefaultQualifier(NonNull.class) -public final class DumpListenersCommand implements PaperSubcommand { +@NullMarked +public final class DumpListenersCommand { + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"); private static final String COMMAND_ARGUMENT_TO_FILE = "tofile"; private static final MethodHandle EVENT_TYPES_HANDLE; + private static final Component HELP_MESSAGE = text("Usage: /paper dumplisteners " + COMMAND_ARGUMENT_TO_FILE + "|", RED); + private static final SuggestionProvider EVENT_SUGGESTIONS = (ctx, builder) -> CompletableFuture.supplyAsync(() -> { + eventClassNames().stream() + .filter(name -> name.toLowerCase().startsWith(builder.getRemainingLowerCase())) + .forEach(builder::suggest); + return builder.build(); + }); + static { try { final Field eventTypesField = HandlerList.class.getDeclaredField("EVENT_TYPES"); @@ -52,17 +64,32 @@ public final class DumpListenersCommand implements PaperSubcommand { } } - @Override - public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { - if (args.length >= 1 && args[0].equals(COMMAND_ARGUMENT_TO_FILE)) { - this.dumpToFile(sender); - return true; - } - this.doDumpListeners(sender, args); - return true; + public static LiteralArgumentBuilder create() { + return Commands.literal("dumplisteners") + .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "dumplisteners")) + + .then(Commands.literal("tofile") + .executes(ctx -> { + dumpToFile(ctx.getSource().getSender()); + return Command.SINGLE_SUCCESS; + }) + ) + + .then(Commands.argument("event", StringArgumentType.word()) + .suggests(EVENT_SUGGESTIONS) + .executes(ctx -> { + doDumpListeners(ctx.getSource().getSender(), StringArgumentType.getString(ctx, "event")); + return Command.SINGLE_SUCCESS; + }) + ) + + .executes(ctx -> { + ctx.getSource().getSender().sendMessage(HELP_MESSAGE); + return Command.SINGLE_SUCCESS; + }); } - private void dumpToFile(final CommandSender sender) { + private static void dumpToFile(final CommandSender sender) { Path parent = Path.of("debug"); Path path = parent.resolve("listeners-" + FORMATTER.format(LocalDateTime.now()) + ".txt"); sender.sendMessage( @@ -77,7 +104,7 @@ private void dumpToFile(final CommandSender sender) { try { Files.createDirectories(parent); Files.createFile(path); - try (final PrintWriter writer = new PrintWriter(path.toFile())){ + try (final PrintWriter writer = new PrintWriter(path.toFile())) { for (final String eventClass : eventClassNames()) { final HandlerList handlers; try { @@ -109,14 +136,7 @@ private void dumpToFile(final CommandSender sender) { ); } - private void doDumpListeners(final CommandSender sender, final String[] args) { - if (args.length == 0) { - sender.sendMessage(text("Usage: /paper dumplisteners " + COMMAND_ARGUMENT_TO_FILE + "|", RED)); - return; - } - - final String className = args[0]; - + private static void doDumpListeners(final CommandSender sender, final String className) { try { final HandlerList handlers = (HandlerList) findClass(className).getMethod("getHandlerList").invoke(null); @@ -153,24 +173,6 @@ private void doDumpListeners(final CommandSender sender, final String[] args) { } } - @Override - public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { - return switch (args.length) { - case 0 -> suggestions(); - case 1 -> suggestions().stream() - .filter(clazz -> clazz.toLowerCase(Locale.ROOT).contains(args[0].toLowerCase(Locale.ROOT))) - .toList(); - default -> Collections.emptyList(); - }; - } - - private static List suggestions() { - final List ret = new ArrayList<>(); - ret.add(COMMAND_ARGUMENT_TO_FILE); - ret.addAll(eventClassNames()); - return ret; - } - @SuppressWarnings("unchecked") private static Set eventClassNames() { try { diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java index 0f8983d07eb1..6071d1e88afb 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java @@ -8,7 +8,11 @@ import com.google.gson.Strictness; import com.google.gson.internal.Streams; import com.google.gson.stream.JsonWriter; -import io.papermc.paper.command.PaperSubcommand; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import io.papermc.paper.command.PaperCommand; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.plugin.entrypoint.Entrypoint; import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler; import io.papermc.paper.plugin.entrypoint.classloader.group.LockingClassLoaderGroup; @@ -17,24 +21,16 @@ import io.papermc.paper.plugin.entrypoint.classloader.group.SpigotPluginClassLoaderGroup; import io.papermc.paper.plugin.entrypoint.classloader.group.StaticPluginClassLoaderGroup; import io.papermc.paper.plugin.entrypoint.dependency.SimpleMetaDependencyTree; -import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; -import io.papermc.paper.plugin.entrypoint.strategy.modern.ModernPluginLoadingStrategy; import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration; +import io.papermc.paper.plugin.entrypoint.strategy.modern.ModernPluginLoadingStrategy; import io.papermc.paper.plugin.manager.PaperPluginManagerImpl; import io.papermc.paper.plugin.provider.PluginProvider; import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; import io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage; import io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup; +import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; import io.papermc.paper.plugin.storage.ConfiguredProviderStorage; import io.papermc.paper.plugin.storage.ProviderStorage; -import net.kyori.adventure.text.event.ClickEvent; -import net.minecraft.server.MinecraftServer; -import org.bukkit.command.CommandSender; -import org.bukkit.plugin.Plugin; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.DefaultQualifier; - -import java.io.IOException; import java.io.PrintStream; import java.io.StringWriter; import java.nio.charset.StandardCharsets; @@ -45,23 +41,33 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import net.kyori.adventure.text.event.ClickEvent; +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; +import org.jspecify.annotations.NullMarked; 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.WHITE; -@DefaultQualifier(NonNull.class) -public final class DumpPluginsCommand implements PaperSubcommand { - @Override - public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { - this.dumpPlugins(sender, args); - return true; - } +@NullMarked +public final class DumpPluginsCommand { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"); - private void dumpPlugins(final CommandSender sender, final String[] args) { + public static LiteralArgumentBuilder create() { + DumpPluginsCommand instance = new DumpPluginsCommand(); + return Commands.literal("dumpplugins") + .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "dumpplugins")) + .executes(ctx -> { + instance.dumpPlugins(ctx.getSource().getSender()); + return Command.SINGLE_SUCCESS; + }); + } + + private void dumpPlugins(final CommandSender sender) { Path parent = Path.of("debug"); Path path = parent.resolve("plugin-info-" + FORMATTER.format(LocalDateTime.now()) + ".json"); try { diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java index e3ddc201b328..30af42447003 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java @@ -1,12 +1,15 @@ package io.papermc.paper.command.subcommands; +import com.google.common.base.Functions; import com.google.common.collect.Maps; -import io.papermc.paper.FeatureHooks; -import io.papermc.paper.command.CommandUtil; -import io.papermc.paper.command.PaperSubcommand; -import java.util.Collections; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import io.papermc.paper.command.PaperCommand; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -21,138 +24,151 @@ 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.HeightMap; 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 org.jspecify.annotations.NullMarked; 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 EntityCommand implements PaperSubcommand { - @Override - public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { - this.listEntities(sender, args); - return true; - } +@NullMarked +public final class EntityCommand { - @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, BuiltInRegistries.ENTITY_TYPE.keySet()); - } - return Collections.emptyList(); + private static final Component HELP_MESSAGE = text("Use /paper entity [list] help for more information on a specific command", RED); + private static final Component LIST_HELP_MESSAGE = text("Use /paper entity list [filter] [worldName] to get entity info that matches the optional filter.", RED); + + public static LiteralArgumentBuilder create() { + return Commands.literal("entity") + .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "entity")) + + .then(Commands.literal("list") + .then(Commands.literal("help") + .executes(ctx -> { + ctx.getSource().getSender().sendMessage(LIST_HELP_MESSAGE); + return Command.SINGLE_SUCCESS; + }) + ) + + .then(Commands.argument("filter", StringArgumentType.string()) + .suggests((ctx, builder) -> { + BuiltInRegistries.ENTITY_TYPE.keySet().stream() + .map(Functions.toStringFunction()) + .filter(type -> type.startsWith(builder.getRemainingLowerCase())) + .forEach(builder::suggest); + return builder.buildFuture(); + }) + + .executes(ctx -> { + listEntities( + ctx.getSource().getSender(), + StringArgumentType.getString(ctx, "filter"), + ctx.getSource().getLocation().getWorld() + ); + return Command.SINGLE_SUCCESS; + }) + + .then(Commands.argument("world", ArgumentTypes.world()) + .executes(ctx -> { + listEntities( + ctx.getSource().getSender(), + StringArgumentType.getString(ctx, "filter"), + ctx.getArgument("world", World.class) + ); + return Command.SINGLE_SUCCESS; + }) + ) + ) + + .executes(ctx -> { + listEntities(ctx.getSource().getSender(), "*", ctx.getSource().getLocation().getWorld()); + return Command.SINGLE_SUCCESS; + }) + ) + + .then(Commands.literal("help") + .executes(ctx -> { + ctx.getSource().getSender().sendMessage(HELP_MESSAGE); + return Command.SINGLE_SUCCESS; + }) + ) + + .executes(ctx -> { + ctx.getSource().getSender().sendMessage(HELP_MESSAGE); + return Command.SINGLE_SUCCESS; + }); } /* * 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.ROOT).equals("list")) { - sender.sendMessage(text("Use /paper entity [list] help for more information on a specific command", RED)); + private static void listEntities(final CommandSender sender, final String filter, final World bukkitWorld) { + final String cleanfilter = filter.replace("?", ".?").replace("*", ".*?"); + Set names = BuiltInRegistries.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; } - if ("list".equals(args[0].toLowerCase(Locale.ROOT))) { - String filter = "*"; - if (args.length > 1) { - if (args[1].toLowerCase(Locale.ROOT).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 = BuiltInRegistries.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; + Map>> list = Maps.newHashMap(); + 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 (!world.isPositionEntityTicking(e.blockPosition()) || (e instanceof net.minecraft.world.entity.Marker && !world.paperConfig().entities.markers.tick)) { // Paper - Configurable marker ticking + nonEntityTicking.merge(key, 1, Integer::sum); } - 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)); + }); + 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; } - 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)); + 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 -> { + final int x = (e.getKey().x << 4) + 8; + final int z = (e.getKey().z << 4) + 8; + final Component message = text(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z + (chunkProviderServer.isPositionTicking(e.getKey().toLong()) ? " (Ticking)" : " (Non-Ticking)")) + .hoverEvent(HoverEvent.showText(text("Click to teleport to chunk", GREEN))) + .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/minecraft:execute as @s in " + world.getWorld().getKey() + " run tp " + x + " " + (world.getWorld().getHighestBlockYAt(x, z, HeightMap.MOTION_BLOCKING) + 1) + " " + z)); + sender.sendMessage(message); + }); + } 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; } - ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); - Map nonEntityTicking = Maps.newHashMap(); - ServerChunkCache chunkProviderServer = world.getChunkSource(); - FeatureHooks.getAllEntities(world).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 (!world.isPositionEntityTicking(e.blockPosition()) || (e instanceof net.minecraft.world.entity.Marker && !world.paperConfig().entities.markers.tick)) { // Paper - Configurable marker ticking - nonEntityTicking.merge(key, 1, Integer::sum); - } + + 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()); }); - 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 -> { - final int x = (e.getKey().x << 4) + 8; - final int z = (e.getKey().z << 4) + 8; - final Component message = text(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z + (chunkProviderServer.isPositionTicking(e.getKey().toLong()) ? " (Ticking)" : " (Non-Ticking)")) - .hoverEvent(HoverEvent.showText(text("Click to teleport to chunk", GREEN))) - .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/minecraft:execute as @s in " + world.getWorld().getKey() + " run tp " + x + " " + (world.getWorld().getHighestBlockYAt(x, z, HeightMap.MOTION_BLOCKING) + 1) + " " + z)); - sender.sendMessage(message); - }); - } 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"); - } + sender.sendMessage("* First number is ticking entities, second number is non-ticking entities"); } } } diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java index cd2e4d792e97..27db29353a50 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java @@ -1,28 +1,34 @@ package io.papermc.paper.command.subcommands; -import io.papermc.paper.command.PaperSubcommand; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import io.papermc.paper.command.PaperCommand; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; 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 org.jspecify.annotations.NullMarked; 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; +@NullMarked +public final class HeapDumpCommand { + + public static LiteralArgumentBuilder create() { + return Commands.literal("heap") + .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + ".heap")) + .executes(ctx -> { + dumpHeap(ctx.getSource().getSender()); + return com.mojang.brigadier.Command.SINGLE_SUCCESS; + }); } - private void dumpHeap(final CommandSender sender) { + private static 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()); diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java index 950e709a7b4b..e59af3fa9ac2 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java @@ -1,10 +1,17 @@ package io.papermc.paper.command.subcommands; import com.google.common.collect.ImmutableMap; -import io.papermc.paper.command.CommandUtil; -import io.papermc.paper.command.PaperSubcommand; -import java.util.ArrayList; -import java.util.Collections; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import io.papermc.paper.command.PaperCommand; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.MessageComponentSerializer; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver; import java.util.List; import java.util.Map; import java.util.function.ToIntFunction; @@ -25,12 +32,11 @@ 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; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class MobcapsCommand { -@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) @@ -42,78 +48,78 @@ public final class MobcapsCommand implements PaperSubcommand { .put(MobCategory.MISC, TextColor.color(0x636363)) .build(); - @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 static final SimpleCommandExceptionType REQUIRES_PLAYER = new SimpleCommandExceptionType(MessageComponentSerializer.message().serialize( + Component.text("Must specify a player! ex: '/paper playermobcount playerName'") + )); + private static final DynamicCommandExceptionType NO_SUCH_WORLD_ERROR = new DynamicCommandExceptionType(obj -> MessageComponentSerializer.message().serialize( + Component.text("'" + obj + "' is not a valid world!") + )); + + public static LiteralArgumentBuilder createGlobal() { + return Commands.literal("mobcaps") + .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "mobcaps")) + + .then(Commands.literal("*") + .executes(ctx -> { + printMobcaps(ctx.getSource().getSender(), Bukkit.getWorlds()); + return Command.SINGLE_SUCCESS; + }) + ) + + .then(Commands.argument("world", ArgumentTypes.world()) + .executes(ctx -> { + printMobcaps(ctx.getSource().getSender(), List.of(ctx.getArgument("world", World.class))); + return Command.SINGLE_SUCCESS; + }) + ) + + .then(Commands.argument("world_name", StringArgumentType.string()) + .executes(ctx -> { + String worldName = StringArgumentType.getString(ctx, "world_name"); + World world = Bukkit.getWorld(worldName); + + if (world == null) { + throw NO_SUCH_WORLD_ERROR.create(worldName); + } - 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; - } + printMobcaps(ctx.getSource().getSender(), List.of(world)); + return Command.SINGLE_SUCCESS; + }) + ) - return Collections.emptyList(); + .executes(ctx -> { + printMobcaps(ctx.getSource().getSender(), List.of(ctx.getSource().getLocation().getWorld())); + return Command.SINGLE_SUCCESS; + }); } - 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)) { - list.add(player.getName()); + public static LiteralArgumentBuilder createPlayer() { + return Commands.literal("playermobcaps") + .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "mobcaps")) + + .then(Commands.argument("players", ArgumentTypes.player()) + .executes(ctx -> { + ctx.getArgument("players", PlayerSelectorArgumentResolver.class).resolve(ctx.getSource()).forEach( + player -> printPlayerMobcaps(ctx.getSource().getSender(), player) + ); + return Command.SINGLE_SUCCESS; + }) + ) + + .executes(ctx -> { + if (ctx.getSource().getExecutor() instanceof Player player) { + printPlayerMobcaps(ctx.getSource().getSender(), player); + return Command.SINGLE_SUCCESS; } - } - return list; - } - return Collections.emptyList(); + throw REQUIRES_PLAYER.create(); + }); } - private void printMobcaps(final CommandSender sender, final String[] args) { - final List worlds; - 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 == 1) { - final String input = args[0]; - if (input.equals("*")) { - worlds = Bukkit.getWorlds(); - } else { - final @Nullable World world = Bukkit.getWorld(input); - if (world == null) { - sender.sendMessage(Component.text("'" + input + "' is not a valid world!", NamedTextColor.RED)); - return; - } else { - worlds = List.of(world); - } - } - } else { - sender.sendMessage(Component.text("Too many arguments!", NamedTextColor.RED)); - return; - } - + private static void printMobcaps(final CommandSender sender, final List worlds) { for (final World world : worlds) { final ServerLevel level = ((CraftWorld) world).getHandle(); - final NaturalSpawner.@Nullable SpawnState state = level.getChunkSource().getLastSpawnState(); + final NaturalSpawner.SpawnState state = level.getChunkSource().getLastSpawnState(); final int chunks; if (state == null) { @@ -140,27 +146,7 @@ private void printMobcaps(final CommandSender sender, final String[] args) { } } - 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 == 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)); - return; - } - } else { - sender.sendMessage(Component.text("Too many arguments!", NamedTextColor.RED)); - return; - } - + private static void printPlayerMobcaps(final CommandSender sender, final Player player) { final ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); final ServerLevel level = serverPlayer.level(); diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java index bd68139ae635..7bc4fcf81a47 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java @@ -1,26 +1,32 @@ package io.papermc.paper.command.subcommands; -import io.papermc.paper.command.PaperSubcommand; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import io.papermc.paper.command.PaperCommand; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; 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 org.jspecify.annotations.NullMarked; 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; +@NullMarked +public final class ReloadCommand { + + public static LiteralArgumentBuilder create() { + return Commands.literal("reload") + .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "reload")) + .executes(ctx -> { + doReload(ctx.getSource().getSender()); + return com.mojang.brigadier.Command.SINGLE_SUCCESS; + }); } - private void doReload(final CommandSender sender) { + private static 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)); diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java index 95d6022c9cfb..3a4402ef5986 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java @@ -4,8 +4,11 @@ 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 com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import io.papermc.paper.command.PaperCommand; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; import java.io.File; import java.io.FileOutputStream; import java.io.PrintStream; @@ -13,14 +16,11 @@ import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.List; - import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; import net.minecraft.server.MinecraftServer; import org.bukkit.command.CommandSender; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.DefaultQualifier; +import org.jspecify.annotations.NullMarked; import static net.kyori.adventure.text.Component.text; import static net.kyori.adventure.text.format.NamedTextColor.GRAY; @@ -28,32 +28,39 @@ import static net.kyori.adventure.text.format.NamedTextColor.RED; import static net.kyori.adventure.text.format.NamedTextColor.WHITE; -@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"); - } +@NullMarked +public final class SyncLoadInfoCommand { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"); - private void doSyncLoadInfo(final CommandSender sender, final String[] args) { + public static LiteralArgumentBuilder create() { + return Commands.literal("syncloadinfo") + .requires(stack -> SyncLoadFinder.ENABLED && stack.getSender().hasPermission(PaperCommand.BASE_PERM + "syncloadinfo")) + .then(Commands.literal("clear") + .executes(ctx -> { + doSyncLoadInfo(ctx.getSource().getSender(), true); + return Command.SINGLE_SUCCESS; + }) + ) + .executes(ctx -> { + doSyncLoadInfo(ctx.getSource().getSender(), false); + return Command.SINGLE_SUCCESS; + }); + } + + private static void doSyncLoadInfo(final CommandSender sender, final boolean clear) { + // This if-statement is technically not needed anymore, but in case somebody decided that it would + // be a wonderful idea to call this method using reflection, we keep it in. if (!SyncLoadFinder.ENABLED) { String systemFlag = "-Dpaper.debug-sync-loads=true"; sender.sendMessage(text().color(RED).append(text("This command requires the server startup flag '")).append( text(systemFlag, WHITE).clickEvent(ClickEvent.copyToClipboard(systemFlag)) - .hoverEvent(HoverEvent.showText(text("Click to copy the system flag")))).append( + .hoverEvent(HoverEvent.showText(text("Click to copy the system flag")))).append( text("' to be set."))); return; } - if (args.length > 0 && args[0].equals("clear")) { + if (clear) { SyncLoadFinder.clear(); sender.sendMessage(text("Sync load data cleared.", GRAY)); return; diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java index 58ca24715eaf..23818616587d 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java @@ -1,21 +1,25 @@ package io.papermc.paper.command.subcommands; -import io.papermc.paper.command.PaperSubcommand; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; 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; +import org.jspecify.annotations.NullMarked; -@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 redirect = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); - if (redirect != null) { - redirect.execute(sender, "paper", new String[0]); - } - return true; +@NullMarked +public final class VersionCommand { + + public static LiteralArgumentBuilder create(String permission, String name) { + return Commands.literal(name) + .requires(stack -> stack.getSender().hasPermission(permission)) + .executes(ctx -> { + final org.bukkit.command.Command redirect = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); + if (redirect != null) { + redirect.execute(ctx.getSource().getSender(), "paper", new String[0]); + } + + return Command.SINGLE_SUCCESS; + }); } } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 03dfcb4665d0..4b9e23e97a5a 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -12,6 +12,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.serialization.Dynamic; import com.mojang.serialization.Lifecycle; +import io.papermc.paper.command.PaperCommand; import io.papermc.paper.configuration.PaperServerConfiguration; import io.papermc.paper.configuration.ServerConfiguration; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; @@ -427,7 +428,11 @@ public CraftPlayer apply(ServerPlayer player) { this.pluginManager = new SimplePluginManager(this, commandMap); this.paperPluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(this, this.commandMap, pluginManager); this.pluginManager.paperPluginManager = this.paperPluginManager; - // Paper end + // Paper end + + // We have to register the permissions of the PaperCommand **after** the Commands have already been registered + // because of the pluginManager not being set then. + PaperCommand.registerPermissions(); CraftRegistry.setMinecraftRegistry(console.registryAccess()); From 03505b4aa09a85885b511929c376bddb0a26d9af Mon Sep 17 00:00:00 2001 From: Strokkur424 Date: Sat, 28 Jun 2025 16:02:03 +0200 Subject: [PATCH 2/9] Convert moonrise-based commands to Brigadier --- .../patches/features/0004-Anti-Xray.patch | 4 +- .../0016-Moonrise-optimisation-patches.patch | 290 ++++++++---------- .../io/papermc/paper/FeatureHooks.java.patch | 7 +- 3 files changed, 137 insertions(+), 164 deletions(-) diff --git a/paper-server/patches/features/0004-Anti-Xray.patch b/paper-server/patches/features/0004-Anti-Xray.patch index 2dbce57f7d32..37d9f72bffff 100644 --- a/paper-server/patches/features/0004-Anti-Xray.patch +++ b/paper-server/patches/features/0004-Anti-Xray.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Anti-Xray diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java -index 811a8e5141f2061a185b53b63d951646141c0c7d..33d3eb510c5844e72bbc382bd24641aae080962d 100644 +index ad287cff6d6554073a07bf0f3ea6b4bbe7d030a3..a30de25f00b770871ec4067683b52f37cc28c9c5 100644 --- a/io/papermc/paper/FeatureHooks.java +++ b/io/papermc/paper/FeatureHooks.java -@@ -45,20 +45,25 @@ public final class FeatureHooks { +@@ -46,20 +46,25 @@ public final class FeatureHooks { } public static LevelChunkSection createSection(final Registry biomeRegistry, final Level level, final ChunkPos chunkPos, final int chunkSection) { diff --git a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch index ebd69473aac1..420ba005a5f0 100644 --- a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch @@ -23061,20 +23061,20 @@ index 0000000000000000000000000000000000000000..f1f72a051083b61273202cb4e67ecb11 + private SaveUtil() {} +} diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java -index b1a7af2dd3a3e166b1e58125f13ce08b9ec6d9ff..df6fbb35e5023b42de0b97434712e04a6b3e66a3 100644 +index a30de25f00b770871ec4067683b52f37cc28c9c5..9f32cdd271b855f4d6a236895f726b727859f8e5 100644 --- a/io/papermc/paper/FeatureHooks.java +++ b/io/papermc/paper/FeatureHooks.java -@@ -1,6 +1,9 @@ - package io.papermc.paper; +@@ -2,6 +2,9 @@ package io.papermc.paper; - import io.papermc.paper.command.PaperSubcommand; + import com.mojang.brigadier.builder.LiteralArgumentBuilder; + import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.subcommands.ChunkDebugCommand; +import io.papermc.paper.command.subcommands.FixLightCommand; +import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.longs.LongSets; -@@ -32,16 +35,20 @@ public final class FeatureHooks { +@@ -33,16 +36,24 @@ public final class FeatureHooks { // this includes non-accessible entities public static Iterable getAllEntities(final net.minecraft.server.level.ServerLevel world) { @@ -23090,13 +23090,17 @@ index b1a7af2dd3a3e166b1e58125f13ce08b9ec6d9ff..df6fbb35e5023b42de0b97434712e04a + ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.init(useParallelGen); // Paper - Chunk system } - public static void registerPaperCommands(final Map, PaperSubcommand> commands) { -+ commands.put(Set.of("fixlight"), new FixLightCommand()); // Paper - rewrite chunk system -+ commands.put(Set.of("debug", "chunkinfo", "holderinfo"), new ChunkDebugCommand()); // Paper - rewrite chunk system + public static void registerPaperCommands(final Map>> commands) { ++ // Paper start - Chunk system ++ commands.put("fixlight", List.of(FixLightCommand.create())); ++ commands.put("debug", List.of(ChunkDebugCommand.createDebug())); ++ commands.put("chunkinfo", List.of(ChunkDebugCommand.createChunkInfo())); ++ commands.put("holderinfo", List.of(ChunkDebugCommand.createHolderInfo())); ++ // Paper end - Chunk system } public static LevelChunkSection createSection(final Registry biomeRegistry, final Level level, final ChunkPos chunkPos, final int chunkSection) { -@@ -67,111 +74,58 @@ public final class FeatureHooks { +@@ -68,111 +79,58 @@ public final class FeatureHooks { } public static Set getSentChunkKeys(final ServerPlayer player) { @@ -23229,7 +23233,7 @@ index b1a7af2dd3a3e166b1e58125f13ce08b9ec6d9ff..df6fbb35e5023b42de0b97434712e04a org.bukkit.Chunk chunk = null; for (net.minecraft.server.level.Ticket ticket : tickets) { -@@ -191,15 +145,15 @@ public final class FeatureHooks { +@@ -192,15 +150,15 @@ public final class FeatureHooks { } public static int getViewDistance(net.minecraft.server.level.ServerLevel world) { @@ -23248,7 +23252,7 @@ index b1a7af2dd3a3e166b1e58125f13ce08b9ec6d9ff..df6fbb35e5023b42de0b97434712e04a } public static void setViewDistance(net.minecraft.server.level.ServerLevel world, int distance) { -@@ -217,35 +171,31 @@ public final class FeatureHooks { +@@ -218,35 +176,31 @@ public final class FeatureHooks { } public static void setSendViewDistance(net.minecraft.server.level.ServerLevel world, int distance) { @@ -23291,26 +23295,24 @@ index b1a7af2dd3a3e166b1e58125f13ce08b9ec6d9ff..df6fbb35e5023b42de0b97434712e04a } } -\ No newline at end of file diff --git a/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd01fba0fc +index 0000000000000000000000000000000000000000..9be961b2c935af162ff27bd49618d6518ad91696 --- /dev/null +++ b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java -@@ -0,0 +1,277 @@ +@@ -0,0 +1,252 @@ +package io.papermc.paper.command.subcommands; + +import ca.spottedleaf.moonrise.common.util.JsonUtil; +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder; -+import io.papermc.paper.command.CommandUtil; -+import io.papermc.paper.command.PaperSubcommand; ++import com.mojang.brigadier.builder.LiteralArgumentBuilder; ++import io.papermc.paper.command.PaperCommand; ++import io.papermc.paper.command.brigadier.CommandSourceStack; ++import io.papermc.paper.command.brigadier.Commands; +import java.io.File; -+import java.util.ArrayList; -+import java.util.Collections; +import java.util.List; -+import java.util.Locale; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.chunk.ChunkAccess; @@ -23320,9 +23322,6 @@ index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd +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; @@ -23330,66 +23329,71 @@ index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd +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); -+ case "holderinfo" -> this.doHolderInfo(sender, args); -+ } -+ return true; ++@org.jspecify.annotations.NullMarked ++public final class ChunkDebugCommand { ++ ++ private static final net.kyori.adventure.text.Component DEBUG_HELP = text("Use /paper debug [chunks] help for more information on a specific command", RED); ++ private static final net.kyori.adventure.text.Component DEBUG_CHUNKS_HELP = text("Use /paper debug chunks to dump loaded chunk information to a file", RED); ++ ++ public static LiteralArgumentBuilder createDebug() { ++ return Commands.literal("debug") ++ .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "debug")) ++ ++ .then(Commands.literal("help") ++ .executes(ctx -> { ++ ctx.getSource().getSender().sendMessage(DEBUG_HELP); ++ return com.mojang.brigadier.Command.SINGLE_SUCCESS; ++ }) ++ ) ++ ++ .then(Commands.literal("chunks") ++ .then(Commands.literal("help") ++ .executes(ctx -> { ++ ctx.getSource().getSender().sendMessage(DEBUG_CHUNKS_HELP); ++ return com.mojang.brigadier.Command.SINGLE_SUCCESS; ++ }) ++ ) ++ ++ .executes(ctx -> { ++ debugDumpLoadedChunks(ctx.getSource().getSender()); ++ return com.mojang.brigadier.Command.SINGLE_SUCCESS; ++ }) ++ ) ++ ++ .executes(ctx -> { ++ ctx.getSource().getSender().sendMessage(DEBUG_HELP); ++ return com.mojang.brigadier.Command.SINGLE_SUCCESS; ++ }); + } + -+ @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"); -+ } -+ } -+ case "holderinfo" -> { -+ List worldNames = new ArrayList<>(); -+ worldNames.add("*"); -+ for (org.bukkit.World world : Bukkit.getWorlds()) { -+ worldNames.add(world.getName()); -+ } -+ if (args.length == 1) { -+ return CommandUtil.getListMatchingLast(sender, args, worldNames); -+ } -+ } -+ case "chunkinfo" -> { -+ List worldNames = new ArrayList<>(); -+ worldNames.add("*"); -+ for (org.bukkit.World world : Bukkit.getWorlds()) { -+ worldNames.add(world.getName()); -+ } -+ if (args.length == 1) { -+ return CommandUtil.getListMatchingLast(sender, args, worldNames); -+ } -+ } -+ } -+ return Collections.emptyList(); ++ private static LiteralArgumentBuilder createWorldInfo(String name, WorldInfoLogic info) { ++ return Commands.literal(name) ++ .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + name)) ++ ++ .then(Commands.argument("world", io.papermc.paper.command.brigadier.argument.ArgumentTypes.world()) ++ .executes(ctx -> { ++ info.doInfo(ctx.getSource().getSender(), List.of(ctx.getArgument("world", org.bukkit.World.class))); ++ return com.mojang.brigadier.Command.SINGLE_SUCCESS; ++ }) ++ ) ++ ++ .then(Commands.literal("*") ++ .executes(ctx -> { ++ info.doInfo(ctx.getSource().getSender(), Bukkit.getWorlds()); ++ return com.mojang.brigadier.Command.SINGLE_SUCCESS; ++ }) ++ ); + } + -+ private void doChunkInfo(final CommandSender sender, final String[] args) { -+ List worlds; -+ if (args.length < 1 || args[0].equals("*")) { -+ worlds = Bukkit.getWorlds(); -+ } else { -+ 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 '" + arg + "' is invalid", RED)); -+ return; -+ } -+ worlds.add(world); -+ } -+ } ++ public static LiteralArgumentBuilder createChunkInfo() { ++ return createWorldInfo("chunkinfo", ChunkDebugCommand::doChunkInfo); ++ } + ++ public static LiteralArgumentBuilder createHolderInfo() { ++ return createWorldInfo("holderinfo", ChunkDebugCommand::doHolderInfo); ++ } ++ ++ private static void doChunkInfo(final CommandSender sender, final List worlds) { + int accumulatedTotal = 0; + int accumulatedInactive = 0; + int accumulatedBorder = 0; @@ -23462,22 +23466,7 @@ index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd + } + } + -+ private void doHolderInfo(final CommandSender sender, final String[] args) { -+ List worlds; -+ if (args.length < 1 || args[0].equals("*")) { -+ worlds = Bukkit.getWorlds(); -+ } else { -+ 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 '" + arg + "' is invalid", RED)); -+ return; -+ } -+ worlds.add(world); -+ } -+ } -+ ++ private static void doHolderInfo(final CommandSender sender, final List worlds) { + int accumulatedTotal = 0; + int accumulatedCanUnload = 0; + int accumulatedNull = 0; @@ -23546,46 +23535,39 @@ index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd + } + } + -+ 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; -+ } ++ private static void debugDumpLoadedChunks(final CommandSender sender) { ++ final File file = ChunkTaskScheduler.getChunkDebugFile(); ++ sender.sendMessage(text("Writing chunk information dump to " + file, GREEN)); + -+ final String debugType = args[0].toLowerCase(Locale.ROOT); -+ switch (debugType) { -+ case "chunks" -> { -+ if (args.length >= 2 && args[1].toLowerCase(Locale.ROOT).equals("help")) { -+ sender.sendMessage(text("Use /paper debug chunks to dump loaded chunk information to a file", RED)); -+ break; -+ } -+ final File file = ChunkTaskScheduler.getChunkDebugFile(); -+ sender.sendMessage(text("Writing chunk information dump to " + file, GREEN)); -+ try { -+ JsonUtil.writeJson(ChunkTaskScheduler.debugAllWorlds(MinecraftServer.getServer()), file); -+ sender.sendMessage(text("Successfully written chunk information!", GREEN)); -+ } catch (Throwable thr) { -+ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); -+ sender.sendMessage(text("Failed to dump chunk information, see console", RED)); -+ } -+ } -+ // "help" & default -+ default -> sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); ++ try { ++ JsonUtil.writeJson(ChunkTaskScheduler.debugAllWorlds(MinecraftServer.getServer()), file); ++ sender.sendMessage(text("Successfully written chunk information!", GREEN)); ++ } catch (Throwable thr) { ++ net.minecraft.server.MinecraftServer.LOGGER.warn("Failed to dump chunk information to file {}", file, thr); ++ sender.sendMessage(text("Failed to dump chunk information, see console", RED)); + } + } + ++ @FunctionalInterface ++ private interface WorldInfoLogic { ++ void doInfo(final CommandSender sender, final List worlds); ++ } +} diff --git a/io/papermc/paper/command/subcommands/FixLightCommand.java b/io/papermc/paper/command/subcommands/FixLightCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..85950a1aa732ab8c01ad28bec9e0de140e1a172e +index 0000000000000000000000000000000000000000..846ba8ffaa0fa4c241aec30d8bb15a9cbd335fe9 --- /dev/null +++ b/io/papermc/paper/command/subcommands/FixLightCommand.java -@@ -0,0 +1,116 @@ +@@ -0,0 +1,110 @@ +package io.papermc.paper.command.subcommands; + +import ca.spottedleaf.moonrise.patches.starlight.light.StarLightLightingProvider; -+import io.papermc.paper.command.PaperSubcommand; ++import com.mojang.brigadier.builder.LiteralArgumentBuilder; ++import io.papermc.paper.command.PaperCommand; ++import io.papermc.paper.command.brigadier.CommandSourceStack; ++import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.util.MCUtil; ++import java.text.DecimalFormat; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.ThreadedLevelLightEngine; @@ -23594,63 +23576,53 @@ index 0000000000000000000000000000000000000000..85950a1aa732ab8c01ad28bec9e0de14 +import org.bukkit.command.CommandSender; +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; -+ -+import java.text.DecimalFormat; ++import org.jspecify.annotations.NullMarked; + +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.RED; + -+@DefaultQualifier(NonNull.class) -+public final class FixLightCommand implements PaperSubcommand { ++@NullMarked ++public final class FixLightCommand { + -+ private static final ThreadLocal ONE_DECIMAL_PLACES = ThreadLocal.withInitial(() -> { -+ return new DecimalFormat("#,##0.0"); -+ }); ++ private static final int MAX_RADIUS = 32; ++ private static final ThreadLocal ONE_DECIMAL_PLACES = ThreadLocal.withInitial(() -> new java.text.DecimalFormat("#,##0.0")); + -+ @Override -+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { -+ this.doFixLight(sender, args); -+ return true; ++ public static LiteralArgumentBuilder create() { ++ return Commands.literal("fixlight") ++ .requires(stack -> stack.getSender() instanceof Player player && player.hasPermission(PaperCommand.BASE_PERM + "fixlight")) ++ ++ .then(Commands.argument("radius", com.mojang.brigadier.arguments.IntegerArgumentType.integer(0)) ++ .executes(ctx -> { ++ doFixLight(ctx.getSource().getSender(), com.mojang.brigadier.arguments.IntegerArgumentType.getInteger(ctx, "radius")); ++ return com.mojang.brigadier.Command.SINGLE_SUCCESS; ++ }) ++ ) ++ ++ .executes(ctx -> { ++ doFixLight(ctx.getSource().getSender(), 2); ++ return com.mojang.brigadier.Command.SINGLE_SUCCESS; ++ }); + } + -+ private void doFixLight(final CommandSender sender, final String[] args) { -+ if (!(sender instanceof Player)) { -+ sender.sendMessage(text("Only players can use this command", RED)); -+ return; -+ } -+ @Nullable Runnable post = null; -+ int radius = 2; -+ if (args.length > 0) { -+ try { -+ final int parsed = Integer.parseInt(args[0]); -+ if (parsed < 0) { -+ sender.sendMessage(text("Radius cannot be negative!", RED)); -+ return; -+ } -+ final int maxRadius = 32; -+ 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; -+ } ++ private static void doFixLight(final CommandSender sender, final int inputRadius) { ++ Runnable post = null; ++ ++ int radius = Math.min(MAX_RADIUS, inputRadius); ++ if (radius != inputRadius) { ++ post = () -> sender.sendMessage(text("Radius '" + inputRadius + "' was not in the required range [0, " + MAX_RADIUS + "], it was lowered to the maximum (" + MAX_RADIUS + " chunks).", RED)); + } + + CraftPlayer player = (CraftPlayer) sender; + ServerPlayer handle = player.getHandle(); + ServerLevel world = (ServerLevel) handle.level(); + ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine(); -+ this.starlightFixLight(handle, world, lightengine, radius, post); ++ starlightFixLight(handle, world, lightengine, radius, post); + } + -+ private void starlightFixLight( ++ private static void starlightFixLight( + final ServerPlayer sender, + final ServerLevel world, + final ThreadedLevelLightEngine lightengine, @@ -23664,7 +23636,7 @@ index 0000000000000000000000000000000000000000..85950a1aa732ab8c01ad28bec9e0de14 + for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext(); ) { + final ChunkPos chunkPos = iterator.next(); + -+ final @Nullable ChunkAccess chunk = (ChunkAccess) world.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); ++ final ChunkAccess chunk = (ChunkAccess) world.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); + if (chunk == null || !chunk.isLightCorrect() || !chunk.getPersistedStatus().isOrAfter(net.minecraft.world.level.chunk.status.ChunkStatus.LIGHT)) { + // cannot relight this chunk + iterator.remove(); diff --git a/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch b/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch index 6d467d774a0d..7c527ac21eef 100644 --- a/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch +++ b/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch @@ -1,9 +1,10 @@ --- /dev/null +++ b/io/papermc/paper/FeatureHooks.java -@@ -1,0 +_,246 @@ +@@ -1,0 +_,247 @@ +package io.papermc.paper; + -+import io.papermc.paper.command.PaperSubcommand; ++import com.mojang.brigadier.builder.LiteralArgumentBuilder; ++import io.papermc.paper.command.brigadier.CommandSourceStack; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.longs.LongSets; @@ -44,7 +45,7 @@ + public static void initChunkTaskScheduler(final boolean useParallelGen) { + } + -+ public static void registerPaperCommands(final Map, PaperSubcommand> commands) { ++ public static void registerPaperCommands(final Map>> commands) { + } + + public static LevelChunkSection createSection(final Registry biomeRegistry, final Level level, final ChunkPos chunkPos, final int chunkSection) { From 8d4293d513573f5739e926caba263b996ad6b5da Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sun, 28 Dec 2025 00:14:02 +0100 Subject: [PATCH 3/9] Rebase onto 1.21.11 (patches version) --- .../0001-Moonrise-optimisation-patches.patch | 294 ++++++++---------- .../patches/features/0029-Anti-Xray.patch | 6 +- .../io/papermc/paper/FeatureHooks.java.patch | 7 +- 3 files changed, 143 insertions(+), 164 deletions(-) diff --git a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch index 9de3cccbff3e..0db9ef05fa22 100644 --- a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch @@ -22256,20 +22256,20 @@ index 0000000000000000000000000000000000000000..95154636727366adfb4fb67e7db8b09f + private SaveUtil() {} +} diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java -index 61c526263cfbd1871f6b8548ed4431a76ee95a31..485413cb6b20c4bd20fbc29a0db43c051993fe79 100644 +index 5d384b9254233669e975e7d37aef101ca00e13cc..aabeb29a7f3d593ad78d47cb6baa932f0dffa5aa 100644 --- a/io/papermc/paper/FeatureHooks.java +++ b/io/papermc/paper/FeatureHooks.java -@@ -1,6 +1,9 @@ - package io.papermc.paper; +@@ -2,6 +2,9 @@ package io.papermc.paper; - import io.papermc.paper.command.PaperSubcommand; + import com.mojang.brigadier.builder.LiteralArgumentBuilder; + import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.subcommands.ChunkDebugCommand; +import io.papermc.paper.command.subcommands.FixLightCommand; +import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.longs.LongSets; -@@ -32,13 +35,16 @@ public final class FeatureHooks { +@@ -33,13 +36,20 @@ public final class FeatureHooks { // this includes non-accessible entities public static Iterable getAllEntities(final net.minecraft.server.level.ServerLevel world) { @@ -22281,13 +22281,17 @@ index 61c526263cfbd1871f6b8548ed4431a76ee95a31..485413cb6b20c4bd20fbc29a0db43c05 + ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.setUnloadDelay(ticks); // Paper - rewrite chunk system } - public static void registerPaperCommands(final Map, PaperSubcommand> commands) { -+ commands.put(Set.of("fixlight"), new FixLightCommand()); // Paper - rewrite chunk system -+ commands.put(Set.of("debug", "chunkinfo", "holderinfo"), new ChunkDebugCommand()); // Paper - rewrite chunk system + public static void registerPaperCommands(final Map>> commands) { ++ // Paper start - Chunk system ++ commands.put("fixlight", List.of(FixLightCommand.create())); ++ commands.put("debug", List.of(ChunkDebugCommand.createDebug())); ++ commands.put("chunkinfo", List.of(ChunkDebugCommand.createChunkInfo())); ++ commands.put("holderinfo", List.of(ChunkDebugCommand.createHolderInfo())); ++ // Paper end - Chunk system } public static LevelChunkSection createSection(final PalettedContainerFactory palettedContainerFactory, final Level level, final ChunkPos chunkPos, final int chunkSection) { -@@ -59,111 +65,58 @@ public final class FeatureHooks { +@@ -60,111 +70,58 @@ public final class FeatureHooks { } public static Set getSentChunkKeys(final ServerPlayer player) { @@ -22420,7 +22424,7 @@ index 61c526263cfbd1871f6b8548ed4431a76ee95a31..485413cb6b20c4bd20fbc29a0db43c05 org.bukkit.Chunk chunk = null; for (net.minecraft.server.level.Ticket ticket : tickets) { -@@ -183,15 +136,15 @@ public final class FeatureHooks { +@@ -184,15 +141,15 @@ public final class FeatureHooks { } public static int getViewDistance(net.minecraft.server.level.ServerLevel world) { @@ -22439,7 +22443,7 @@ index 61c526263cfbd1871f6b8548ed4431a76ee95a31..485413cb6b20c4bd20fbc29a0db43c05 } public static void setViewDistance(net.minecraft.server.level.ServerLevel world, int distance) { -@@ -209,35 +162,31 @@ public final class FeatureHooks { +@@ -210,35 +167,31 @@ public final class FeatureHooks { } public static void setSendViewDistance(net.minecraft.server.level.ServerLevel world, int distance) { @@ -22481,28 +22485,26 @@ index 61c526263cfbd1871f6b8548ed4431a76ee95a31..485413cb6b20c4bd20fbc29a0db43c05 + ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer)player).moonrise$getViewDistanceHolder().setSendViewDistance(distance); // Paper - rewrite chunk system } --} -\ No newline at end of file -+} + } diff --git a/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd01fba0fc +index 0000000000000000000000000000000000000000..88459f79fb0af8bbc37834dbc56c9c39b58c461e --- /dev/null +++ b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java -@@ -0,0 +1,277 @@ +@@ -0,0 +1,254 @@ +package io.papermc.paper.command.subcommands; + +import ca.spottedleaf.moonrise.common.util.JsonUtil; +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder; -+import io.papermc.paper.command.CommandUtil; -+import io.papermc.paper.command.PaperSubcommand; ++import com.mojang.brigadier.Command; ++import com.mojang.brigadier.builder.LiteralArgumentBuilder; ++import io.papermc.paper.command.PaperCommand; ++import io.papermc.paper.command.brigadier.CommandSourceStack; ++import io.papermc.paper.command.brigadier.Commands; +import java.io.File; -+import java.util.ArrayList; -+import java.util.Collections; +import java.util.List; -+import java.util.Locale; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.chunk.ChunkAccess; @@ -22512,9 +22514,7 @@ index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd +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 org.jspecify.annotations.NullMarked; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.BLUE; @@ -22522,66 +22522,71 @@ index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd +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); -+ case "holderinfo" -> this.doHolderInfo(sender, args); -+ } -+ return true; ++@NullMarked ++public final class ChunkDebugCommand { ++ ++ private static final net.kyori.adventure.text.Component DEBUG_HELP = text("Use /paper debug [chunks] help for more information on a specific command", RED); ++ private static final net.kyori.adventure.text.Component DEBUG_CHUNKS_HELP = text("Use /paper debug chunks to dump loaded chunk information to a file", RED); ++ ++ public static LiteralArgumentBuilder createDebug() { ++ return Commands.literal("debug") ++ .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "debug")) ++ ++ .then(Commands.literal("help") ++ .executes(ctx -> { ++ ctx.getSource().getSender().sendMessage(DEBUG_HELP); ++ return Command.SINGLE_SUCCESS; ++ }) ++ ) ++ ++ .then(Commands.literal("chunks") ++ .then(Commands.literal("help") ++ .executes(ctx -> { ++ ctx.getSource().getSender().sendMessage(DEBUG_CHUNKS_HELP); ++ return Command.SINGLE_SUCCESS; ++ }) ++ ) ++ ++ .executes(ctx -> { ++ debugDumpLoadedChunks(ctx.getSource().getSender()); ++ return Command.SINGLE_SUCCESS; ++ }) ++ ) ++ ++ .executes(ctx -> { ++ ctx.getSource().getSender().sendMessage(DEBUG_HELP); ++ return Command.SINGLE_SUCCESS; ++ }); + } + -+ @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"); -+ } -+ } -+ case "holderinfo" -> { -+ List worldNames = new ArrayList<>(); -+ worldNames.add("*"); -+ for (org.bukkit.World world : Bukkit.getWorlds()) { -+ worldNames.add(world.getName()); -+ } -+ if (args.length == 1) { -+ return CommandUtil.getListMatchingLast(sender, args, worldNames); -+ } -+ } -+ case "chunkinfo" -> { -+ List worldNames = new ArrayList<>(); -+ worldNames.add("*"); -+ for (org.bukkit.World world : Bukkit.getWorlds()) { -+ worldNames.add(world.getName()); -+ } -+ if (args.length == 1) { -+ return CommandUtil.getListMatchingLast(sender, args, worldNames); -+ } -+ } -+ } -+ return Collections.emptyList(); ++ private static LiteralArgumentBuilder createWorldInfo(String name, WorldInfoLogic info) { ++ return Commands.literal(name) ++ .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + name)) ++ ++ .then(Commands.argument("world", io.papermc.paper.command.brigadier.argument.ArgumentTypes.world()) ++ .executes(ctx -> { ++ info.doInfo(ctx.getSource().getSender(), List.of(ctx.getArgument("world", org.bukkit.World.class))); ++ return Command.SINGLE_SUCCESS; ++ }) ++ ) ++ ++ .then(Commands.literal("*") ++ .executes(ctx -> { ++ info.doInfo(ctx.getSource().getSender(), Bukkit.getWorlds()); ++ return Command.SINGLE_SUCCESS; ++ }) ++ ); + } + -+ private void doChunkInfo(final CommandSender sender, final String[] args) { -+ List worlds; -+ if (args.length < 1 || args[0].equals("*")) { -+ worlds = Bukkit.getWorlds(); -+ } else { -+ 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 '" + arg + "' is invalid", RED)); -+ return; -+ } -+ worlds.add(world); -+ } -+ } ++ public static LiteralArgumentBuilder createChunkInfo() { ++ return createWorldInfo("chunkinfo", ChunkDebugCommand::doChunkInfo); ++ } + ++ public static LiteralArgumentBuilder createHolderInfo() { ++ return createWorldInfo("holderinfo", ChunkDebugCommand::doHolderInfo); ++ } ++ ++ private static void doChunkInfo(final CommandSender sender, final List worlds) { + int accumulatedTotal = 0; + int accumulatedInactive = 0; + int accumulatedBorder = 0; @@ -22654,22 +22659,7 @@ index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd + } + } + -+ private void doHolderInfo(final CommandSender sender, final String[] args) { -+ List worlds; -+ if (args.length < 1 || args[0].equals("*")) { -+ worlds = Bukkit.getWorlds(); -+ } else { -+ 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 '" + arg + "' is invalid", RED)); -+ return; -+ } -+ worlds.add(world); -+ } -+ } -+ ++ private static void doHolderInfo(final CommandSender sender, final List worlds) { + int accumulatedTotal = 0; + int accumulatedCanUnload = 0; + int accumulatedNull = 0; @@ -22738,45 +22728,39 @@ index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd + } + } + -+ 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; -+ } ++ private static void debugDumpLoadedChunks(final CommandSender sender) { ++ final File file = ChunkTaskScheduler.getChunkDebugFile(); ++ sender.sendMessage(text("Writing chunk information dump to " + file, GREEN)); + -+ final String debugType = args[0].toLowerCase(Locale.ROOT); -+ switch (debugType) { -+ case "chunks" -> { -+ if (args.length >= 2 && args[1].toLowerCase(Locale.ROOT).equals("help")) { -+ sender.sendMessage(text("Use /paper debug chunks to dump loaded chunk information to a file", RED)); -+ break; -+ } -+ final File file = ChunkTaskScheduler.getChunkDebugFile(); -+ sender.sendMessage(text("Writing chunk information dump to " + file, GREEN)); -+ try { -+ JsonUtil.writeJson(ChunkTaskScheduler.debugAllWorlds(MinecraftServer.getServer()), file); -+ sender.sendMessage(text("Successfully written chunk information!", GREEN)); -+ } catch (Throwable thr) { -+ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); -+ sender.sendMessage(text("Failed to dump chunk information, see console", RED)); -+ } -+ } -+ // "help" & default -+ default -> sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); ++ try { ++ JsonUtil.writeJson(ChunkTaskScheduler.debugAllWorlds(MinecraftServer.getServer()), file); ++ sender.sendMessage(text("Successfully written chunk information!", GREEN)); ++ } catch (Throwable thr) { ++ net.minecraft.server.MinecraftServer.LOGGER.warn("Failed to dump chunk information to file {}", file, thr); ++ sender.sendMessage(text("Failed to dump chunk information, see console", RED)); + } + } + ++ @FunctionalInterface ++ private interface WorldInfoLogic { ++ void doInfo(final CommandSender sender, final List worlds); ++ } +} diff --git a/io/papermc/paper/command/subcommands/FixLightCommand.java b/io/papermc/paper/command/subcommands/FixLightCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..85950a1aa732ab8c01ad28bec9e0de140e1a172e +index 0000000000000000000000000000000000000000..7de21e177a7a781706632118957e3f130f418485 --- /dev/null +++ b/io/papermc/paper/command/subcommands/FixLightCommand.java -@@ -0,0 +1,116 @@ +@@ -0,0 +1,115 @@ +package io.papermc.paper.command.subcommands; + +import ca.spottedleaf.moonrise.patches.starlight.light.StarLightLightingProvider; -+import io.papermc.paper.command.PaperSubcommand; ++import com.mojang.brigadier.Command; ++import com.mojang.brigadier.arguments.IntegerArgumentType; ++import com.mojang.brigadier.builder.LiteralArgumentBuilder; ++import io.papermc.paper.command.PaperCommand; ++import io.papermc.paper.command.brigadier.CommandSourceStack; ++import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.util.MCUtil; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; @@ -22786,9 +22770,8 @@ index 0000000000000000000000000000000000000000..85950a1aa732ab8c01ad28bec9e0de14 +import org.bukkit.command.CommandSender; +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; ++import org.jspecify.annotations.Nullable; ++import org.jspecify.annotations.NullMarked; + +import java.text.DecimalFormat; + @@ -22797,52 +22780,47 @@ index 0000000000000000000000000000000000000000..85950a1aa732ab8c01ad28bec9e0de14 +import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; +import static net.kyori.adventure.text.format.NamedTextColor.RED; + -+@DefaultQualifier(NonNull.class) -+public final class FixLightCommand implements PaperSubcommand { ++@NullMarked ++public final class FixLightCommand { + ++ private static final int MAX_RADIUS = 32; + private static final ThreadLocal ONE_DECIMAL_PLACES = ThreadLocal.withInitial(() -> { + return new DecimalFormat("#,##0.0"); + }); + -+ @Override -+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { -+ this.doFixLight(sender, args); -+ return true; ++ public static LiteralArgumentBuilder create() { ++ return Commands.literal("fixlight") ++ .requires(stack -> stack.getSender() instanceof Player player && player.hasPermission(PaperCommand.BASE_PERM + "fixlight")) ++ ++ .then(Commands.argument("radius", IntegerArgumentType.integer(0)) ++ .executes(ctx -> { ++ doFixLight(ctx.getSource().getSender(), IntegerArgumentType.getInteger(ctx, "radius")); ++ return Command.SINGLE_SUCCESS; ++ }) ++ ) ++ ++ .executes(ctx -> { ++ doFixLight(ctx.getSource().getSender(), 2); ++ return Command.SINGLE_SUCCESS; ++ }); + } + -+ private void doFixLight(final CommandSender sender, final String[] args) { -+ if (!(sender instanceof Player)) { -+ sender.sendMessage(text("Only players can use this command", RED)); -+ return; -+ } -+ @Nullable Runnable post = null; -+ int radius = 2; -+ if (args.length > 0) { -+ try { -+ final int parsed = Integer.parseInt(args[0]); -+ if (parsed < 0) { -+ sender.sendMessage(text("Radius cannot be negative!", RED)); -+ return; -+ } -+ final int maxRadius = 32; -+ 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; -+ } ++ private static void doFixLight(final CommandSender sender, final int inputRadius) { ++ Runnable post = null; ++ ++ int radius = Math.min(MAX_RADIUS, inputRadius); ++ if (radius != inputRadius) { ++ post = () -> sender.sendMessage(text("Radius '" + inputRadius + "' was not in the required range [0, " + MAX_RADIUS + "], it was lowered to the maximum (" + MAX_RADIUS + " chunks).", RED)); + } + + CraftPlayer player = (CraftPlayer) sender; + ServerPlayer handle = player.getHandle(); + ServerLevel world = (ServerLevel) handle.level(); + ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine(); -+ this.starlightFixLight(handle, world, lightengine, radius, post); ++ starlightFixLight(handle, world, lightengine, radius, post); + } + -+ private void starlightFixLight( ++ private static void starlightFixLight( + final ServerPlayer sender, + final ServerLevel world, + final ThreadedLevelLightEngine lightengine, @@ -22856,7 +22834,7 @@ index 0000000000000000000000000000000000000000..85950a1aa732ab8c01ad28bec9e0de14 + for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext(); ) { + final ChunkPos chunkPos = iterator.next(); + -+ final @Nullable ChunkAccess chunk = (ChunkAccess) world.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); ++ final ChunkAccess chunk = (ChunkAccess) world.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); + if (chunk == null || !chunk.isLightCorrect() || !chunk.getPersistedStatus().isOrAfter(net.minecraft.world.level.chunk.status.ChunkStatus.LIGHT)) { + // cannot relight this chunk + iterator.remove(); @@ -31715,7 +31693,7 @@ index 9dc13a5b6cc1afc118a261802c71da25ae577fe5..a49a06662de4062a77112e358f536d45 public static ProblemReporter.PathElement problemPath(ChunkPos pos) { diff --git a/net/minecraft/world/level/chunk/ChunkGenerator.java b/net/minecraft/world/level/chunk/ChunkGenerator.java -index 2acee7d7211c4e7b9ac02e9a48d6c3ea239e6949..78600f4c583056403c93e72ec0996b00ec6d1284 100644 +index faf8f53bdcaa69fbbd816739dd4f1e812ced2165..7b98964eb77a619bdaaf5a9d331e3cfe080b52a4 100644 --- a/net/minecraft/world/level/chunk/ChunkGenerator.java +++ b/net/minecraft/world/level/chunk/ChunkGenerator.java @@ -116,7 +116,7 @@ public abstract class ChunkGenerator { diff --git a/paper-server/patches/features/0029-Anti-Xray.patch b/paper-server/patches/features/0029-Anti-Xray.patch index 532e5b76a2e6..fc7ab06dd242 100644 --- a/paper-server/patches/features/0029-Anti-Xray.patch +++ b/paper-server/patches/features/0029-Anti-Xray.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Anti-Xray diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java -index 485413cb6b20c4bd20fbc29a0db43c051993fe79..22f37a0779996c9e3a9416a2a1ba2bee74e3f568 100644 +index aabeb29a7f3d593ad78d47cb6baa932f0dffa5aa..5464eb75e1bbf2962491fcc13bcfe3d771644f79 100644 --- a/io/papermc/paper/FeatureHooks.java +++ b/io/papermc/paper/FeatureHooks.java -@@ -48,20 +48,25 @@ public final class FeatureHooks { +@@ -53,20 +53,25 @@ public final class FeatureHooks { } public static LevelChunkSection createSection(final PalettedContainerFactory palettedContainerFactory, final Level level, final ChunkPos chunkPos, final int chunkSection) { @@ -184,7 +184,7 @@ index c65b274b965b95eae33690e63c5da2d5a9f2981a..644948d64791d0ffa4166375d0f4419f if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) { new io.papermc.paper.event.packet.PlayerChunkLoadEvent(new org.bukkit.craftbukkit.CraftChunk(chunk), packetListener.getPlayer().getBukkitEntity()).callEvent(); diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java -index a17d021269c65d98a2ba847c0059a421ea051863..9f86d1fc4ea4c6987fa207644573565aea7edead 100644 +index dec9dd8e42f90ccf7e5e3ad945e459d07159250d..989ac565c47a70c7947cb7315d0f5c2cfecd0363 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -329,7 +329,7 @@ public abstract class PlayerList { diff --git a/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch b/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch index dd764f0757aa..7b127e049e93 100644 --- a/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch +++ b/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch @@ -1,9 +1,10 @@ --- /dev/null +++ b/io/papermc/paper/FeatureHooks.java -@@ -1,0 +_,243 @@ +@@ -1,0 +_,244 @@ +package io.papermc.paper; + -+import io.papermc.paper.command.PaperSubcommand; ++import com.mojang.brigadier.builder.LiteralArgumentBuilder; ++import io.papermc.paper.command.brigadier.CommandSourceStack; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.longs.LongSets; @@ -41,7 +42,7 @@ + public static void setPlayerChunkUnloadDelay(final long ticks) { + } + -+ public static void registerPaperCommands(final Map, PaperSubcommand> commands) { ++ public static void registerPaperCommands(final Map>> commands) { + } + + public static LevelChunkSection createSection(final PalettedContainerFactory palettedContainerFactory, final Level level, final ChunkPos chunkPos, final int chunkSection) { From 33fa4ae2239f4d9382b012f722b2577bc6db12f0 Mon Sep 17 00:00:00 2001 From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> Date: Sun, 1 Feb 2026 19:36:24 +0100 Subject: [PATCH 4/9] tweaks fix command permissions and entity/playermobcaps command --- .editorconfig | 2 +- paper-server/build.gradle.kts | 1 + .../0001-Moonrise-optimisation-patches.patch | 153 +++++++++--------- .../dedicated/DedicatedServer.java.patch | 2 +- .../papermc/paper/command/PaperCommand.java | 66 ++++---- .../papermc/paper/command/PaperCommands.java | 22 +-- .../paper/command/PaperPluginsCommand.java | 5 +- .../DynamicCommandExceptionType.java | 26 +++ .../exception/SimpleCommandExceptionType.java | 25 +++ .../command/subcommands/DumpItemCommand.java | 45 +++--- .../subcommands/DumpListenersCommand.java | 88 ++++------ .../subcommands/DumpPluginsCommand.java | 71 +++----- .../command/subcommands/EntityCommand.java | 108 ++++++++----- .../command/subcommands/HeapDumpCommand.java | 33 ++-- .../command/subcommands/MobcapsCommand.java | 117 +++++--------- .../command/subcommands/ReloadCommand.java | 20 +-- .../subcommands/SyncLoadInfoCommand.java | 65 +++----- .../command/subcommands/VersionCommand.java | 10 +- .../org/bukkit/craftbukkit/CraftServer.java | 2 +- 19 files changed, 418 insertions(+), 443 deletions(-) create mode 100644 paper-server/src/main/java/io/papermc/paper/command/brigadier/exception/DynamicCommandExceptionType.java create mode 100644 paper-server/src/main/java/io/papermc/paper/command/brigadier/exception/SimpleCommandExceptionType.java diff --git a/.editorconfig b/.editorconfig index 600eac0fb910..8177de6f9fa0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -36,7 +36,7 @@ ij_java_method_parameters_right_paren_on_new_line = true ij_java_use_fq_class_names = false ij_java_class_names_in_javadoc = 1 -[paper-server/src/minecraft/java/**/*.java] +[paper-server/src/minecraft/java/{net/minecraft,com/mojang}/**/*.java] ij_java_use_fq_class_names = true [paper-server/src/minecraft/resources/data/**/*.json] diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts index dac35c177814..bf36a4a81aa3 100644 --- a/paper-server/build.gradle.kts +++ b/paper-server/build.gradle.kts @@ -306,6 +306,7 @@ fun TaskContainer.registerRunTask( systemProperty("disable.watchdog", true) } systemProperty("io.papermc.paper.suppress.sout.nags", true) + systemProperty("paper.debug-sync-loads", true) val memoryGb = providers.gradleProperty("paper.runMemoryGb").getOrElse("2") minHeapSize = "${memoryGb}G" diff --git a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch index 0db9ef05fa22..14becddb01c6 100644 --- a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch @@ -22256,7 +22256,7 @@ index 0000000000000000000000000000000000000000..95154636727366adfb4fb67e7db8b09f + private SaveUtil() {} +} diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java -index 5d384b9254233669e975e7d37aef101ca00e13cc..aabeb29a7f3d593ad78d47cb6baa932f0dffa5aa 100644 +index 6f9e2dd6de4ae89ef6f393882903b13c72899605..da1cec4991420a091a55270a49246d1020f1bbb6 100644 --- a/io/papermc/paper/FeatureHooks.java +++ b/io/papermc/paper/FeatureHooks.java @@ -2,6 +2,9 @@ package io.papermc.paper; @@ -22486,12 +22486,13 @@ index 5d384b9254233669e975e7d37aef101ca00e13cc..aabeb29a7f3d593ad78d47cb6baa932f } } +\ No newline at end of file diff --git a/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..88459f79fb0af8bbc37834dbc56c9c39b58c461e +index 0000000000000000000000000000000000000000..7ce40cacc0ba87c68e6e149f2097854b2deab8ac --- /dev/null +++ b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java -@@ -0,0 +1,254 @@ +@@ -0,0 +1,256 @@ +package io.papermc.paper.command.subcommands; + +import ca.spottedleaf.moonrise.common.util.JsonUtil; @@ -22503,8 +22504,10 @@ index 0000000000000000000000000000000000000000..88459f79fb0af8bbc37834dbc56c9c39 +import io.papermc.paper.command.PaperCommand; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; ++import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import java.io.File; +import java.util.List; ++import net.kyori.adventure.text.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.chunk.ChunkAccess; @@ -22512,6 +22515,7 @@ index 0000000000000000000000000000000000000000..88459f79fb0af8bbc37834dbc56c9c39 +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.ProtoChunk; +import org.bukkit.Bukkit; ++import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftWorld; +import org.jspecify.annotations.NullMarked; @@ -22525,55 +22529,46 @@ index 0000000000000000000000000000000000000000..88459f79fb0af8bbc37834dbc56c9c39 +@NullMarked +public final class ChunkDebugCommand { + -+ private static final net.kyori.adventure.text.Component DEBUG_HELP = text("Use /paper debug [chunks] help for more information on a specific command", RED); -+ private static final net.kyori.adventure.text.Component DEBUG_CHUNKS_HELP = text("Use /paper debug chunks to dump loaded chunk information to a file", RED); ++ private static final Component DEBUG_HELP = text("Use /paper debug [chunks] help for more information on a specific command", RED); ++ private static final Component DEBUG_CHUNKS_HELP = text("Use /paper debug chunks to dump loaded chunk information to a file", RED); + + public static LiteralArgumentBuilder createDebug() { + return Commands.literal("debug") -+ .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "debug")) -+ ++ .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + "debug")) + .then(Commands.literal("help") -+ .executes(ctx -> { -+ ctx.getSource().getSender().sendMessage(DEBUG_HELP); ++ .executes(context -> { ++ context.getSource().getSender().sendMessage(DEBUG_HELP); + return Command.SINGLE_SUCCESS; + }) + ) -+ + .then(Commands.literal("chunks") + .then(Commands.literal("help") -+ .executes(ctx -> { -+ ctx.getSource().getSender().sendMessage(DEBUG_CHUNKS_HELP); ++ .executes(context -> { ++ context.getSource().getSender().sendMessage(DEBUG_CHUNKS_HELP); + return Command.SINGLE_SUCCESS; + }) + ) -+ -+ .executes(ctx -> { -+ debugDumpLoadedChunks(ctx.getSource().getSender()); -+ return Command.SINGLE_SUCCESS; ++ .executes(context -> { ++ return debugDumpLoadedChunks(context.getSource().getSender()); + }) + ) -+ -+ .executes(ctx -> { -+ ctx.getSource().getSender().sendMessage(DEBUG_HELP); ++ .executes(context -> { ++ context.getSource().getSender().sendMessage(DEBUG_HELP); + return Command.SINGLE_SUCCESS; + }); + } + + private static LiteralArgumentBuilder createWorldInfo(String name, WorldInfoLogic info) { + return Commands.literal(name) -+ .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + name)) -+ -+ .then(Commands.argument("world", io.papermc.paper.command.brigadier.argument.ArgumentTypes.world()) -+ .executes(ctx -> { -+ info.doInfo(ctx.getSource().getSender(), List.of(ctx.getArgument("world", org.bukkit.World.class))); -+ return Command.SINGLE_SUCCESS; ++ .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + name)) ++ .then(Commands.argument("world", ArgumentTypes.world()) ++ .executes(context -> { ++ return info.doInfo(context.getSource().getSender(), List.of(context.getArgument("world", World.class))); + }) + ) -+ + .then(Commands.literal("*") -+ .executes(ctx -> { -+ info.doInfo(ctx.getSource().getSender(), Bukkit.getWorlds()); -+ return Command.SINGLE_SUCCESS; ++ .executes(context -> { ++ return info.doInfo(context.getSource().getSender(), Bukkit.getWorlds()); + }) + ); + } @@ -22586,14 +22581,14 @@ index 0000000000000000000000000000000000000000..88459f79fb0af8bbc37834dbc56c9c39 + return createWorldInfo("holderinfo", ChunkDebugCommand::doHolderInfo); + } + -+ private static void doChunkInfo(final CommandSender sender, final List worlds) { ++ private static int doChunkInfo(final CommandSender sender, final List worlds) { + int accumulatedTotal = 0; + int accumulatedInactive = 0; + int accumulatedBorder = 0; + int accumulatedTicking = 0; + int accumulatedEntityTicking = 0; + -+ for (final org.bukkit.World bukkitWorld : worlds) { ++ for (final World bukkitWorld : worlds) { + final ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); + + int total = 0; @@ -22606,7 +22601,7 @@ index 0000000000000000000000000000000000000000..88459f79fb0af8bbc37834dbc56c9c39 + final NewChunkHolder.ChunkCompletion completion = holder.getLastChunkCompletion(); + final ChunkAccess chunk = completion == null ? null : completion.chunk(); + -+ if (!(chunk instanceof LevelChunk fullChunk)) { ++ if (!(chunk instanceof LevelChunk)) { + continue; + } + @@ -22638,7 +22633,7 @@ index 0000000000000000000000000000000000000000..88459f79fb0af8bbc37834dbc56c9c39 + accumulatedTicking += blockTicking; + accumulatedEntityTicking += entityTicking; + -+ sender.sendMessage(text().append(text("Chunks in ", BLUE), text(bukkitWorld.getName(), GREEN), text(":"))); ++ sender.sendMessage(text().append(text("Chunks in ", BLUE), text(bukkitWorld.getKey().asString(), GREEN), text(":"))); + sender.sendMessage(text().color(DARK_AQUA).append( + text("Total: ", BLUE), text(total), + text(" Inactive: ", BLUE), text(inactive), @@ -22657,9 +22652,10 @@ index 0000000000000000000000000000000000000000..88459f79fb0af8bbc37834dbc56c9c39 + text(" Entity Ticking: ", BLUE), text(accumulatedEntityTicking) + )); + } ++ return worlds.size(); + } + -+ private static void doHolderInfo(final CommandSender sender, final List worlds) { ++ private static int doHolderInfo(final CommandSender sender, final List worlds) { + int accumulatedTotal = 0; + int accumulatedCanUnload = 0; + int accumulatedNull = 0; @@ -22667,7 +22663,7 @@ index 0000000000000000000000000000000000000000..88459f79fb0af8bbc37834dbc56c9c39 + int accumulatedProtoChunk = 0; + int accumulatedFullChunk = 0; + -+ for (final org.bukkit.World bukkitWorld : worlds) { ++ for (final World bukkitWorld : worlds) { + final ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); + + int total = 0; @@ -22705,7 +22701,7 @@ index 0000000000000000000000000000000000000000..88459f79fb0af8bbc37834dbc56c9c39 + accumulatedProtoChunk += protoChunk; + accumulatedFullChunk += fullChunk; + -+ sender.sendMessage(text().append(text("Chunks in ", BLUE), text(bukkitWorld.getName(), GREEN), text(":"))); ++ sender.sendMessage(text().append(text("Chunks in ", BLUE), text(bukkitWorld.key().asString(), GREEN), text(":"))); + sender.sendMessage(text().color(DARK_AQUA).append( + text("Total: ", BLUE), text(total), + text(" Unloadable: ", BLUE), text(canUnload), @@ -22726,32 +22722,39 @@ index 0000000000000000000000000000000000000000..88459f79fb0af8bbc37834dbc56c9c39 + text(" Full: ", BLUE), text(accumulatedFullChunk) + )); + } ++ return worlds.size(); + } + -+ private static void debugDumpLoadedChunks(final CommandSender sender) { ++ private static int debugDumpLoadedChunks(final CommandSender sender) { + final File file = ChunkTaskScheduler.getChunkDebugFile(); -+ sender.sendMessage(text("Writing chunk information dump to " + file, GREEN)); ++ sender.sendMessage( ++ text("Writing chunk information dump to", GREEN) ++ .appendSpace() ++ .append(PaperCommand.asFriendlyPath(file.toPath())) ++ ); + + try { + JsonUtil.writeJson(ChunkTaskScheduler.debugAllWorlds(MinecraftServer.getServer()), file); + sender.sendMessage(text("Successfully written chunk information!", GREEN)); ++ return Command.SINGLE_SUCCESS; + } catch (Throwable thr) { -+ net.minecraft.server.MinecraftServer.LOGGER.warn("Failed to dump chunk information to file {}", file, thr); ++ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file {}", file, thr); + sender.sendMessage(text("Failed to dump chunk information, see console", RED)); ++ return 0; + } + } + + @FunctionalInterface + private interface WorldInfoLogic { -+ void doInfo(final CommandSender sender, final List worlds); ++ int doInfo(final CommandSender sender, final List worlds); + } +} diff --git a/io/papermc/paper/command/subcommands/FixLightCommand.java b/io/papermc/paper/command/subcommands/FixLightCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..7de21e177a7a781706632118957e3f130f418485 +index 0000000000000000000000000000000000000000..1da6b0e57d8b4577c0e352a4158ee511dcbec880 --- /dev/null +++ b/io/papermc/paper/command/subcommands/FixLightCommand.java -@@ -0,0 +1,115 @@ +@@ -0,0 +1,103 @@ +package io.papermc.paper.command.subcommands; + +import ca.spottedleaf.moonrise.patches.starlight.light.StarLightLightingProvider; @@ -22762,23 +22765,23 @@ index 0000000000000000000000000000000000000000..7de21e177a7a781706632118957e3f13 +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.util.MCUtil; ++import java.text.DecimalFormat; ++import java.util.Iterator; ++import java.util.LinkedHashSet; +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.status.ChunkStatus; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; -+import org.jspecify.annotations.Nullable; +import org.jspecify.annotations.NullMarked; + -+import java.text.DecimalFormat; -+ +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.RED; + +@NullMarked +public final class FixLightCommand { @@ -22790,52 +22793,43 @@ index 0000000000000000000000000000000000000000..7de21e177a7a781706632118957e3f13 + + public static LiteralArgumentBuilder create() { + return Commands.literal("fixlight") -+ .requires(stack -> stack.getSender() instanceof Player player && player.hasPermission(PaperCommand.BASE_PERM + "fixlight")) -+ -+ .then(Commands.argument("radius", IntegerArgumentType.integer(0)) -+ .executes(ctx -> { -+ doFixLight(ctx.getSource().getSender(), IntegerArgumentType.getInteger(ctx, "radius")); ++ .requires(source -> source.getSender() instanceof Player player && player.hasPermission(PaperCommand.BASE_PERM + "fixlight")) ++ .then(Commands.argument("radius", IntegerArgumentType.integer(0, MAX_RADIUS)) ++ .executes(context -> { ++ doFixLight(context.getSource().getSender(), IntegerArgumentType.getInteger(context, "radius")); + return Command.SINGLE_SUCCESS; + }) + ) -+ -+ .executes(ctx -> { -+ doFixLight(ctx.getSource().getSender(), 2); ++ .executes(context -> { ++ doFixLight(context.getSource().getSender(), 2); + return Command.SINGLE_SUCCESS; + }); + } + -+ private static void doFixLight(final CommandSender sender, final int inputRadius) { -+ Runnable post = null; -+ -+ int radius = Math.min(MAX_RADIUS, inputRadius); -+ if (radius != inputRadius) { -+ post = () -> sender.sendMessage(text("Radius '" + inputRadius + "' was not in the required range [0, " + MAX_RADIUS + "], it was lowered to the maximum (" + MAX_RADIUS + " chunks).", RED)); -+ } -+ ++ private static void doFixLight(final CommandSender sender, final int radius) { + CraftPlayer player = (CraftPlayer) sender; + ServerPlayer handle = player.getHandle(); -+ ServerLevel world = (ServerLevel) handle.level(); -+ ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine(); -+ starlightFixLight(handle, world, lightengine, radius, post); ++ ServerLevel world = handle.level(); ++ ThreadedLevelLightEngine lightEngine = world.getChunkSource().getLightEngine(); ++ starlightFixLight(handle, sender, world, lightEngine, radius); + } + + private static void starlightFixLight( -+ final ServerPlayer sender, ++ final ServerPlayer player, ++ final CommandSender sender, + final ServerLevel world, -+ final ThreadedLevelLightEngine lightengine, -+ final int radius, -+ final @Nullable Runnable done ++ final ThreadedLevelLightEngine lightEngine, ++ final int radius + ) { + final long start = System.nanoTime(); -+ final 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 ++ final LinkedHashSet chunks = new LinkedHashSet<>(MCUtil.getSpiralOutChunks(player.blockPosition(), radius)); // getChunkCoordinates is actually just bad mappings, this function rets position as blockpos + + final int[] pending = new int[1]; -+ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext(); ) { ++ for (Iterator iterator = chunks.iterator(); iterator.hasNext(); ) { + final ChunkPos chunkPos = iterator.next(); + + final ChunkAccess chunk = (ChunkAccess) world.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); -+ if (chunk == null || !chunk.isLightCorrect() || !chunk.getPersistedStatus().isOrAfter(net.minecraft.world.level.chunk.status.ChunkStatus.LIGHT)) { ++ if (chunk == null || !chunk.isLightCorrect() || !chunk.getPersistedStatus().isOrAfter(ChunkStatus.LIGHT)) { + // cannot relight this chunk + iterator.remove(); + continue; @@ -22845,26 +22839,23 @@ index 0000000000000000000000000000000000000000..7de21e177a7a781706632118957e3f13 + } + + final int[] relitChunks = new int[1]; -+ ((StarLightLightingProvider)lightengine).starlight$serverRelightChunks(chunks, ++ ((StarLightLightingProvider) lightEngine).starlight$serverRelightChunks(chunks, + (final ChunkPos chunkPos) -> { + ++relitChunks[0]; -+ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( ++ sender.sendMessage(text().color(DARK_AQUA).append( + text("Relit chunk ", BLUE), text(chunkPos.toString()), + text(", progress: ", BLUE), text(ONE_DECIMAL_PLACES.get().format(100.0 * (double) (relitChunks[0]) / (double) pending[0]) + "%") + )); + }, + (final int totalRelit) -> { + final long end = System.nanoTime(); -+ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( ++ sender.sendMessage(text().color(DARK_AQUA).append( + text("Relit ", BLUE), text(totalRelit), + text(" chunks. Took ", BLUE), text(ONE_DECIMAL_PLACES.get().format(1.0e-6 * (end - start)) + "ms") + )); -+ if (done != null) { -+ done.run(); -+ } + } + ); -+ sender.getBukkitEntity().sendMessage(text().color(BLUE).append(text("Relighting "), text(pending[0], DARK_AQUA), text(" chunks"))); ++ sender.sendMessage(text().color(BLUE).append(text("Relighting "), text(pending[0], DARK_AQUA), text(" chunks"))); + } +} diff --git a/io/papermc/paper/threadedregions/TickRegions.java b/io/papermc/paper/threadedregions/TickRegions.java @@ -23294,7 +23285,7 @@ index cab1ab613dd081e9472de515a19e1b4738e4fba3..96f7f37c42cecea1d714f7b16276f430 // CraftBukkit start public boolean isDebugging() { diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java -index 294ad63a3ec518fd26e6f82c005ecfd6660ddd4f..eb06d8f012684845146429832e977e6c1ddcd62b 100644 +index aaa257057ddef4c370b23338512b5548263e808c..0cff8232e25e7801db22d6c47b32802dd28bd4a5 100644 --- a/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java @@ -550,7 +550,33 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface @@ -23317,7 +23308,7 @@ index 294ad63a3ec518fd26e6f82c005ecfd6660ddd4f..eb06d8f012684845146429832e977e6c + ca.spottedleaf.moonrise.common.util.JsonUtil.writeJson(ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.debugAllWorlds(this), file); + sender.sendMessage(net.kyori.adventure.text.Component.text("Successfully written chunk information!", net.kyori.adventure.text.format.NamedTextColor.GREEN)); + } catch (Throwable thr) { -+ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); ++ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file {}", file.toString(), thr); + sender.sendMessage(net.kyori.adventure.text.Component.text("Failed to dump chunk information, see console", net.kyori.adventure.text.format.NamedTextColor.RED)); + } + }; diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch index c8cd84714454..27c9fe3c266d 100644 --- a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch @@ -112,7 +112,7 @@ + // Paper end - fix converting txt to json file + org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread + thread.start(); // Paper - Enhance console tab completions for brigadier commands; start console thread after MinecraftServer.console & PaperConfig are initialized -+ io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command ++ io.papermc.paper.command.PaperCommands.registerLegacyCommands(this); // Paper - setup legacy command + this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics + com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now diff --git a/paper-server/src/main/java/io/papermc/paper/command/PaperCommand.java b/paper-server/src/main/java/io/papermc/paper/command/PaperCommand.java index f51d0d1783f4..72553f5acfcd 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/PaperCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/PaperCommand.java @@ -1,5 +1,6 @@ package io.papermc.paper.command; +import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.LiteralCommandNode; import io.papermc.paper.FeatureHooks; @@ -14,10 +15,15 @@ import io.papermc.paper.command.subcommands.ReloadCommand; import io.papermc.paper.command.subcommands.SyncLoadInfoCommand; import io.papermc.paper.command.subcommands.VersionCommand; +import java.nio.file.Path; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; import net.minecraft.util.Util; import org.bukkit.Bukkit; import org.bukkit.permissions.Permission; @@ -25,29 +31,31 @@ import org.bukkit.plugin.PluginManager; import org.jspecify.annotations.NullMarked; +import static net.kyori.adventure.text.Component.text; + @NullMarked public final class PaperCommand { + public static final DateTimeFormatter FILENAME_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss", Locale.ROOT); // could use the formatter in Util too + + public static Component asFriendlyPath(Path path) { + return text(path.toString()) + .hoverEvent(text("Click to copy the full path of the file")) + .clickEvent(ClickEvent.copyToClipboard(path.toAbsolutePath().toString())); + } + public static final String BASE_PERM = "bukkit.command.paper."; static final String DESCRIPTION = "Paper related commands"; public static LiteralCommandNode create() { - // TODO: FIX ISSUES WITH COMMANDS - // TODO: TEST COMMANDS - - final List permissions = new ArrayList<>(); - permissions.add("bukkit.command.paper"); - permissions.addAll(SUBCOMMANDS.keySet().stream().map(s -> BASE_PERM + s).toList()); - LiteralArgumentBuilder rootNode = Commands.literal("paper") - .requires(stack -> stack.getSender().hasPermission(String.join(";", permissions))) - .executes(ctx -> { - ctx.getSource().getSender().sendPlainMessage("/paper [" + String.join(" | ", SUBCOMMANDS.keySet()) + "]"); - return com.mojang.brigadier.Command.SINGLE_SUCCESS; + .requires(source -> source.getSender().hasPermission("bukkit.command.paper")) + .executes(context -> { + context.getSource().getSender().sendPlainMessage("/paper [" + String.join(" | ", SUBCOMMANDS.keySet()) + "]"); + return Command.SINGLE_SUCCESS; }); - - SUBCOMMANDS.values().forEach(subs -> subs.forEach(rootNode::then)); + SUBCOMMANDS.values().forEach(cmd -> cmd.forEach(rootNode::then)); return rootNode.build(); } @@ -57,30 +65,26 @@ public static void registerPermissions() { permissions.addAll(SUBCOMMANDS.keySet().stream().map(s -> BASE_PERM + s).toList()); final PluginManager pluginManager = Bukkit.getServer().getPluginManager(); - for (final String perm : permissions) { - pluginManager.addPermission(new Permission(perm, PermissionDefault.OP)); + for (final String permission : permissions) { + pluginManager.addPermission(new Permission(permission, PermissionDefault.OP)); } } // subcommand label -> subcommand - private static final Map>> SUBCOMMANDS = Util.make(() -> { - final Map>> commands = new HashMap<>(14); - - commands.put("heap", List.of(HeapDumpCommand.create())); - commands.put("entity", List.of(EntityCommand.create())); - commands.put("reload", List.of(ReloadCommand.create())); - commands.put("version", List.of( + private static final Map>> SUBCOMMANDS = Util.make(new HashMap<>(14), map -> { + map.put("heap", List.of(HeapDumpCommand.create())); + map.put("entity", List.of(EntityCommand.create())); + map.put("reload", List.of(ReloadCommand.create())); + map.put("version", List.of( VersionCommand.create(BASE_PERM + "version", "version"), VersionCommand.create(BASE_PERM + "version", "ver")) ); - commands.put("dumpplugins", List.of(DumpPluginsCommand.create())); - commands.put("syncloadinfo", List.of(SyncLoadInfoCommand.create())); - commands.put("dumpitem", List.of(DumpItemCommand.create())); - commands.put("mobcaps", List.of(MobcapsCommand.createGlobal())); - commands.put("playermobcaps", List.of(MobcapsCommand.createPlayer())); - commands.put("dumplisteners", List.of(DumpListenersCommand.create())); - FeatureHooks.registerPaperCommands(commands); - - return commands; + map.put("dumpplugins", List.of(DumpPluginsCommand.create())); + map.put("syncloadinfo", List.of(SyncLoadInfoCommand.create())); + map.put("dumpitem", List.of(DumpItemCommand.create())); + map.put("mobcaps", List.of(MobcapsCommand.createGlobal())); + map.put("playermobcaps", List.of(MobcapsCommand.createPlayer())); + map.put("dumplisteners", List.of(DumpListenersCommand.create())); + FeatureHooks.registerPaperCommands(map); }); } diff --git a/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java b/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java index 42b50f7505eb..723238dc5b14 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java +++ b/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java @@ -3,34 +3,34 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import io.papermc.paper.command.brigadier.CommandRegistrationFlag; import io.papermc.paper.command.brigadier.CommandSourceStack; -import net.minecraft.server.MinecraftServer; -import org.bukkit.command.Command; - import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.DefaultQualifier; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.Util; +import org.bukkit.command.Command; +import org.jspecify.annotations.NullMarked; -@DefaultQualifier(NonNull.class) +@NullMarked public final class PaperCommands { private PaperCommands() { } - private static final Map COMMANDS = new HashMap<>(); - - public static void registerCommands(final MinecraftServer server) { - COMMANDS.put("mspt", new MSPTCommand("mspt")); + private static final Map COMMANDS = Util.make(new HashMap<>(), map -> { + map.put("mspt", new MSPTCommand("mspt")); + }); + public static void registerLegacyCommands(final MinecraftServer server) { COMMANDS.forEach((s, command) -> { server.server.getCommandMap().register(s, "Paper", command); }); } public static void registerCommands() { - // Paper commands go here + // brigadier commands go here + registerInternalCommand(PaperCommand.create(), "paper", PaperCommand.DESCRIPTION, List.of(), Set.of()); registerInternalCommand(PaperVersionCommand.create(), "bukkit", PaperVersionCommand.DESCRIPTION, List.of("ver", "about"), Set.of()); registerInternalCommand(PaperPluginsCommand.create(), "bukkit", PaperPluginsCommand.DESCRIPTION, List.of("pl"), Set.of()); } diff --git a/paper-server/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java b/paper-server/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java index 9c49bb7fb8c3..d563c26a800f 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java @@ -67,10 +67,9 @@ public class PaperPluginsCommand { private static final Type JAVA_PLUGIN_PROVIDER_TYPE = new TypeToken>() {}.getType(); public static LiteralCommandNode create() { - final PaperPluginsCommand command = new PaperPluginsCommand(); return Commands.literal("plugins") .requires(source -> source.getSender().hasPermission("bukkit.command.plugins")) - .executes(command::execute) + .executes(PaperPluginsCommand::execute) .build(); } @@ -170,7 +169,7 @@ private static TextColor fromStatus(final PluginProvider provider) { } } - private int execute(CommandContext context) { + private static int execute(CommandContext context) { final CommandSender sender = context.getSource().getSender(); final TreeMap> paperPlugins = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); final TreeMap> spigotPlugins = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); diff --git a/paper-server/src/main/java/io/papermc/paper/command/brigadier/exception/DynamicCommandExceptionType.java b/paper-server/src/main/java/io/papermc/paper/command/brigadier/exception/DynamicCommandExceptionType.java new file mode 100644 index 000000000000..316a452b72c8 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/command/brigadier/exception/DynamicCommandExceptionType.java @@ -0,0 +1,26 @@ +package io.papermc.paper.command.brigadier.exception; + +import com.mojang.brigadier.ImmutableStringReader; +import com.mojang.brigadier.Message; +import com.mojang.brigadier.exceptions.CommandExceptionType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import java.util.function.Function; +import io.papermc.paper.command.brigadier.MessageComponentSerializer; +import net.kyori.adventure.text.Component; + +public class DynamicCommandExceptionType implements CommandExceptionType { + + private final Function function; + + public DynamicCommandExceptionType(final Function function) { + this.function = arg -> MessageComponentSerializer.message().serialize(function.apply(arg)); + } + + public CommandSyntaxException create(final Object arg) { + return new CommandSyntaxException(this, this.function.apply(arg)); + } + + public CommandSyntaxException createWithContext(final ImmutableStringReader reader, final Object arg) { + return new CommandSyntaxException(this, this.function.apply(arg), reader.getString(), reader.getCursor()); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/command/brigadier/exception/SimpleCommandExceptionType.java b/paper-server/src/main/java/io/papermc/paper/command/brigadier/exception/SimpleCommandExceptionType.java new file mode 100644 index 000000000000..01e189840066 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/command/brigadier/exception/SimpleCommandExceptionType.java @@ -0,0 +1,25 @@ +package io.papermc.paper.command.brigadier.exception; + +import com.mojang.brigadier.ImmutableStringReader; +import com.mojang.brigadier.Message; +import com.mojang.brigadier.exceptions.CommandExceptionType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import io.papermc.paper.command.brigadier.MessageComponentSerializer; +import net.kyori.adventure.text.Component; + +public class SimpleCommandExceptionType implements CommandExceptionType { + + private final Message message; + + public SimpleCommandExceptionType(final Component message) { + this.message = MessageComponentSerializer.message().serialize(message); + } + + public CommandSyntaxException create() { + return new CommandSyntaxException(this, this.message); + } + + public CommandSyntaxException createWithContext(final ImmutableStringReader reader) { + return new CommandSyntaxException(this, this.message, reader.getString(), reader.getCursor()); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java index 923470aec14e..4a772c552482 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java @@ -18,25 +18,24 @@ import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.text.JoinConfiguration; import net.kyori.adventure.text.TextComponent; -import net.minecraft.core.Registry; -import net.minecraft.core.RegistryAccess; +import net.minecraft.commands.arguments.EntityArgument; import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.component.DataComponentType; import net.minecraft.core.component.TypedDataComponent; -import net.minecraft.core.registries.Registries; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.NbtUtils; import net.minecraft.nbt.SnbtPrinterTagVisitor; import net.minecraft.nbt.Tag; import net.minecraft.resources.RegistryOps; import net.minecraft.world.item.ItemStack; -import org.bukkit.command.CommandSender; -import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftRegistry; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.entity.Player; import org.jspecify.annotations.NullMarked; +import static java.util.Objects.requireNonNull; import static net.kyori.adventure.text.Component.join; import static net.kyori.adventure.text.Component.text; import static net.kyori.adventure.text.Component.textOfChildren; @@ -54,29 +53,31 @@ public final class DumpItemCommand { public static LiteralArgumentBuilder create() { return Commands.literal("dumpitem") - .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "dumpitem")) - + .requires(source -> { + return source.getSender().hasPermission(PaperCommand.BASE_PERM + "dumpitem"); + }) .then(Commands.literal("all") - .executes(ctx -> { - doDumpItem(ctx.getSource().getSender(), true); + .executes(context -> { + if (!(context.getSource().getExecutor() instanceof Player player)) { + throw EntityArgument.NO_PLAYERS_FOUND.create(); + } + doDumpItem(player, true); return Command.SINGLE_SUCCESS; }) ) - - .executes(ctx -> { - doDumpItem(ctx.getSource().getSender(), false); + .executes(context -> { + if (!(context.getSource().getExecutor() instanceof Player player)) { + throw EntityArgument.NO_PLAYERS_FOUND.create(); + } + doDumpItem(player, false); return Command.SINGLE_SUCCESS; }); } @SuppressWarnings({"unchecked", "OptionalAssignedToNull", "rawtypes"}) - private static void doDumpItem(final CommandSender sender, final boolean includeAllComponents) { - if (!(sender instanceof final Player player)) { - sender.sendMessage("Only players can use this command"); - return; - } + private static void doDumpItem(final Player player, final boolean includeAllComponents) { final ItemStack itemStack = CraftItemStack.asNMSCopy(player.getInventory().getItemInMainHand()); - final TextComponent.Builder visualOutput = Component.text(); + final TextComponent.Builder visualOutput = text(); final StringBuilder itemCommandBuilder = new StringBuilder(); final String itemName = itemStack.getItemHolder().unwrapKey().orElseThrow().identifier().toString(); itemCommandBuilder.append(itemName); @@ -89,13 +90,11 @@ private static void doDumpItem(final CommandSender sender, final boolean include referencedComponentTypes.addAll(prototype.keySet()); } - final RegistryAccess.Frozen access = ((CraftServer) sender.getServer()).getServer().registryAccess(); - final RegistryOps ops = access.createSerializationContext(NbtOps.INSTANCE); - final Registry> registry = access.lookupOrThrow(Registries.DATA_COMPONENT_TYPE); + final RegistryOps ops = CraftRegistry.getMinecraftRegistry().createSerializationContext(NbtOps.INSTANCE); final List componentComponents = new ArrayList<>(); final List commandComponents = new ArrayList<>(); for (final DataComponentType type : referencedComponentTypes) { - final String path = registry.getResourceKey(type).orElseThrow().identifier().getPath(); + final String path = requireNonNull(BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(type)).getPath(); final Optional patchedValue = patch.get(type); final TypedDataComponent prototypeValue = prototype.getTyped(type); if (patchedValue != null) { @@ -124,7 +123,7 @@ private static void doDumpItem(final CommandSender sender, final boolean include } player.sendMessage(visualOutput.build().compact()); final Component copyMsg = text("Click to copy item definition to clipboard for use with /give", GRAY, ITALIC); - sender.sendMessage(copyMsg.clickEvent(copyToClipboard(itemCommandBuilder.toString()))); + player.sendMessage(copyMsg.clickEvent(copyToClipboard(itemCommandBuilder.toString()))); } private static void writeComponentValue(final Consumer visualOutput, final Consumer commandOutput, final String path, final Tag serialized) { diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java index 2c963242bb53..476ff9fdb34a 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java @@ -17,12 +17,11 @@ import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.Collections; +import java.util.Locale; import java.util.Set; import java.util.concurrent.CompletableFuture; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.event.ClickEvent; import net.minecraft.server.MinecraftServer; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; @@ -31,29 +30,25 @@ import org.bukkit.plugin.RegisteredListener; import org.jspecify.annotations.NullMarked; -import static net.kyori.adventure.text.Component.newline; -import static net.kyori.adventure.text.Component.space; 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; -import static net.kyori.adventure.text.format.NamedTextColor.WHITE; @NullMarked public final class DumpListenersCommand { - private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"); private static final String COMMAND_ARGUMENT_TO_FILE = "tofile"; - private static final MethodHandle EVENT_TYPES_HANDLE; - private static final Component HELP_MESSAGE = text("Usage: /paper dumplisteners " + COMMAND_ARGUMENT_TO_FILE + "|", RED); - private static final SuggestionProvider EVENT_SUGGESTIONS = (ctx, builder) -> CompletableFuture.supplyAsync(() -> { + + private static final SuggestionProvider EVENT_SUGGESTIONS = (context, builder) -> CompletableFuture.supplyAsync(() -> { eventClassNames().stream() - .filter(name -> name.toLowerCase().startsWith(builder.getRemainingLowerCase())) + .filter(name -> name.toLowerCase(Locale.ENGLISH).startsWith(builder.getRemainingLowerCase())) .forEach(builder::suggest); return builder.build(); }); + private static final MethodHandle EVENT_TYPES_HANDLE; static { try { final Field eventTypesField = HandlerList.class.getDeclaredField("EVENT_TYPES"); @@ -66,44 +61,34 @@ public final class DumpListenersCommand { public static LiteralArgumentBuilder create() { return Commands.literal("dumplisteners") - .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "dumplisteners")) - - .then(Commands.literal("tofile") - .executes(ctx -> { - dumpToFile(ctx.getSource().getSender()); - return Command.SINGLE_SUCCESS; + .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + "dumplisteners")) + .then(Commands.literal(COMMAND_ARGUMENT_TO_FILE) + .executes(context -> { + return dumpToFile(context.getSource().getSender()); }) ) - .then(Commands.argument("event", StringArgumentType.word()) .suggests(EVENT_SUGGESTIONS) - .executes(ctx -> { - doDumpListeners(ctx.getSource().getSender(), StringArgumentType.getString(ctx, "event")); + .executes(context -> { + doDumpListeners(context.getSource().getSender(), StringArgumentType.getString(context, "event")); return Command.SINGLE_SUCCESS; }) ) - - .executes(ctx -> { - ctx.getSource().getSender().sendMessage(HELP_MESSAGE); + .executes(context -> { + context.getSource().getSender().sendMessage(HELP_MESSAGE); return Command.SINGLE_SUCCESS; }); } - private static void dumpToFile(final CommandSender sender) { - Path parent = Path.of("debug"); - Path path = parent.resolve("listeners-" + FORMATTER.format(LocalDateTime.now()) + ".txt"); + private static int dumpToFile(final CommandSender sender) { + Path path = Path.of("debug", "listeners-" + PaperCommand.FILENAME_DATE_TIME_FORMATTER.format(LocalDateTime.now()) + ".txt"); sender.sendMessage( - text("Writing listeners into directory", GREEN) + text("Writing listeners into", GREEN) .appendSpace() - .append( - text(parent.toString(), WHITE) - .hoverEvent(text("Click to copy the full path of debug directory", WHITE)) - .clickEvent(ClickEvent.copyToClipboard(parent.toAbsolutePath().toString())) - ) + .append(PaperCommand.asFriendlyPath(path)) ); try { - Files.createDirectories(parent); - Files.createFile(path); + Files.createDirectories(path.getParent()); try (final PrintWriter writer = new PrintWriter(path.toFile())) { for (final String eventClass : eventClassNames()) { final HandlerList handlers; @@ -120,49 +105,43 @@ private static void dumpToFile(final CommandSender sender) { } } } + sender.sendMessage(text("Successfully written listeners", GREEN)); + return Command.SINGLE_SUCCESS; } catch (final IOException ex) { sender.sendMessage(text("Failed to write dumped listener! See the console for more info.", RED)); MinecraftServer.LOGGER.warn("Error occurred while dumping listeners", ex); - return; + return 0; } - sender.sendMessage( - text("Successfully written listeners into", GREEN) - .appendSpace() - .append( - text(path.toString(), WHITE) - .hoverEvent(text("Click to copy the full path of the file", WHITE)) - .clickEvent(ClickEvent.copyToClipboard(path.toAbsolutePath().toString())) - ) - ); } - private static void doDumpListeners(final CommandSender sender, final String className) { + private static int doDumpListeners(final CommandSender sender, final String className) { try { final HandlerList handlers = (HandlerList) findClass(className).getMethod("getHandlerList").invoke(null); if (handlers.getRegisteredListeners().length == 0) { - sender.sendMessage(text(className + " does not have any registered listeners.")); - return; + sender.sendPlainMessage(className + " does not have any registered listeners."); + return 0; } - sender.sendMessage(text("Listeners for " + className + ":")); + sender.sendPlainMessage("Listeners for " + className + ":"); for (final RegisteredListener listener : handlers.getRegisteredListeners()) { - final Component hoverText = text("Priority: " + listener.getPriority().name() + " (" + listener.getPriority().getSlot() + ")", WHITE) - .append(newline()) + final Component hoverText = text("Priority: " + listener.getPriority().name() + " (" + listener.getPriority().getSlot() + ")") + .appendNewline() .append(text("Listener: " + listener.getListener())) - .append(newline()) + .appendNewline() .append(text("Executor: " + listener.getExecutor())) - .append(newline()) + .appendNewline() .append(text("Ignoring cancelled: " + listener.isIgnoringCancelled())); sender.sendMessage(text(listener.getPlugin().getName(), GREEN) - .append(space()) + .appendSpace() .append(text("(" + listener.getListener().getClass().getName() + ")", GRAY).hoverEvent(hoverText))); } - sender.sendMessage(text("Total listeners: " + handlers.getRegisteredListeners().length)); - + int size = handlers.getRegisteredListeners().length; + sender.sendPlainMessage("Total listeners: " + size); + return size; } catch (final ClassNotFoundException e) { sender.sendMessage(text("Unable to find a class named '" + className + "'. Make sure to use the fully qualified name.", RED)); } catch (final NoSuchMethodException e) { @@ -171,6 +150,7 @@ private static void doDumpListeners(final CommandSender sender, final String cla sender.sendMessage(text("Something went wrong, see the console for more details.", RED)); MinecraftServer.LOGGER.warn("Error occurred while dumping listeners for class {}", className, e); } + return 0; } @SuppressWarnings("unchecked") diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java index 6071d1e88afb..6ae5fe875a55 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java @@ -37,11 +37,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.Map; -import net.kyori.adventure.text.event.ClickEvent; import net.minecraft.server.MinecraftServer; import org.bukkit.command.CommandSender; import org.bukkit.plugin.Plugin; @@ -50,40 +48,29 @@ 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.WHITE; @NullMarked public final class DumpPluginsCommand { - private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"); - public static LiteralArgumentBuilder create() { - DumpPluginsCommand instance = new DumpPluginsCommand(); return Commands.literal("dumpplugins") - .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "dumpplugins")) - .executes(ctx -> { - instance.dumpPlugins(ctx.getSource().getSender()); - return Command.SINGLE_SUCCESS; + .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + "dumpplugins")) + .executes(context -> { + return dumpPlugins(context.getSource().getSender()); }); } - private void dumpPlugins(final CommandSender sender) { - Path parent = Path.of("debug"); - Path path = parent.resolve("plugin-info-" + FORMATTER.format(LocalDateTime.now()) + ".json"); + private static int dumpPlugins(final CommandSender sender) { + Path path = Path.of("debug", "plugin-info-" + PaperCommand.FILENAME_DATE_TIME_FORMATTER.format(LocalDateTime.now()) + ".json"); try { - Files.createDirectories(parent); - Files.createFile(path); + Files.createDirectories(path.getParent()); sender.sendMessage( - text("Writing plugin information into directory", GREEN) + text("Writing plugin debug information into", GREEN) .appendSpace() - .append( - text(parent.toString(), WHITE) - .hoverEvent(text("Click to copy the full path of debug directory", WHITE)) - .clickEvent(ClickEvent.copyToClipboard(parent.toAbsolutePath().toString())) - ) + .append(PaperCommand.asFriendlyPath(path)) ); - final JsonObject data = this.writeDebug(); + final JsonObject data = writeDebug(); StringWriter stringWriter = new StringWriter(); JsonWriter jsonWriter = new JsonWriter(stringWriter); @@ -94,36 +81,30 @@ private void dumpPlugins(final CommandSender sender) { try (PrintStream out = new PrintStream(Files.newOutputStream(path), false, StandardCharsets.UTF_8)) { out.print(stringWriter); } - sender.sendMessage( - text("Successfully written plugin debug information into", GREEN) - .appendSpace() - .append( - text(path.toString(), WHITE) - .hoverEvent(text("Click to copy the full path of the file", WHITE)) - .clickEvent(ClickEvent.copyToClipboard(path.toAbsolutePath().toString())) - ) - ); - } catch (Throwable e) { + sender.sendMessage(text("Successfully written plugin debug information", GREEN)); + return Command.SINGLE_SUCCESS; + } catch (Throwable thr) { sender.sendMessage(text("Failed to write plugin information! See the console for more info.", RED)); - MinecraftServer.LOGGER.warn("Error occurred while dumping plugin info", e); + MinecraftServer.LOGGER.warn("Error occurred while dumping plugin info", thr); + return 0; } } - private JsonObject writeDebug() { + private static JsonObject writeDebug() { JsonObject root = new JsonObject(); if (ConfiguredProviderStorage.LEGACY_PLUGIN_LOADING) { root.addProperty("legacy-loading-strategy", true); } - this.writeProviders(root); - this.writePlugins(root); - this.writeClassloaders(root); + writeProviders(root); + writePlugins(root); + writeClassloaders(root); return root; } @SuppressWarnings("unchecked") - private void writeProviders(JsonObject root) { + private static void writeProviders(JsonObject root) { JsonObject rootProviders = new JsonObject(); root.add("providers", rootProviders); @@ -172,7 +153,7 @@ public boolean preloadProvider(PluginProvider provider) { } } - private void writePlugins(JsonObject root) { + private static void writePlugins(JsonObject root) { JsonArray rootPlugins = new JsonArray(); root.add("plugins", rootPlugins); @@ -181,7 +162,7 @@ private void writePlugins(JsonObject root) { } } - private void writeClassloaders(JsonObject root) { + private static void writeClassloaders(JsonObject root) { JsonObject classLoadersRoot = new JsonObject(); root.add("classloaders", classLoadersRoot); @@ -192,11 +173,11 @@ private void writeClassloaders(JsonObject root) { JsonArray array = new JsonArray(); classLoadersRoot.add("children", array); for (PluginClassLoaderGroup group : storage.getGroups()) { - array.add(this.writeClassloader(group)); + array.add(writeClassloader(group)); } } - private JsonObject writeClassloader(PluginClassLoaderGroup group) { + private static JsonObject writeClassloader(PluginClassLoaderGroup group) { JsonObject classLoadersRoot = new JsonObject(); if (group instanceof SimpleListPluginClassLoaderGroup listGroup) { JsonArray array = new JsonArray(); @@ -209,12 +190,12 @@ private JsonObject writeClassloader(PluginClassLoaderGroup group) { classLoadersRoot.add("children", array); for (ConfiguredPluginClassLoader innerGroup : listGroup.getClassLoaders()) { - array.add(this.writeClassloader(innerGroup)); + array.add(writeClassloader(innerGroup)); } } else if (group instanceof LockingClassLoaderGroup locking) { // Unwrap - return this.writeClassloader(locking.getParent()); + return writeClassloader(locking.getParent()); } else { classLoadersRoot.addProperty("raw", group.toString()); } @@ -222,7 +203,7 @@ private JsonObject writeClassloader(PluginClassLoaderGroup group) { return classLoadersRoot; } - private JsonElement writeClassloader(ConfiguredPluginClassLoader innerGroup) { + private static JsonElement writeClassloader(ConfiguredPluginClassLoader innerGroup) { return new JsonPrimitive(innerGroup.toString()); } } diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java index 9ce06db6511e..dafad2e6ed50 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java @@ -1,14 +1,14 @@ package io.papermc.paper.command.subcommands; -import com.google.common.base.Functions; -import com.google.common.collect.Maps; import com.mojang.brigadier.Command; -import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import io.papermc.paper.command.PaperCommand; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import io.papermc.paper.command.brigadier.argument.RegistryArgumentExtractor; +import io.papermc.paper.registry.RegistryKey; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -42,61 +42,78 @@ public final class EntityCommand { public static LiteralArgumentBuilder create() { return Commands.literal("entity") - .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "entity")) - + .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + "entity")) .then(Commands.literal("list") .then(Commands.literal("help") - .executes(ctx -> { - ctx.getSource().getSender().sendMessage(LIST_HELP_MESSAGE); + .executes(context -> { + context.getSource().getSender().sendMessage(LIST_HELP_MESSAGE); return Command.SINGLE_SUCCESS; }) ) - + /* .then(Commands.argument("filter", StringArgumentType.string()) - .suggests((ctx, builder) -> { + .suggests((context, builder) -> { BuiltInRegistries.ENTITY_TYPE.keySet().stream() - .map(Functions.toStringFunction()) + .map(Object::toString) .filter(type -> type.startsWith(builder.getRemainingLowerCase())) .forEach(builder::suggest); return builder.buildFuture(); }) - - .executes(ctx -> { - listEntities( - ctx.getSource().getSender(), - StringArgumentType.getString(ctx, "filter"), - ctx.getSource().getLocation().getWorld() + .executes(context -> { + return listEntities( + context.getSource().getSender(), + StringArgumentType.getString(context, "filter"), + context.getSource().getLocation().getWorld() + ); + }) + .then(Commands.argument("world", ArgumentTypes.world()) + .executes(context -> { + return listEntities( + context.getSource().getSender(), + StringArgumentType.getString(context, "filter"), + context.getArgument("world", World.class) + ); + }) + ) + )*/ + // string argument is super strict for some reason for unquoted stuff this help mitigate it (ideally should allow entity type tags too then the regex can be dropped I think) + .then(Commands.argument("entity-type", ArgumentTypes.resourceKey(RegistryKey.ENTITY_TYPE)) + .suggests((context, builder) -> { + BuiltInRegistries.ENTITY_TYPE.keySet().stream() + .map(Object::toString) + .filter(type -> type.startsWith(builder.getRemainingLowerCase())) + .forEach(builder::suggest); + return builder.buildFuture(); + }) + .executes(context -> { + return listEntities( + context.getSource().getSender(), + RegistryArgumentExtractor.getTypedKey(context, RegistryKey.ENTITY_TYPE, "entity-type").asString(), + context.getSource().getLocation().getWorld() ); - return Command.SINGLE_SUCCESS; }) - .then(Commands.argument("world", ArgumentTypes.world()) - .executes(ctx -> { - listEntities( - ctx.getSource().getSender(), - StringArgumentType.getString(ctx, "filter"), - ctx.getArgument("world", World.class) + .executes(context -> { + return listEntities( + context.getSource().getSender(), + RegistryArgumentExtractor.getTypedKey(context, RegistryKey.ENTITY_TYPE, "entity-type").asString(), + context.getArgument("world", World.class) ); - return Command.SINGLE_SUCCESS; }) ) ) - - .executes(ctx -> { - listEntities(ctx.getSource().getSender(), "*", ctx.getSource().getLocation().getWorld()); - return Command.SINGLE_SUCCESS; + .executes(context -> { + return listEntities(context.getSource().getSender(), "*", context.getSource().getLocation().getWorld()); }) ) - .then(Commands.literal("help") - .executes(ctx -> { - ctx.getSource().getSender().sendMessage(HELP_MESSAGE); + .executes(context -> { + context.getSource().getSender().sendMessage(HELP_MESSAGE); return Command.SINGLE_SUCCESS; }) ) - - .executes(ctx -> { - ctx.getSource().getSender().sendMessage(HELP_MESSAGE); + .executes(context -> { + context.getSource().getSender().sendMessage(HELP_MESSAGE); return Command.SINGLE_SUCCESS; }); } @@ -104,7 +121,7 @@ public static LiteralArgumentBuilder create() { /* * Ported from MinecraftForge - author: LexManos - License: LGPLv2.1 */ - private static void listEntities(final CommandSender sender, final String filter, final World bukkitWorld) { + private static int listEntities(final CommandSender sender, final String filter, final World bukkitWorld) { final String cleanfilter = filter.replace("?", ".?").replace("*", ".*?"); Set names = BuiltInRegistries.ENTITY_TYPE.keySet().stream() .filter(n -> n.toString().matches(cleanfilter)) @@ -112,17 +129,17 @@ private static void listEntities(final CommandSender sender, final String filter 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; + return 0; } - Map>> list = Maps.newHashMap(); + Map>> list = new HashMap<>(); ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); - Map nonEntityTicking = Maps.newHashMap(); + Map nonEntityTicking = new HashMap<>(); ServerChunkCache chunkProviderServer = world.getChunkSource(); world.getAllEntities().forEach(e -> { Identifier key = EntityType.getKey(e.getType()); - MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap())); + MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, new HashMap<>())); ChunkPos chunk = e.chunkPosition(); info.left++; info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); @@ -136,19 +153,21 @@ private static void listEntities(final CommandSender sender, final String filter int nonTicking = nonEntityTicking.getOrDefault(name, 0); if (info == null) { sender.sendMessage(text("No entities found.", RED)); - return; + return Command.SINGLE_SUCCESS; } sender.sendMessage("Entity: " + name + " Total Ticking: " + (info.getLeft() - nonTicking) + ", Total Non-Ticking: " + nonTicking); - info.getRight().entrySet().stream() + List> entitiesPerChunk = 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 -> { + .limit(10).toList(); + entitiesPerChunk.forEach(e -> { final int x = (e.getKey().x << 4) + 8; final int z = (e.getKey().z << 4) + 8; final Component message = text(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z + (chunkProviderServer.isPositionTicking(e.getKey().toLong()) ? " (Ticking)" : " (Non-Ticking)")) .hoverEvent(HoverEvent.showText(text("Click to teleport to chunk", GREEN))) - .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/minecraft:execute as @s in " + world.getWorld().getKey() + " run tp " + x + " " + (world.getWorld().getHighestBlockYAt(x, z, HeightMap.MOTION_BLOCKING) + 1) + " " + z)); + .clickEvent(ClickEvent.runCommand("/minecraft:execute in " + world.getWorld().getKey() + " run tp " + x + " " + (world.getWorld().getHighestBlockYAt(x, z, HeightMap.MOTION_BLOCKING) + 1) + " " + z)); sender.sendMessage(message); }); + return entitiesPerChunk.size(); } else { List> info = list.entrySet().stream() .filter(e -> names.contains(e.getKey())) @@ -158,7 +177,7 @@ private static void listEntities(final CommandSender sender, final String filter if (info.isEmpty()) { sender.sendMessage(text("No entities found.", RED)); - return; + return Command.SINGLE_SUCCESS; } int count = info.stream().mapToInt(Pair::getRight).sum(); @@ -169,6 +188,7 @@ private static void listEntities(final CommandSender sender, final String filter sender.sendMessage(" " + (e.getValue() - nonTicking) + " (" + nonTicking + ") " + ": " + e.getKey()); }); sender.sendMessage("* First number is ticking entities, second number is non-ticking entities"); + return info.size(); } } } diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java index 27db29353a50..4cf698c8166f 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java @@ -1,12 +1,12 @@ package io.papermc.paper.command.subcommands; +import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import io.papermc.paper.command.PaperCommand; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; +import java.nio.file.Path; 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.jspecify.annotations.NullMarked; @@ -21,24 +21,29 @@ public final class HeapDumpCommand { public static LiteralArgumentBuilder create() { return Commands.literal("heap") - .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + ".heap")) - .executes(ctx -> { - dumpHeap(ctx.getSource().getSender()); - return com.mojang.brigadier.Command.SINGLE_SUCCESS; + .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + ".heap")) + .executes(context -> { + return dumpHeap(context.getSource().getSender()); }); } - private static 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()); + private static int dumpHeap(final CommandSender sender) { + Path dir = Path.of("dumps"); + String name = "heap-dump-" + PaperCommand.FILENAME_DATE_TIME_FORMATTER.format(LocalDateTime.now()); - Command.broadcastCommandMessage(sender, text("Writing JVM heap data...", YELLOW)); + sender.sendMessage(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)); + Path path = CraftServer.dumpHeap(dir, name); + if (path != null) { + sender.sendMessage( + text("Heap dump saved to", GREEN) + .appendSpace() + .append(PaperCommand.asFriendlyPath(path)) + ); + return Command.SINGLE_SUCCESS; } else { - Command.broadcastCommandMessage(sender, text("Failed to write heap dump, see server log for details", RED)); + sender.sendMessage(text("Failed to write heap dump, see server log for details", RED)); + return 0; } } } diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java index e59af3fa9ac2..66589f71f4ee 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java @@ -1,17 +1,14 @@ package io.papermc.paper.command.subcommands; -import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; import com.mojang.brigadier.Command; -import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; -import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import io.papermc.paper.command.PaperCommand; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; -import io.papermc.paper.command.brigadier.MessageComponentSerializer; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver; +import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.function.ToIntFunction; @@ -21,9 +18,11 @@ import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextColor; +import net.minecraft.commands.arguments.EntityArgument; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Util; import net.minecraft.world.entity.MobCategory; import net.minecraft.world.level.NaturalSpawner; import org.bukkit.Bukkit; @@ -37,122 +36,83 @@ @NullMarked public final class MobcapsCommand { - static final Map MOB_CATEGORY_COLORS = ImmutableMap.builder() - .put(MobCategory.MONSTER, NamedTextColor.RED) - .put(MobCategory.CREATURE, NamedTextColor.GREEN) - .put(MobCategory.AMBIENT, NamedTextColor.GRAY) - .put(MobCategory.AXOLOTLS, TextColor.color(0x7324FF)) - .put(MobCategory.UNDERGROUND_WATER_CREATURE, TextColor.color(0x3541E6)) - .put(MobCategory.WATER_CREATURE, TextColor.color(0x006EFF)) - .put(MobCategory.WATER_AMBIENT, TextColor.color(0x00B3FF)) - .put(MobCategory.MISC, TextColor.color(0x636363)) - .build(); - - private static final SimpleCommandExceptionType REQUIRES_PLAYER = new SimpleCommandExceptionType(MessageComponentSerializer.message().serialize( - Component.text("Must specify a player! ex: '/paper playermobcount playerName'") - )); - private static final DynamicCommandExceptionType NO_SUCH_WORLD_ERROR = new DynamicCommandExceptionType(obj -> MessageComponentSerializer.message().serialize( - Component.text("'" + obj + "' is not a valid world!") - )); + static final Map MOB_CATEGORY_COLORS = Maps.immutableEnumMap(Util.make(new EnumMap<>(MobCategory.class), map -> { + map.put(MobCategory.MONSTER, NamedTextColor.RED); + map.put(MobCategory.CREATURE, NamedTextColor.GREEN); + map.put(MobCategory.AMBIENT, NamedTextColor.GRAY); + map.put(MobCategory.AXOLOTLS, TextColor.color(0x7324FF)); + map.put(MobCategory.UNDERGROUND_WATER_CREATURE, TextColor.color(0x3541E6)); + map.put(MobCategory.WATER_CREATURE, TextColor.color(0x006EFF)); + map.put(MobCategory.WATER_AMBIENT, TextColor.color(0x00B3FF)); + map.put(MobCategory.MISC, TextColor.color(0x636363)); + })); public static LiteralArgumentBuilder createGlobal() { return Commands.literal("mobcaps") - .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "mobcaps")) - + .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + "mobcaps")) .then(Commands.literal("*") - .executes(ctx -> { - printMobcaps(ctx.getSource().getSender(), Bukkit.getWorlds()); - return Command.SINGLE_SUCCESS; + .executes(context -> { + return printMobcaps(context.getSource().getSender(), Bukkit.getWorlds()); }) ) - .then(Commands.argument("world", ArgumentTypes.world()) - .executes(ctx -> { - printMobcaps(ctx.getSource().getSender(), List.of(ctx.getArgument("world", World.class))); - return Command.SINGLE_SUCCESS; - }) - ) - - .then(Commands.argument("world_name", StringArgumentType.string()) - .executes(ctx -> { - String worldName = StringArgumentType.getString(ctx, "world_name"); - World world = Bukkit.getWorld(worldName); - - if (world == null) { - throw NO_SUCH_WORLD_ERROR.create(worldName); - } - - printMobcaps(ctx.getSource().getSender(), List.of(world)); - return Command.SINGLE_SUCCESS; + .executes(context -> { + return printMobcaps(context.getSource().getSender(), List.of(context.getArgument("world", World.class))); }) ) - - .executes(ctx -> { - printMobcaps(ctx.getSource().getSender(), List.of(ctx.getSource().getLocation().getWorld())); - return Command.SINGLE_SUCCESS; + .executes(context -> { + return printMobcaps(context.getSource().getSender(), List.of(context.getSource().getLocation().getWorld())); }); } public static LiteralArgumentBuilder createPlayer() { return Commands.literal("playermobcaps") - .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "mobcaps")) - - .then(Commands.argument("players", ArgumentTypes.player()) - .executes(ctx -> { - ctx.getArgument("players", PlayerSelectorArgumentResolver.class).resolve(ctx.getSource()).forEach( - player -> printPlayerMobcaps(ctx.getSource().getSender(), player) + .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + "playermobcaps")) + .then(Commands.argument("player", ArgumentTypes.player()) + .executes(context -> { + return printPlayerMobcaps( + context.getSource().getSender(), + context.getArgument("player", PlayerSelectorArgumentResolver.class).resolve(context.getSource()).getFirst() ); - return Command.SINGLE_SUCCESS; }) ) - - .executes(ctx -> { - if (ctx.getSource().getExecutor() instanceof Player player) { - printPlayerMobcaps(ctx.getSource().getSender(), player); - return Command.SINGLE_SUCCESS; + .executes(context -> { + if (!(context.getSource().getExecutor() instanceof Player player)) { + throw EntityArgument.NO_PLAYERS_FOUND.create(); } - - throw REQUIRES_PLAYER.create(); + return printPlayerMobcaps(context.getSource().getSender(), player); }); } - private static void printMobcaps(final CommandSender sender, final List worlds) { + private static int printMobcaps(final CommandSender sender, final List worlds) { for (final World world : worlds) { final ServerLevel level = ((CraftWorld) world).getHandle(); final NaturalSpawner.SpawnState state = level.getChunkSource().getLastSpawnState(); - final int chunks; - if (state == null) { - chunks = 0; - } else { - chunks = state.getSpawnableChunkCount(); - } + final int chunks = state == null ? 0 : state.getSpawnableChunkCount(); sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), Component.text("Mobcaps for world: "), - Component.text(world.getName(), NamedTextColor.AQUA), + Component.text(world.key().asString(), NamedTextColor.AQUA), Component.text(" (" + chunks + " spawnable chunks)") )); sender.sendMessage(createMobcapsComponent( category -> { - if (state == null) { - return 0; - } else { - return state.getMobCategoryCounts().getOrDefault(category, 0); - } + return state == null ? 0 : state.getMobCategoryCounts().getOrDefault(category, 0); }, category -> NaturalSpawner.globalLimitForCategory(level, category, chunks) )); } + return worlds.size(); } - private static void printPlayerMobcaps(final CommandSender sender, final Player player) { + private static int printPlayerMobcaps(final CommandSender sender, final Player player) { final ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); final ServerLevel level = serverPlayer.level(); if (!level.paperConfig().entities.spawning.perPlayerMobSpawns) { sender.sendMessage(Component.text("Use '/paper mobcaps' for worlds where per-player mob spawning is disabled.", NamedTextColor.RED)); - return; + return 0; } sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), Component.text("Mobcaps for player: "), Component.text(player.getName(), NamedTextColor.GREEN))); @@ -160,6 +120,7 @@ private static void printPlayerMobcaps(final CommandSender sender, final Player category -> level.chunkSource.chunkMap.getMobCountNear(serverPlayer, category), category -> level.getWorld().getSpawnLimitUnsafe(org.bukkit.craftbukkit.util.CraftSpawnCategory.toBukkit(category)) )); + return Command.SINGLE_SUCCESS; } private static Component createMobcapsComponent(final ToIntFunction countGetter, final ToIntFunction limitGetter) { diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java index 7bc4fcf81a47..f02f8770d77f 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java @@ -1,39 +1,39 @@ package io.papermc.paper.command.subcommands; +import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import io.papermc.paper.command.PaperCommand; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import net.minecraft.server.MinecraftServer; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; -import org.bukkit.craftbukkit.CraftServer; import org.jspecify.annotations.NullMarked; 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 org.bukkit.command.Command.broadcastCommandMessage; @NullMarked public final class ReloadCommand { public static LiteralArgumentBuilder create() { return Commands.literal("reload") - .requires(stack -> stack.getSender().hasPermission(PaperCommand.BASE_PERM + "reload")) - .executes(ctx -> { - doReload(ctx.getSource().getSender()); - return com.mojang.brigadier.Command.SINGLE_SUCCESS; + .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + "reload")) + .executes(context -> { + doReload(context.getSource().getSender()); + return Command.SINGLE_SUCCESS; }); } private static 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)); + broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", RED)); + broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", RED)); - MinecraftServer server = ((CraftServer) sender.getServer()).getServer(); + MinecraftServer server = MinecraftServer.getServer(); server.paperConfigurations.reloadConfigs(server); server.server.reloadCount++; - Command.broadcastCommandMessage(sender, text("Paper config reload complete.", GREEN)); + broadcastCommandMessage(sender, text("Paper config reload complete.", GREEN)); } } diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java index 3a4402ef5986..f9a340cb9828 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java @@ -2,6 +2,7 @@ import com.destroystokyo.paper.io.SyncLoadFinder; import com.google.gson.JsonObject; +import com.google.gson.Strictness; import com.google.gson.internal.Streams; import com.google.gson.stream.JsonWriter; import com.mojang.brigadier.Command; @@ -9,87 +10,69 @@ import io.papermc.paper.command.PaperCommand; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; -import java.io.File; -import java.io.FileOutputStream; import java.io.PrintStream; import java.io.StringWriter; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import net.kyori.adventure.text.event.ClickEvent; -import net.kyori.adventure.text.event.HoverEvent; import net.minecraft.server.MinecraftServer; import org.bukkit.command.CommandSender; import org.jspecify.annotations.NullMarked; 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.GOLD; 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.WHITE; @NullMarked public final class SyncLoadInfoCommand { - private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"); - public static LiteralArgumentBuilder create() { return Commands.literal("syncloadinfo") - .requires(stack -> SyncLoadFinder.ENABLED && stack.getSender().hasPermission(PaperCommand.BASE_PERM + "syncloadinfo")) + .requires(source -> SyncLoadFinder.ENABLED && source.getSender().hasPermission(PaperCommand.BASE_PERM + "syncloadinfo")) .then(Commands.literal("clear") - .executes(ctx -> { - doSyncLoadInfo(ctx.getSource().getSender(), true); + .executes(context -> { + SyncLoadFinder.clear(); + context.getSource().getSender().sendMessage(text("Sync load data cleared.", GOLD)); return Command.SINGLE_SUCCESS; }) ) - .executes(ctx -> { - doSyncLoadInfo(ctx.getSource().getSender(), false); - return Command.SINGLE_SUCCESS; + .executes(context -> { + return dumpSyncLoadInfo(context.getSource().getSender()); }); } - private static void doSyncLoadInfo(final CommandSender sender, final boolean clear) { - // This if-statement is technically not needed anymore, but in case somebody decided that it would - // be a wonderful idea to call this method using reflection, we keep it in. - if (!SyncLoadFinder.ENABLED) { - String systemFlag = "-Dpaper.debug-sync-loads=true"; - sender.sendMessage(text().color(RED).append(text("This command requires the server startup flag '")).append( - text(systemFlag, WHITE).clickEvent(ClickEvent.copyToClipboard(systemFlag)) - .hoverEvent(HoverEvent.showText(text("Click to copy the system flag")))).append( - text("' to be set."))); - return; - } - - if (clear) { - SyncLoadFinder.clear(); - sender.sendMessage(text("Sync load data cleared.", GRAY)); - return; - } - - File file = new File(new File(new File("."), "debug"), - "sync-load-info-" + FORMATTER.format(LocalDateTime.now()) + ".txt"); - file.getParentFile().mkdirs(); - sender.sendMessage(text("Writing sync load info to " + file, GREEN)); - + private static int dumpSyncLoadInfo(final CommandSender sender) { + final Path path = Path.of("debug", "sync-load-info-" + PaperCommand.FILENAME_DATE_TIME_FORMATTER.format(LocalDateTime.now()) + ".txt"); + sender.sendMessage( + text("Writing sync load info into", GREEN) + .appendSpace() + .append(PaperCommand.asFriendlyPath(path)) + ); try { + Files.createDirectories(path.getParent()); + final JsonObject data = SyncLoadFinder.serialize(); - StringWriter stringWriter = new StringWriter(); + final StringWriter stringWriter = new StringWriter(); JsonWriter jsonWriter = new JsonWriter(stringWriter); jsonWriter.setIndent(" "); - jsonWriter.setLenient(false); + jsonWriter.setStrictness(Strictness.STRICT); Streams.write(data, jsonWriter); try ( - PrintStream out = new PrintStream(new FileOutputStream(file), false, StandardCharsets.UTF_8) + final PrintStream out = new PrintStream(Files.newOutputStream(path), false, StandardCharsets.UTF_8) ) { out.print(stringWriter); } sender.sendMessage(text("Successfully written sync load information!", GREEN)); + return Command.SINGLE_SUCCESS; } catch (Throwable thr) { sender.sendMessage(text("Failed to write sync load information! See the console for more info.", RED)); MinecraftServer.LOGGER.warn("Error occurred while dumping sync chunk load info", thr); + return 0; } } } diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java index 23818616587d..cdf3760639c5 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java @@ -12,14 +12,14 @@ public final class VersionCommand { public static LiteralArgumentBuilder create(String permission, String name) { return Commands.literal(name) - .requires(stack -> stack.getSender().hasPermission(permission)) - .executes(ctx -> { + .requires(source -> source.getSender().hasPermission(permission)) + .executes(context -> { final org.bukkit.command.Command redirect = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); - if (redirect != null) { - redirect.execute(ctx.getSource().getSender(), "paper", new String[0]); + if (redirect != null && redirect.execute(context.getSource().getSender(), "paper", new String[0])) { + return Command.SINGLE_SUCCESS; } - return Command.SINGLE_SUCCESS; + return 0; }); } } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 970c4966423f..a26c66b3f25a 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -1027,7 +1027,7 @@ public void reload() { // Paper end this.reloadData(); org.spigotmc.SpigotConfig.registerCommands(); // Spigot - io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper + io.papermc.paper.command.PaperCommands.registerLegacyCommands(this.console); // Paper this.spark.registerCommandBeforePlugins(this); // Paper - spark this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); From ac3a7d62aa0563f8bc77adb63300fe2e319a17bd Mon Sep 17 00:00:00 2001 From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> Date: Sun, 1 Feb 2026 21:45:49 +0100 Subject: [PATCH 5/9] fix more permissions and move weird debug command --- paper-server/build.gradle.kts | 1 - .../0001-Moonrise-optimisation-patches.patch | 75 ++++++++----------- .../io/papermc/paper/command/CommandUtil.java | 69 ----------------- .../papermc/paper/command/PaperCommand.java | 23 ++++-- .../paper/command/PaperVersionCommand.java | 2 +- .../command/subcommands/DumpItemCommand.java | 4 +- .../subcommands/DumpListenersCommand.java | 6 +- .../subcommands/DumpPluginsCommand.java | 2 +- .../command/subcommands/EntityCommand.java | 11 +-- .../command/subcommands/HeapDumpCommand.java | 2 +- .../command/subcommands/MobcapsCommand.java | 46 ++++++------ .../command/subcommands/ReloadCommand.java | 2 +- .../subcommands/SyncLoadInfoCommand.java | 2 +- .../command/subcommands/VersionCommand.java | 5 +- 14 files changed, 85 insertions(+), 165 deletions(-) delete mode 100644 paper-server/src/main/java/io/papermc/paper/command/CommandUtil.java diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts index bf36a4a81aa3..dac35c177814 100644 --- a/paper-server/build.gradle.kts +++ b/paper-server/build.gradle.kts @@ -306,7 +306,6 @@ fun TaskContainer.registerRunTask( systemProperty("disable.watchdog", true) } systemProperty("io.papermc.paper.suppress.sout.nags", true) - systemProperty("paper.debug-sync-loads", true) val memoryGb = providers.gradleProperty("paper.runMemoryGb").getOrElse("2") minHeapSize = "${memoryGb}G" diff --git a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch index 14becddb01c6..a5dad389ba3e 100644 --- a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch @@ -22489,10 +22489,10 @@ index 6f9e2dd6de4ae89ef6f393882903b13c72899605..da1cec4991420a091a55270a49246d10 \ No newline at end of file diff --git a/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..7ce40cacc0ba87c68e6e149f2097854b2deab8ac +index 0000000000000000000000000000000000000000..76f16944f108e6b3fd9015da3b1f84e1517616a1 --- /dev/null +++ b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java -@@ -0,0 +1,256 @@ +@@ -0,0 +1,281 @@ +package io.papermc.paper.command.subcommands; + +import ca.spottedleaf.moonrise.common.util.JsonUtil; @@ -22507,6 +22507,7 @@ index 0000000000000000000000000000000000000000..7ce40cacc0ba87c68e6e149f2097854b +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import java.io.File; +import java.util.List; ++import java.util.concurrent.atomic.AtomicInteger; +import net.kyori.adventure.text.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; @@ -22517,6 +22518,7 @@ index 0000000000000000000000000000000000000000..7ce40cacc0ba87c68e6e149f2097854b +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.command.CommandSender; ++import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.craftbukkit.CraftWorld; +import org.jspecify.annotations.NullMarked; + @@ -22531,10 +22533,11 @@ index 0000000000000000000000000000000000000000..7ce40cacc0ba87c68e6e149f2097854b + + private static final Component DEBUG_HELP = text("Use /paper debug [chunks] help for more information on a specific command", RED); + private static final Component DEBUG_CHUNKS_HELP = text("Use /paper debug chunks to dump loaded chunk information to a file", RED); ++ private static final AtomicInteger ASYNC_DEBUG_CHUNKS_COUNT = new AtomicInteger(); + + public static LiteralArgumentBuilder createDebug() { + return Commands.literal("debug") -+ .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + "debug")) ++ .requires(PaperCommand.hasPermission("debug")) + .then(Commands.literal("help") + .executes(context -> { + context.getSource().getSender().sendMessage(DEBUG_HELP); @@ -22551,6 +22554,28 @@ index 0000000000000000000000000000000000000000..7ce40cacc0ba87c68e6e149f2097854b + .executes(context -> { + return debugDumpLoadedChunks(context.getSource().getSender()); + }) ++ .then(Commands.literal("--async") ++ .requires(source -> source.getSender() instanceof ConsoleCommandSender) ++ .executes(context -> { ++ context.getSource().getSender().sendPlainMessage("Scheduling async debug chunks"); ++ Runnable run = () -> { ++ MinecraftServer server = MinecraftServer.getServer(); ++ context.getSource().getSender().sendPlainMessage("Async debug chunks executing"); ++ ChunkTaskScheduler.dumpAllChunkLoadInfo(server, false); ++ File file = ChunkTaskScheduler.getChunkDebugFile(); ++ context.getSource().getSender().sendMessage(text("Writing chunk information dump to " + file, GREEN)); ++ try { ++ JsonUtil.writeJson(ChunkTaskScheduler.debugAllWorlds(server), file); ++ context.getSource().getSender().sendMessage(text("Successfully written chunk information!", GREEN)); ++ } catch (Throwable thr) { ++ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file {}", file.toString(), thr); ++ context.getSource().getSender().sendMessage(text("Failed to dump chunk information, see console", RED)); ++ } ++ }; ++ Thread.ofPlatform().name("Async debug thread #" + ASYNC_DEBUG_CHUNKS_COUNT.getAndIncrement()).daemon(true).start(run); ++ return Command.SINGLE_SUCCESS; ++ }) ++ ) + ) + .executes(context -> { + context.getSource().getSender().sendMessage(DEBUG_HELP); @@ -22560,7 +22585,7 @@ index 0000000000000000000000000000000000000000..7ce40cacc0ba87c68e6e149f2097854b + + private static LiteralArgumentBuilder createWorldInfo(String name, WorldInfoLogic info) { + return Commands.literal(name) -+ .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + name)) ++ .requires(PaperCommand.hasPermission(name)) + .then(Commands.argument("world", ArgumentTypes.world()) + .executes(context -> { + return info.doInfo(context.getSource().getSender(), List.of(context.getArgument("world", World.class))); @@ -22751,7 +22776,7 @@ index 0000000000000000000000000000000000000000..7ce40cacc0ba87c68e6e149f2097854b +} diff --git a/io/papermc/paper/command/subcommands/FixLightCommand.java b/io/papermc/paper/command/subcommands/FixLightCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..1da6b0e57d8b4577c0e352a4158ee511dcbec880 +index 0000000000000000000000000000000000000000..5f7a2cd7eadc8ef55496dbda76683bc13d5ffb79 --- /dev/null +++ b/io/papermc/paper/command/subcommands/FixLightCommand.java @@ -0,0 +1,103 @@ @@ -22793,7 +22818,7 @@ index 0000000000000000000000000000000000000000..1da6b0e57d8b4577c0e352a4158ee511 + + public static LiteralArgumentBuilder create() { + return Commands.literal("fixlight") -+ .requires(source -> source.getSender() instanceof Player player && player.hasPermission(PaperCommand.BASE_PERM + "fixlight")) ++ .requires(PaperCommand.hasPermission("fixlight").and(source -> source.getSender() instanceof Player)) + .then(Commands.argument("radius", IntegerArgumentType.integer(0, MAX_RADIUS)) + .executes(context -> { + doFixLight(context.getSource().getSender(), IntegerArgumentType.getInteger(context, "radius")); @@ -23284,44 +23309,6 @@ index cab1ab613dd081e9472de515a19e1b4738e4fba3..96f7f37c42cecea1d714f7b16276f430 // CraftBukkit start public boolean isDebugging() { -diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java -index aaa257057ddef4c370b23338512b5548263e808c..0cff8232e25e7801db22d6c47b32802dd28bd4a5 100644 ---- a/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/net/minecraft/server/dedicated/DedicatedServer.java -@@ -550,7 +550,33 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - this.handleConsoleInputs(); - } - -+ private static final java.util.concurrent.atomic.AtomicInteger ASYNC_DEBUG_CHUNKS_COUNT = new java.util.concurrent.atomic.AtomicInteger(); // Paper - rewrite chunk system -+ - public void handleConsoleInput(String msg, CommandSourceStack source) { -+ // Paper start - rewrite chunk system -+ if (msg.equalsIgnoreCase("paper debug chunks --async")) { -+ LOGGER.info("Scheduling async debug chunks"); -+ Runnable run = () -> { -+ LOGGER.info("Async debug chunks executing"); -+ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(this, false); -+ org.bukkit.command.CommandSender sender = MinecraftServer.getServer().console; -+ java.io.File file = ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.getChunkDebugFile(); -+ sender.sendMessage(net.kyori.adventure.text.Component.text("Writing chunk information dump to " + file, net.kyori.adventure.text.format.NamedTextColor.GREEN)); -+ try { -+ ca.spottedleaf.moonrise.common.util.JsonUtil.writeJson(ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.debugAllWorlds(this), file); -+ sender.sendMessage(net.kyori.adventure.text.Component.text("Successfully written chunk information!", net.kyori.adventure.text.format.NamedTextColor.GREEN)); -+ } catch (Throwable thr) { -+ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file {}", file.toString(), thr); -+ sender.sendMessage(net.kyori.adventure.text.Component.text("Failed to dump chunk information, see console", net.kyori.adventure.text.format.NamedTextColor.RED)); -+ } -+ }; -+ Thread t = new Thread(run); -+ t.setName("Async debug thread #" + ASYNC_DEBUG_CHUNKS_COUNT.getAndIncrement()); -+ t.setDaemon(true); -+ t.start(); -+ return; -+ } -+ // Paper end - rewrite chunk system - this.serverCommandQueue.add(new ConsoleInput(msg, source)); // Paper - Perf: use proper queue - } - diff --git a/net/minecraft/server/level/ChunkHolder.java b/net/minecraft/server/level/ChunkHolder.java index e8f81fa17a0470ae9bf3f506fc7fb10fb7810cc7..22123d8b0fd8741c6df4efaec56f3c694e0529a3 100644 --- a/net/minecraft/server/level/ChunkHolder.java diff --git a/paper-server/src/main/java/io/papermc/paper/command/CommandUtil.java b/paper-server/src/main/java/io/papermc/paper/command/CommandUtil.java deleted file mode 100644 index a9443f24971f..000000000000 --- a/paper-server/src/main/java/io/papermc/paper/command/CommandUtil.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.papermc.paper.command; - -import com.google.common.base.Functions; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import net.minecraft.resources.Identifier; -import org.bukkit.command.CommandSender; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.DefaultQualifier; - -@DefaultQualifier(NonNull.class) -public final class CommandUtil { - private CommandUtil() { - } - - // Code from Mojang - copyright them - public static List getListMatchingLast( - final CommandSender sender, - final String[] args, - final String... matches - ) { - return getListMatchingLast(sender, args, Arrays.asList(matches)); - } - - public static boolean matches(final String s, final String s1) { - return s1.regionMatches(true, 0, s, 0, s.length()); - } - - public static List getListMatchingLast( - final CommandSender sender, - final String[] strings, - final Collection collection - ) { - String last = strings[strings.length - 1]; - ArrayList results = Lists.newArrayList(); - - if (!collection.isEmpty()) { - Iterator iterator = Iterables.transform(collection, Functions.toStringFunction()).iterator(); - - while (iterator.hasNext()) { - String s1 = (String) iterator.next(); - - if (matches(last, s1) && (sender.hasPermission(PaperCommand.BASE_PERM + s1) || sender.hasPermission("bukkit.command.paper"))) { - results.add(s1); - } - } - - if (results.isEmpty()) { - iterator = collection.iterator(); - - while (iterator.hasNext()) { - Object object = iterator.next(); - - if (object instanceof Identifier && matches(last, ((Identifier) object).getPath())) { - results.add(String.valueOf(object)); - } - } - } - } - - return results; - } - // end copy stuff -} diff --git a/paper-server/src/main/java/io/papermc/paper/command/PaperCommand.java b/paper-server/src/main/java/io/papermc/paper/command/PaperCommand.java index 72553f5acfcd..46a5a01f5f9c 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/PaperCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/PaperCommand.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.minecraft.util.Util; @@ -44,14 +46,16 @@ public static Component asFriendlyPath(Path path) { .clickEvent(ClickEvent.copyToClipboard(path.toAbsolutePath().toString())); } - public static final String BASE_PERM = "bukkit.command.paper."; + public static final String BASE_PERM = "bukkit.command.paper"; static final String DESCRIPTION = "Paper related commands"; public static LiteralCommandNode create() { LiteralArgumentBuilder rootNode = Commands.literal("paper") - .requires(source -> source.getSender().hasPermission("bukkit.command.paper")) + .requires(source -> source.getSender().hasPermission(BASE_PERM) || SUBPERMISSIONS.stream().anyMatch(name -> source.getSender().hasPermission(name))) .executes(context -> { - context.getSource().getSender().sendPlainMessage("/paper [" + String.join(" | ", SUBCOMMANDS.keySet()) + "]"); + context.getSource().getSender().sendPlainMessage("/paper [" + SUBCOMMANDS.keySet().stream().filter( + name -> hasPermission(name).test(context.getSource())).collect(Collectors.joining(" | ") + ) + "]"); return Command.SINGLE_SUCCESS; }); @@ -59,10 +63,14 @@ public static LiteralCommandNode create() { return rootNode.build(); } + public static Predicate hasPermission(String name) { + return source -> source.getSender().hasPermission(BASE_PERM) || source.getSender().hasPermission(BASE_PERM + '.' + name); + } + public static void registerPermissions() { final List permissions = new ArrayList<>(); - permissions.add("bukkit.command.paper"); - permissions.addAll(SUBCOMMANDS.keySet().stream().map(s -> BASE_PERM + s).toList()); + permissions.add(BASE_PERM); + permissions.addAll(SUBCOMMANDS.keySet().stream().map(name -> BASE_PERM + + '.' + name).toList()); final PluginManager pluginManager = Bukkit.getServer().getPluginManager(); for (final String permission : permissions) { @@ -76,8 +84,8 @@ public static void registerPermissions() { map.put("entity", List.of(EntityCommand.create())); map.put("reload", List.of(ReloadCommand.create())); map.put("version", List.of( - VersionCommand.create(BASE_PERM + "version", "version"), - VersionCommand.create(BASE_PERM + "version", "ver")) + VersionCommand.create("version"), + VersionCommand.create("ver")) ); map.put("dumpplugins", List.of(DumpPluginsCommand.create())); map.put("syncloadinfo", List.of(SyncLoadInfoCommand.create())); @@ -87,4 +95,5 @@ public static void registerPermissions() { map.put("dumplisteners", List.of(DumpListenersCommand.create())); FeatureHooks.registerPaperCommands(map); }); + private static final List SUBPERMISSIONS = SUBCOMMANDS.keySet().stream().map(name -> BASE_PERM + '.' + name).toList(); } diff --git a/paper-server/src/main/java/io/papermc/paper/command/PaperVersionCommand.java b/paper-server/src/main/java/io/papermc/paper/command/PaperVersionCommand.java index bfacb856623b..a3908df843bf 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/PaperVersionCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/PaperVersionCommand.java @@ -84,7 +84,7 @@ private int pluginVersion(final CommandContext context) { private CompletableFuture suggestPlugins(final CommandContext context, final SuggestionsBuilder builder) { for (final Plugin plugin : Bukkit.getPluginManager().getPlugins()) { final String name = plugin.getName(); - if (StringUtil.startsWithIgnoreCase(name, builder.getRemainingLowerCase())) { + if (StringUtil.startsWithIgnoreCase(name, builder.getRemaining())) { builder.suggest(name); } } diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java index 4a772c552482..d44a621549f3 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java @@ -53,9 +53,7 @@ public final class DumpItemCommand { public static LiteralArgumentBuilder create() { return Commands.literal("dumpitem") - .requires(source -> { - return source.getSender().hasPermission(PaperCommand.BASE_PERM + "dumpitem"); - }) + .requires(PaperCommand.hasPermission("dumpitem")) .then(Commands.literal("all") .executes(context -> { if (!(context.getSource().getExecutor() instanceof Player player)) { diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java index 476ff9fdb34a..3e5bda7b2110 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java @@ -18,7 +18,6 @@ import java.nio.file.Path; import java.time.LocalDateTime; import java.util.Collections; -import java.util.Locale; import java.util.Set; import java.util.concurrent.CompletableFuture; import net.kyori.adventure.text.Component; @@ -28,6 +27,7 @@ import org.bukkit.event.HandlerList; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.RegisteredListener; +import org.bukkit.util.StringUtil; import org.jspecify.annotations.NullMarked; import static net.kyori.adventure.text.Component.text; @@ -43,7 +43,7 @@ public final class DumpListenersCommand { private static final SuggestionProvider EVENT_SUGGESTIONS = (context, builder) -> CompletableFuture.supplyAsync(() -> { eventClassNames().stream() - .filter(name -> name.toLowerCase(Locale.ENGLISH).startsWith(builder.getRemainingLowerCase())) + .filter(name -> StringUtil.startsWithIgnoreCase(name, builder.getRemaining()) || StringUtil.startsWithIgnoreCase(name.substring(name.lastIndexOf('.') + 1), builder.getRemaining())) .forEach(builder::suggest); return builder.build(); }); @@ -61,7 +61,7 @@ public final class DumpListenersCommand { public static LiteralArgumentBuilder create() { return Commands.literal("dumplisteners") - .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + "dumplisteners")) + .requires(PaperCommand.hasPermission("dumplisteners")) .then(Commands.literal(COMMAND_ARGUMENT_TO_FILE) .executes(context -> { return dumpToFile(context.getSource().getSender()); diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java index 6ae5fe875a55..2c4c726cd390 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java @@ -54,7 +54,7 @@ public final class DumpPluginsCommand { public static LiteralArgumentBuilder create() { return Commands.literal("dumpplugins") - .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + "dumpplugins")) + .requires(PaperCommand.hasPermission("dumpplugins")) .executes(context -> { return dumpPlugins(context.getSource().getSender()); }); diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java index dafad2e6ed50..0b6b31f2e2ac 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java @@ -42,7 +42,7 @@ public final class EntityCommand { public static LiteralArgumentBuilder create() { return Commands.literal("entity") - .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + "entity")) + .requires(PaperCommand.hasPermission("entity")) .then(Commands.literal("list") .then(Commands.literal("help") .executes(context -> { @@ -55,7 +55,7 @@ public static LiteralArgumentBuilder create() { .suggests((context, builder) -> { BuiltInRegistries.ENTITY_TYPE.keySet().stream() .map(Object::toString) - .filter(type -> type.startsWith(builder.getRemainingLowerCase())) + .filter(type -> StringUtil.startsWithIgnoreCase(type, builder.getRemaining())) .forEach(builder::suggest); return builder.buildFuture(); }) @@ -78,13 +78,6 @@ public static LiteralArgumentBuilder create() { )*/ // string argument is super strict for some reason for unquoted stuff this help mitigate it (ideally should allow entity type tags too then the regex can be dropped I think) .then(Commands.argument("entity-type", ArgumentTypes.resourceKey(RegistryKey.ENTITY_TYPE)) - .suggests((context, builder) -> { - BuiltInRegistries.ENTITY_TYPE.keySet().stream() - .map(Object::toString) - .filter(type -> type.startsWith(builder.getRemainingLowerCase())) - .forEach(builder::suggest); - return builder.buildFuture(); - }) .executes(context -> { return listEntities( context.getSource().getSender(), diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java index 4cf698c8166f..8f3bad7e52b3 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java @@ -21,7 +21,7 @@ public final class HeapDumpCommand { public static LiteralArgumentBuilder create() { return Commands.literal("heap") - .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + ".heap")) + .requires(PaperCommand.hasPermission("heap")) .executes(context -> { return dumpHeap(context.getSource().getSender()); }); diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java index 66589f71f4ee..034f5db306b1 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java @@ -33,6 +33,8 @@ import org.bukkit.entity.Player; import org.jspecify.annotations.NullMarked; +import static net.kyori.adventure.text.Component.text; + @NullMarked public final class MobcapsCommand { @@ -49,7 +51,7 @@ public final class MobcapsCommand { public static LiteralArgumentBuilder createGlobal() { return Commands.literal("mobcaps") - .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + "mobcaps")) + .requires(PaperCommand.hasPermission("mobcaps")) .then(Commands.literal("*") .executes(context -> { return printMobcaps(context.getSource().getSender(), Bukkit.getWorlds()); @@ -67,7 +69,7 @@ public static LiteralArgumentBuilder createGlobal() { public static LiteralArgumentBuilder createPlayer() { return Commands.literal("playermobcaps") - .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + "playermobcaps")) + .requires(PaperCommand.hasPermission("playermobcaps")) .then(Commands.argument("player", ArgumentTypes.player()) .executes(context -> { return printPlayerMobcaps( @@ -91,9 +93,9 @@ private static int printMobcaps(final CommandSender sender, final List wo final int chunks = state == null ? 0 : state.getSpawnableChunkCount(); sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), - Component.text("Mobcaps for world: "), - Component.text(world.key().asString(), NamedTextColor.AQUA), - Component.text(" (" + chunks + " spawnable chunks)") + text("Mobcaps for world: "), + text(world.key().asString(), NamedTextColor.AQUA), + text(" (" + chunks + " spawnable chunks)") )); sender.sendMessage(createMobcapsComponent( @@ -111,11 +113,11 @@ private static int printPlayerMobcaps(final CommandSender sender, final Player p final ServerLevel level = serverPlayer.level(); if (!level.paperConfig().entities.spawning.perPlayerMobSpawns) { - sender.sendMessage(Component.text("Use '/paper mobcaps' for worlds where per-player mob spawning is disabled.", NamedTextColor.RED)); + sender.sendMessage(text("Use '/paper mobcaps' for worlds where per-player mob spawning is disabled.", NamedTextColor.RED)); return 0; } - sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), Component.text("Mobcaps for player: "), Component.text(player.getName(), NamedTextColor.GREEN))); + sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), text("Mobcaps for player: "), text(player.getName(), NamedTextColor.GREEN))); sender.sendMessage(createMobcapsComponent( category -> level.chunkSource.chunkMap.getMobCountNear(serverPlayer, category), category -> level.getWorld().getSpawnLimitUnsafe(org.bukkit.craftbukkit.util.CraftSpawnCategory.toBukkit(category)) @@ -130,43 +132,43 @@ private static Component createMobcapsComponent(final ToIntFunction final TextColor color = entry.getValue(); final Component categoryHover = Component.join(JoinConfiguration.noSeparators(), - Component.text("Entity types in category ", TextColor.color(0xE0E0E0)), - Component.text(category.getName(), color), - Component.text(':', NamedTextColor.GRAY), + text("Entity types in category ", TextColor.color(0xE0E0E0)), + text(category.getName(), color), + text(':', NamedTextColor.GRAY), Component.newline(), Component.newline(), BuiltInRegistries.ENTITY_TYPE.entrySet().stream() .filter(it -> it.getValue().getCategory() == category) .map(it -> Component.translatable(it.getValue().getDescriptionId())) - .collect(Component.toComponent(Component.text(", ", NamedTextColor.GRAY))) + .collect(Component.toComponent(text(", ", NamedTextColor.GRAY))) ); - final Component categoryComponent = Component.text() + final Component categoryComponent = text() .content(" " + category.getName()) .color(color) .hoverEvent(categoryHover) .build(); - final TextComponent.Builder builder = Component.text() + final TextComponent.Builder builder = text() .append( categoryComponent, - Component.text(": ", NamedTextColor.GRAY) + text(": ", NamedTextColor.GRAY) ); final int limit = limitGetter.applyAsInt(category); if (limit != -1) { builder.append( - Component.text(countGetter.applyAsInt(category)), - Component.text("/", NamedTextColor.GRAY), - Component.text(limit) + text(countGetter.applyAsInt(category)), + text("/", NamedTextColor.GRAY), + text(limit) ); } else { - builder.append(Component.text() + builder.append(text() .append( - Component.text('n'), - Component.text("/", NamedTextColor.GRAY), - Component.text('a') + text('n'), + text("/", NamedTextColor.GRAY), + text('a') ) - .hoverEvent(Component.text("This category does not naturally spawn."))); + .hoverEvent(text("This category does not naturally spawn."))); } return builder; }) diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java index f02f8770d77f..f57ddf634d2a 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java @@ -19,7 +19,7 @@ public final class ReloadCommand { public static LiteralArgumentBuilder create() { return Commands.literal("reload") - .requires(source -> source.getSender().hasPermission(PaperCommand.BASE_PERM + "reload")) + .requires(PaperCommand.hasPermission("reload")) .executes(context -> { doReload(context.getSource().getSender()); return Command.SINGLE_SUCCESS; diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java index f9a340cb9828..3449f7f4e1b7 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java @@ -30,7 +30,7 @@ public final class SyncLoadInfoCommand { public static LiteralArgumentBuilder create() { return Commands.literal("syncloadinfo") - .requires(source -> SyncLoadFinder.ENABLED && source.getSender().hasPermission(PaperCommand.BASE_PERM + "syncloadinfo")) + .requires(PaperCommand.hasPermission("syncloadinfo").and($ -> SyncLoadFinder.ENABLED)) .then(Commands.literal("clear") .executes(context -> { SyncLoadFinder.clear(); diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java index cdf3760639c5..e330c6770c41 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java @@ -2,6 +2,7 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import io.papermc.paper.command.PaperCommand; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import net.minecraft.server.MinecraftServer; @@ -10,9 +11,9 @@ @NullMarked public final class VersionCommand { - public static LiteralArgumentBuilder create(String permission, String name) { + public static LiteralArgumentBuilder create(String name) { return Commands.literal(name) - .requires(source -> source.getSender().hasPermission(permission)) + .requires(PaperCommand.hasPermission("version")) .executes(context -> { final org.bukkit.command.Command redirect = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); if (redirect != null && redirect.execute(context.getSource().getSender(), "paper", new String[0])) { From 77a4e450054b08033ab9d0cefbe7b650a275c1a6 Mon Sep 17 00:00:00 2001 From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:27:26 +0100 Subject: [PATCH 6/9] simplify --- .../0001-Moonrise-optimisation-patches.patch | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch index a5dad389ba3e..9cbbd0f01702 100644 --- a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch @@ -22489,10 +22489,10 @@ index 6f9e2dd6de4ae89ef6f393882903b13c72899605..da1cec4991420a091a55270a49246d10 \ No newline at end of file diff --git a/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..76f16944f108e6b3fd9015da3b1f84e1517616a1 +index 0000000000000000000000000000000000000000..349e82ea06fcaad570e93b2155ae1f92a537b649 --- /dev/null +++ b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java -@@ -0,0 +1,281 @@ +@@ -0,0 +1,271 @@ +package io.papermc.paper.command.subcommands; + +import ca.spottedleaf.moonrise.common.util.JsonUtil; @@ -22551,31 +22551,21 @@ index 0000000000000000000000000000000000000000..76f16944f108e6b3fd9015da3b1f84e1 + return Command.SINGLE_SUCCESS; + }) + ) -+ .executes(context -> { -+ return debugDumpLoadedChunks(context.getSource().getSender()); -+ }) + .then(Commands.literal("--async") + .requires(source -> source.getSender() instanceof ConsoleCommandSender) + .executes(context -> { + context.getSource().getSender().sendPlainMessage("Scheduling async debug chunks"); -+ Runnable run = () -> { -+ MinecraftServer server = MinecraftServer.getServer(); ++ Thread.ofPlatform().name("Async debug thread #" + ASYNC_DEBUG_CHUNKS_COUNT.getAndIncrement()).daemon(true).start(() -> { + context.getSource().getSender().sendPlainMessage("Async debug chunks executing"); -+ ChunkTaskScheduler.dumpAllChunkLoadInfo(server, false); -+ File file = ChunkTaskScheduler.getChunkDebugFile(); -+ context.getSource().getSender().sendMessage(text("Writing chunk information dump to " + file, GREEN)); -+ try { -+ JsonUtil.writeJson(ChunkTaskScheduler.debugAllWorlds(server), file); -+ context.getSource().getSender().sendMessage(text("Successfully written chunk information!", GREEN)); -+ } catch (Throwable thr) { -+ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file {}", file.toString(), thr); -+ context.getSource().getSender().sendMessage(text("Failed to dump chunk information, see console", RED)); -+ } -+ }; -+ Thread.ofPlatform().name("Async debug thread #" + ASYNC_DEBUG_CHUNKS_COUNT.getAndIncrement()).daemon(true).start(run); ++ ChunkTaskScheduler.dumpAllChunkLoadInfo(MinecraftServer.getServer(), false); ++ debugDumpLoadedChunks(context.getSource().getSender()); ++ }); + return Command.SINGLE_SUCCESS; + }) + ) ++ .executes(context -> { ++ return debugDumpLoadedChunks(context.getSource().getSender()); ++ }) + ) + .executes(context -> { + context.getSource().getSender().sendMessage(DEBUG_HELP); From 59458269f29d68f124ae6ac8dce3215592cb9592 Mon Sep 17 00:00:00 2001 From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> Date: Mon, 2 Feb 2026 20:12:38 +0100 Subject: [PATCH 7/9] add back no arg chunkinfo/holderinfo command --- .../java/org/bukkit/event/HandlerList.java | 7 +- .../0001-Moonrise-optimisation-patches.patch | 111 +++++++++--------- .../io/papermc/paper/FeatureHooks.java.patch | 2 +- .../papermc/paper/command/PaperCommand.java | 54 ++++----- .../papermc/paper/command/PaperCommands.java | 2 +- .../paper/command/PaperPluginsCommand.java | 8 +- .../paper/command/PaperVersionCommand.java | 50 ++++---- .../argument/VanillaArgumentProviderImpl.java | 19 ++- .../command/subcommands/DumpItemCommand.java | 21 ++-- .../subcommands/DumpListenersCommand.java | 54 ++++----- .../subcommands/DumpPluginsCommand.java | 2 - .../command/subcommands/EntityCommand.java | 44 ++++--- .../command/subcommands/HeapDumpCommand.java | 2 - .../command/subcommands/MobcapsCommand.java | 24 ++-- .../command/subcommands/ReloadCommand.java | 2 - .../subcommands/SyncLoadInfoCommand.java | 10 +- .../command/subcommands/VersionCommand.java | 14 +-- .../command/subcommands/package-info.java | 4 + .../org/spigotmc/TicksPerSecondCommand.java | 2 +- 19 files changed, 209 insertions(+), 223 deletions(-) create mode 100644 paper-server/src/main/java/io/papermc/paper/command/subcommands/package-info.java diff --git a/paper-api/src/main/java/org/bukkit/event/HandlerList.java b/paper-api/src/main/java/org/bukkit/event/HandlerList.java index ef9d9f3ddaf2..cd6a8f4d543a 100644 --- a/paper-api/src/main/java/org/bukkit/event/HandlerList.java +++ b/paper-api/src/main/java/org/bukkit/event/HandlerList.java @@ -5,7 +5,9 @@ import java.util.EnumMap; import java.util.List; import java.util.ListIterator; +import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.RegisteredListener; import org.jetbrains.annotations.NotNull; @@ -36,7 +38,8 @@ public class HandlerList { /** * Event types which have instantiated a {@link HandlerList}. */ - private static final java.util.Set EVENT_TYPES = java.util.concurrent.ConcurrentHashMap.newKeySet(); + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") + private static final Map EVENT_TYPES = new ConcurrentHashMap<>(); /** * Bake all handler lists. Best used just after all normal event @@ -102,7 +105,7 @@ public HandlerList() { java.lang.StackWalker.getInstance(java.util.EnumSet.of(java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE), 4) .walk(s -> s.filter(f -> Event.class.isAssignableFrom(f.getDeclaringClass())).findFirst()) .map(f -> f.getDeclaringClass().getName()) - .ifPresent(EVENT_TYPES::add); + .ifPresent(name -> EVENT_TYPES.put(name, this)); handlerslots = new EnumMap<>(EventPriority.class); for (EventPriority o : EventPriority.values()) { diff --git a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch index 9cbbd0f01702..075bced4f834 100644 --- a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch @@ -22256,7 +22256,7 @@ index 0000000000000000000000000000000000000000..95154636727366adfb4fb67e7db8b09f + private SaveUtil() {} +} diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java -index 6f9e2dd6de4ae89ef6f393882903b13c72899605..da1cec4991420a091a55270a49246d1020f1bbb6 100644 +index 619e981bc08b5f3c69868f15e559ff1bb9e52959..8e7f8f25925586634bba9310e882ba40d48ba5ad 100644 --- a/io/papermc/paper/FeatureHooks.java +++ b/io/papermc/paper/FeatureHooks.java @@ -2,6 +2,9 @@ package io.papermc.paper; @@ -22281,12 +22281,12 @@ index 6f9e2dd6de4ae89ef6f393882903b13c72899605..da1cec4991420a091a55270a49246d10 + ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.setUnloadDelay(ticks); // Paper - rewrite chunk system } - public static void registerPaperCommands(final Map>> commands) { + public static void registerPaperCommands(final List> commands) { + // Paper start - Chunk system -+ commands.put("fixlight", List.of(FixLightCommand.create())); -+ commands.put("debug", List.of(ChunkDebugCommand.createDebug())); -+ commands.put("chunkinfo", List.of(ChunkDebugCommand.createChunkInfo())); -+ commands.put("holderinfo", List.of(ChunkDebugCommand.createHolderInfo())); ++ commands.add(FixLightCommand.create()); ++ commands.add(ChunkDebugCommand.createDebug()); ++ commands.add(ChunkDebugCommand.createChunkInfo()); ++ commands.add(ChunkDebugCommand.createHolderInfo()); + // Paper end - Chunk system } @@ -22489,10 +22489,10 @@ index 6f9e2dd6de4ae89ef6f393882903b13c72899605..da1cec4991420a091a55270a49246d10 \ No newline at end of file diff --git a/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..349e82ea06fcaad570e93b2155ae1f92a537b649 +index 0000000000000000000000000000000000000000..cc77c279492a0506e1b4d3447879a0b6983ace71 --- /dev/null +++ b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java -@@ -0,0 +1,271 @@ +@@ -0,0 +1,266 @@ +package io.papermc.paper.command.subcommands; + +import ca.spottedleaf.moonrise.common.util.JsonUtil; @@ -22508,6 +22508,7 @@ index 0000000000000000000000000000000000000000..349e82ea06fcaad570e93b2155ae1f92 +import java.io.File; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; ++import java.util.function.ToIntBiFunction; +import net.kyori.adventure.text.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; @@ -22531,13 +22532,16 @@ index 0000000000000000000000000000000000000000..349e82ea06fcaad570e93b2155ae1f92 +@NullMarked +public final class ChunkDebugCommand { + -+ private static final Component DEBUG_HELP = text("Use /paper debug [chunks] help for more information on a specific command", RED); -+ private static final Component DEBUG_CHUNKS_HELP = text("Use /paper debug chunks to dump loaded chunk information to a file", RED); ++ private static final Component DEBUG_HELP = text("Use /paper debug chunks to dump loaded chunk information to a file", RED); + private static final AtomicInteger ASYNC_DEBUG_CHUNKS_COUNT = new AtomicInteger(); + + public static LiteralArgumentBuilder createDebug() { + return Commands.literal("debug") + .requires(PaperCommand.hasPermission("debug")) ++ .executes(context -> { ++ context.getSource().getSender().sendMessage(DEBUG_HELP); ++ return Command.SINGLE_SUCCESS; ++ }) + .then(Commands.literal("help") + .executes(context -> { + context.getSource().getSender().sendMessage(DEBUG_HELP); @@ -22545,12 +22549,9 @@ index 0000000000000000000000000000000000000000..349e82ea06fcaad570e93b2155ae1f92 + }) + ) + .then(Commands.literal("chunks") -+ .then(Commands.literal("help") -+ .executes(context -> { -+ context.getSource().getSender().sendMessage(DEBUG_CHUNKS_HELP); -+ return Command.SINGLE_SUCCESS; -+ }) -+ ) ++ .executes(context -> { ++ return debugDumpLoadedChunks(context.getSource().getSender()); ++ }) + .then(Commands.literal("--async") + .requires(source -> source.getSender() instanceof ConsoleCommandSender) + .executes(context -> { @@ -22563,27 +22564,26 @@ index 0000000000000000000000000000000000000000..349e82ea06fcaad570e93b2155ae1f92 + return Command.SINGLE_SUCCESS; + }) + ) -+ .executes(context -> { -+ return debugDumpLoadedChunks(context.getSource().getSender()); -+ }) -+ ) -+ .executes(context -> { -+ context.getSource().getSender().sendMessage(DEBUG_HELP); -+ return Command.SINGLE_SUCCESS; -+ }); ++ ); + } + -+ private static LiteralArgumentBuilder createWorldInfo(String name, WorldInfoLogic info) { ++ private static LiteralArgumentBuilder createWorldInfo(String name, ToIntBiFunction> action) { + return Commands.literal(name) + .requires(PaperCommand.hasPermission(name)) -+ .then(Commands.argument("world", ArgumentTypes.world()) ++ .executes(context -> { ++ return action.applyAsInt( ++ context.getSource().getSender(), ++ context.getSource().getSender() instanceof ConsoleCommandSender ? Bukkit.getWorlds() : List.of(context.getSource().getLocation().getWorld()) ++ ); ++ }) ++ .then(Commands.literal("*") + .executes(context -> { -+ return info.doInfo(context.getSource().getSender(), List.of(context.getArgument("world", World.class))); ++ return action.applyAsInt(context.getSource().getSender(), Bukkit.getWorlds()); + }) + ) -+ .then(Commands.literal("*") ++ .then(Commands.argument("world", ArgumentTypes.world()) + .executes(context -> { -+ return info.doInfo(context.getSource().getSender(), Bukkit.getWorlds()); ++ return action.applyAsInt(context.getSource().getSender(), List.of(context.getArgument("world", World.class))); + }) + ); + } @@ -22648,7 +22648,7 @@ index 0000000000000000000000000000000000000000..349e82ea06fcaad570e93b2155ae1f92 + accumulatedTicking += blockTicking; + accumulatedEntityTicking += entityTicking; + -+ sender.sendMessage(text().append(text("Chunks in ", BLUE), text(bukkitWorld.getKey().asString(), GREEN), text(":"))); ++ sender.sendMessage(text().append(text("Chunks in ", BLUE), text(bukkitWorld.key().asString(), GREEN), text(":"))); + sender.sendMessage(text().color(DARK_AQUA).append( + text("Total: ", BLUE), text(total), + text(" Inactive: ", BLUE), text(inactive), @@ -22758,18 +22758,13 @@ index 0000000000000000000000000000000000000000..349e82ea06fcaad570e93b2155ae1f92 + return 0; + } + } -+ -+ @FunctionalInterface -+ private interface WorldInfoLogic { -+ int doInfo(final CommandSender sender, final List worlds); -+ } +} diff --git a/io/papermc/paper/command/subcommands/FixLightCommand.java b/io/papermc/paper/command/subcommands/FixLightCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..5f7a2cd7eadc8ef55496dbda76683bc13d5ffb79 +index 0000000000000000000000000000000000000000..5ee1eb777e5382a2066ec73468ec4bac84db9673 --- /dev/null +++ b/io/papermc/paper/command/subcommands/FixLightCommand.java -@@ -0,0 +1,103 @@ +@@ -0,0 +1,109 @@ +package io.papermc.paper.command.subcommands; + +import ca.spottedleaf.moonrise.patches.starlight.light.StarLightLightingProvider; @@ -22783,13 +22778,14 @@ index 0000000000000000000000000000000000000000..5f7a2cd7eadc8ef55496dbda76683bc1 +import java.text.DecimalFormat; +import java.util.Iterator; +import java.util.LinkedHashSet; ++import java.util.function.Consumer; ++import net.kyori.adventure.text.ComponentLike; +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.status.ChunkStatus; -+import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; @@ -22808,33 +22804,38 @@ index 0000000000000000000000000000000000000000..5f7a2cd7eadc8ef55496dbda76683bc1 + + public static LiteralArgumentBuilder create() { + return Commands.literal("fixlight") -+ .requires(PaperCommand.hasPermission("fixlight").and(source -> source.getSender() instanceof Player)) ++ .requires(PaperCommand.hasPermission("fixlight")) ++ .executes(context -> { ++ if (!(context.getSource().getExecutor() instanceof Player player)) { ++ throw net.minecraft.commands.CommandSourceStack.ERROR_NOT_PLAYER.create(); ++ } ++ doFixLight(player, 2); ++ return Command.SINGLE_SUCCESS; ++ }) + .then(Commands.argument("radius", IntegerArgumentType.integer(0, MAX_RADIUS)) + .executes(context -> { -+ doFixLight(context.getSource().getSender(), IntegerArgumentType.getInteger(context, "radius")); ++ if (!(context.getSource().getExecutor() instanceof Player player)) { ++ throw net.minecraft.commands.CommandSourceStack.ERROR_NOT_PLAYER.create(); ++ } ++ doFixLight(player, IntegerArgumentType.getInteger(context, "radius")); + return Command.SINGLE_SUCCESS; + }) -+ ) -+ .executes(context -> { -+ doFixLight(context.getSource().getSender(), 2); -+ return Command.SINGLE_SUCCESS; -+ }); ++ ); + } + -+ private static void doFixLight(final CommandSender sender, final int radius) { -+ CraftPlayer player = (CraftPlayer) sender; -+ ServerPlayer handle = player.getHandle(); -+ ServerLevel world = handle.level(); ++ private static void doFixLight(final Player sender, final int radius) { ++ ServerPlayer player = ((CraftPlayer) sender).getHandle(); ++ ServerLevel world = player.level(); + ThreadedLevelLightEngine lightEngine = world.getChunkSource().getLightEngine(); -+ starlightFixLight(handle, sender, world, lightEngine, radius); ++ starlightFixLight(player, world, lightEngine, radius, sender::sendMessage); + } + + private static void starlightFixLight( + final ServerPlayer player, -+ final CommandSender sender, + final ServerLevel world, + final ThreadedLevelLightEngine lightEngine, -+ final int radius ++ final int radius, ++ final Consumer output + ) { + final long start = System.nanoTime(); + final LinkedHashSet chunks = new LinkedHashSet<>(MCUtil.getSpiralOutChunks(player.blockPosition(), radius)); // getChunkCoordinates is actually just bad mappings, this function rets position as blockpos @@ -22857,20 +22858,20 @@ index 0000000000000000000000000000000000000000..5f7a2cd7eadc8ef55496dbda76683bc1 + ((StarLightLightingProvider) lightEngine).starlight$serverRelightChunks(chunks, + (final ChunkPos chunkPos) -> { + ++relitChunks[0]; -+ sender.sendMessage(text().color(DARK_AQUA).append( ++ output.accept(text().color(DARK_AQUA).append( + text("Relit chunk ", BLUE), text(chunkPos.toString()), + text(", progress: ", BLUE), text(ONE_DECIMAL_PLACES.get().format(100.0 * (double) (relitChunks[0]) / (double) pending[0]) + "%") + )); + }, + (final int totalRelit) -> { + final long end = System.nanoTime(); -+ sender.sendMessage(text().color(DARK_AQUA).append( ++ output.accept(text().color(DARK_AQUA).append( + text("Relit ", BLUE), text(totalRelit), + text(" chunks. Took ", BLUE), text(ONE_DECIMAL_PLACES.get().format(1.0e-6 * (end - start)) + "ms") + )); + } + ); -+ sender.sendMessage(text().color(BLUE).append(text("Relighting "), text(pending[0], DARK_AQUA), text(" chunks"))); ++ output.accept(text().color(BLUE).append(text("Relighting "), text(pending[0], DARK_AQUA), text(" chunks"))); + } +} diff --git a/io/papermc/paper/threadedregions/TickRegions.java b/io/papermc/paper/threadedregions/TickRegions.java diff --git a/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch b/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch index 7b127e049e93..63fb271ce366 100644 --- a/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch +++ b/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch @@ -42,7 +42,7 @@ + public static void setPlayerChunkUnloadDelay(final long ticks) { + } + -+ public static void registerPaperCommands(final Map>> commands) { ++ public static void registerPaperCommands(final List> commands) { + } + + public static LevelChunkSection createSection(final PalettedContainerFactory palettedContainerFactory, final Level level, final ChunkPos chunkPos, final int chunkSection) { diff --git a/paper-server/src/main/java/io/papermc/paper/command/PaperCommand.java b/paper-server/src/main/java/io/papermc/paper/command/PaperCommand.java index 46a5a01f5f9c..c04b174692be 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/PaperCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/PaperCommand.java @@ -15,13 +15,13 @@ 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.objects.ObjectArrayList; import java.nio.file.Path; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import net.kyori.adventure.text.Component; @@ -40,7 +40,7 @@ public final class PaperCommand { public static final DateTimeFormatter FILENAME_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss", Locale.ROOT); // could use the formatter in Util too - public static Component asFriendlyPath(Path path) { + public static Component asFriendlyPath(final Path path) { return text(path.toString()) .hoverEvent(text("Click to copy the full path of the file")) .clickEvent(ClickEvent.copyToClipboard(path.toAbsolutePath().toString())); @@ -50,16 +50,18 @@ public static Component asFriendlyPath(Path path) { static final String DESCRIPTION = "Paper related commands"; public static LiteralCommandNode create() { - LiteralArgumentBuilder rootNode = Commands.literal("paper") + final LiteralArgumentBuilder rootNode = Commands.literal("paper") .requires(source -> source.getSender().hasPermission(BASE_PERM) || SUBPERMISSIONS.stream().anyMatch(name -> source.getSender().hasPermission(name))) .executes(context -> { context.getSource().getSender().sendPlainMessage("/paper [" + SUBCOMMANDS.keySet().stream().filter( - name -> hasPermission(name).test(context.getSource())).collect(Collectors.joining(" | ") - ) + "]"); + name -> hasPermission(name).test(context.getSource()) + ).collect(Collectors.joining(" | ")) + "]"); return Command.SINGLE_SUCCESS; }); - SUBCOMMANDS.values().forEach(cmd -> cmd.forEach(rootNode::then)); + SUBCOMMANDS.values().forEach(rootNode::then); + rootNode.then(VersionCommand.create("ver")); + return rootNode.build(); } @@ -68,32 +70,26 @@ public static Predicate hasPermission(String name) { } public static void registerPermissions() { - final List permissions = new ArrayList<>(); - permissions.add(BASE_PERM); - permissions.addAll(SUBCOMMANDS.keySet().stream().map(name -> BASE_PERM + + '.' + name).toList()); - final PluginManager pluginManager = Bukkit.getServer().getPluginManager(); - for (final String permission : permissions) { + pluginManager.addPermission(new Permission(BASE_PERM, PermissionDefault.OP)); + for (final String permission : SUBPERMISSIONS) { pluginManager.addPermission(new Permission(permission, PermissionDefault.OP)); } } - // subcommand label -> subcommand - private static final Map>> SUBCOMMANDS = Util.make(new HashMap<>(14), map -> { - map.put("heap", List.of(HeapDumpCommand.create())); - map.put("entity", List.of(EntityCommand.create())); - map.put("reload", List.of(ReloadCommand.create())); - map.put("version", List.of( - VersionCommand.create("version"), - VersionCommand.create("ver")) - ); - map.put("dumpplugins", List.of(DumpPluginsCommand.create())); - map.put("syncloadinfo", List.of(SyncLoadInfoCommand.create())); - map.put("dumpitem", List.of(DumpItemCommand.create())); - map.put("mobcaps", List.of(MobcapsCommand.createGlobal())); - map.put("playermobcaps", List.of(MobcapsCommand.createPlayer())); - map.put("dumplisteners", List.of(DumpListenersCommand.create())); - FeatureHooks.registerPaperCommands(map); - }); + private static final Map> SUBCOMMANDS = Util.make(new ObjectArrayList>(), list -> { + list.add(HeapDumpCommand.create()); + list.add(EntityCommand.create()); + list.add(ReloadCommand.create()); + list.add(VersionCommand.create("version")); + list.add(DumpPluginsCommand.create()); + list.add(SyncLoadInfoCommand.create()); + list.add(DumpItemCommand.create()); + list.add(MobcapsCommand.createGlobal()); + list.add(MobcapsCommand.createPlayer()); + list.add(DumpListenersCommand.create()); + FeatureHooks.registerPaperCommands(list); + }).stream().collect(Collectors.toMap(LiteralArgumentBuilder::getLiteral, Function.identity())); + private static final List SUBPERMISSIONS = SUBCOMMANDS.keySet().stream().map(name -> BASE_PERM + '.' + name).toList(); } diff --git a/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java b/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java index 723238dc5b14..c1a692669bbb 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java +++ b/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java @@ -30,9 +30,9 @@ public static void registerLegacyCommands(final MinecraftServer server) { public static void registerCommands() { // brigadier commands go here - registerInternalCommand(PaperCommand.create(), "paper", PaperCommand.DESCRIPTION, List.of(), Set.of()); registerInternalCommand(PaperVersionCommand.create(), "bukkit", PaperVersionCommand.DESCRIPTION, List.of("ver", "about"), Set.of()); registerInternalCommand(PaperPluginsCommand.create(), "bukkit", PaperPluginsCommand.DESCRIPTION, List.of("pl"), Set.of()); + registerInternalCommand(PaperCommand.create(), "paper", PaperCommand.DESCRIPTION, List.of(), Set.of()); } private static void registerInternalCommand(final LiteralCommandNode node, final String namespace, final String description, final List aliases, final Set flags) { diff --git a/paper-server/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java b/paper-server/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java index d563c26a800f..0562f730181d 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java @@ -34,8 +34,8 @@ import org.jspecify.annotations.NullMarked; @NullMarked -public class PaperPluginsCommand { - public static final String DESCRIPTION = "Gets a list of plugins running on the server"; +public final class PaperPluginsCommand { + static final String DESCRIPTION = "Gets a list of plugins running on the server"; private static final TextColor INFO_COLOR = TextColor.color(52, 159, 218); @@ -130,9 +130,9 @@ private static Component header(final String header, final int color, final int } private static Component asPlainComponents(final String strings) { - final net.kyori.adventure.text.TextComponent.Builder builder = Component.text(); + final TextComponent.Builder builder = Component.text(); for (final String string : strings.split("\n")) { - builder.append(Component.newline()); + builder.appendNewline(); builder.append(Component.text(string, NamedTextColor.WHITE)); } diff --git a/paper-server/src/main/java/io/papermc/paper/command/PaperVersionCommand.java b/paper-server/src/main/java/io/papermc/paper/command/PaperVersionCommand.java index a3908df843bf..92849da499af 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/PaperVersionCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/PaperVersionCommand.java @@ -30,8 +30,8 @@ import org.jspecify.annotations.NullMarked; @NullMarked -public class PaperVersionCommand { - public static final String DESCRIPTION = "Gets the version of this server including any plugins in use"; +public final class PaperVersionCommand { + static final String DESCRIPTION = "Gets the version of this server including any plugins in use"; private static final Component NOT_RUNNING = Component.text() .append(Component.text("This server is not running any plugin by that name.")) @@ -45,24 +45,22 @@ public class PaperVersionCommand { private static final Component FAILED_TO_FETCH = Component.text("Could not fetch version information!", NamedTextColor.RED); private static final Component FETCHING = Component.text("Checking version, please wait...", NamedTextColor.WHITE, TextDecoration.ITALIC); - private final VersionFetcher versionFetcher = CraftMagicNumbers.INSTANCE.getVersionFetcher(); - private CompletableFuture computedVersion = CompletableFuture.completedFuture(new ComputedVersion(Component.empty(), -1)); // Precompute-- someday move that stuff out of bukkit + private static final VersionFetcher versionFetcher = CraftMagicNumbers.INSTANCE.getVersionFetcher(); + private static CompletableFuture computedVersion = CompletableFuture.completedFuture(new ComputedVersion(Component.empty(), -1)); // Precompute-- someday move that stuff out of bukkit public static LiteralCommandNode create() { - final PaperVersionCommand command = new PaperVersionCommand(); - return Commands.literal("version") .requires(source -> source.getSender().hasPermission("bukkit.command.version")) + .executes(PaperVersionCommand::serverVersion) .then(Commands.argument("plugin", StringArgumentType.word()) - .suggests(command::suggestPlugins) - .executes(command::pluginVersion)) - .executes(command::serverVersion) + .suggests(PaperVersionCommand::suggestPlugins) + .executes(PaperVersionCommand::pluginVersion)) .build(); } - private int pluginVersion(final CommandContext context) { + private static int pluginVersion(final CommandContext context) { final CommandSender sender = context.getSource().getSender(); - final String pluginName = context.getArgument("plugin", String.class).toLowerCase(Locale.ROOT); + final String pluginName = StringArgumentType.getString(context, "plugin").toLowerCase(Locale.ROOT); Plugin plugin = Bukkit.getPluginManager().getPlugin(pluginName); if (plugin == null) { @@ -73,15 +71,15 @@ private int pluginVersion(final CommandContext context) { } if (plugin != null) { - this.sendPluginInfo(plugin, sender); + sendPluginInfo(plugin, sender); + return Command.SINGLE_SUCCESS; } else { sender.sendMessage(NOT_RUNNING); + return 0; } - - return Command.SINGLE_SUCCESS; } - private CompletableFuture suggestPlugins(final CommandContext context, final SuggestionsBuilder builder) { + private static CompletableFuture suggestPlugins(final CommandContext context, final SuggestionsBuilder builder) { for (final Plugin plugin : Bukkit.getPluginManager().getPlugins()) { final String name = plugin.getName(); if (StringUtil.startsWithIgnoreCase(name, builder.getRemaining())) { @@ -92,7 +90,7 @@ private CompletableFuture suggestPlugins(final CommandContext names) { return Component.join(PLAYER_JOIN_CONFIGURATION, names.stream().map(Component::text).toList()).color(NamedTextColor.GREEN); } - private int serverVersion(CommandContext context) { + public static int serverVersion(CommandContext context) { sendVersion(context.getSource().getSender()); return Command.SINGLE_SUCCESS; } - private void sendVersion(final CommandSender sender) { + private static void sendVersion(final CommandSender sender) { final CompletableFuture version = getVersionOrFetch(); if (!version.isDone()) { sender.sendMessage(FETCHING); @@ -150,24 +148,24 @@ private void sendVersion(final CommandSender sender) { }); } - private CompletableFuture getVersionOrFetch() { - if (!this.computedVersion.isDone()) { - return this.computedVersion; + private static CompletableFuture getVersionOrFetch() { + if (!computedVersion.isDone()) { + return computedVersion; } - if (this.computedVersion.isCompletedExceptionally() || System.currentTimeMillis() - this.computedVersion.resultNow().computedTime() > this.versionFetcher.getCacheTime()) { - this.computedVersion = this.fetchVersionMessage(); + if (computedVersion.isCompletedExceptionally() || System.currentTimeMillis() - computedVersion.resultNow().computedTime() > versionFetcher.getCacheTime()) { + computedVersion = fetchVersionMessage(); } - return this.computedVersion; + return computedVersion; } - private CompletableFuture fetchVersionMessage() { + private static CompletableFuture fetchVersionMessage() { return CompletableFuture.supplyAsync(() -> { final Component message = Component.textOfChildren( Component.text(Bukkit.getVersionMessage(), NamedTextColor.WHITE), Component.newline(), - this.versionFetcher.getVersionMessage() + versionFetcher.getVersionMessage() ); return new ComputedVersion( diff --git a/paper-server/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java b/paper-server/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java index bff395667f69..2c78c218f209 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java +++ b/paper-server/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java @@ -11,6 +11,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.datafixers.util.Either; import io.papermc.paper.adventure.PaperAdventure; import io.papermc.paper.command.brigadier.PaperCommands; import io.papermc.paper.command.brigadier.argument.predicate.BlockInWorldPredicate; @@ -35,6 +36,7 @@ import io.papermc.paper.registry.RegistryAccess; import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.TypedKey; +import io.papermc.paper.registry.tag.TagKey; import io.papermc.paper.util.MCUtil; import java.util.Collection; import java.util.Collections; @@ -61,12 +63,13 @@ import net.minecraft.commands.arguments.GameProfileArgument; import net.minecraft.commands.arguments.HeightmapTypeArgument; import net.minecraft.commands.arguments.HexColorArgument; +import net.minecraft.commands.arguments.IdentifierArgument; import net.minecraft.commands.arguments.MessageArgument; import net.minecraft.commands.arguments.ObjectiveCriteriaArgument; import net.minecraft.commands.arguments.RangeArgument; import net.minecraft.commands.arguments.ResourceArgument; import net.minecraft.commands.arguments.ResourceKeyArgument; -import net.minecraft.commands.arguments.IdentifierArgument; +import net.minecraft.commands.arguments.ResourceOrTagKeyArgument; import net.minecraft.commands.arguments.ScoreboardSlotArgument; import net.minecraft.commands.arguments.StyleArgument; import net.minecraft.commands.arguments.TemplateMirrorArgument; @@ -407,7 +410,19 @@ public ArgumentType templateRotation() { public ArgumentType> resourceKey(final RegistryKey registryKey) { return this.wrap( ResourceKeyArgument.key(PaperRegistries.registryToNms(registryKey)), - nmsRegistryKey -> TypedKey.create(registryKey, CraftNamespacedKey.fromMinecraft(nmsRegistryKey.identifier())) + nmsRegistryKey -> TypedKey.create(registryKey, PaperAdventure.asAdventure(nmsRegistryKey.identifier())) + ); + } + + // expose for paper entity list command instead of regex? + public static ArgumentType, TagKey>> resourceKeyOrTagKey(final RegistryKey registryKey) { + return new NativeWrapperArgumentType<>( + ResourceOrTagKeyArgument.resourceOrTagKey(PaperRegistries.registryToNms(registryKey)), + result -> result.unwrap().mapBoth(key -> { + return TypedKey.create(registryKey, PaperAdventure.asAdventure(key.identifier())); + }, key -> { + return TagKey.create(registryKey, PaperAdventure.asAdventure(key.location())); + }) ); } diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java index d44a621549f3..3c5630ded3fe 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java @@ -18,7 +18,6 @@ import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.text.JoinConfiguration; import net.kyori.adventure.text.TextComponent; -import net.minecraft.commands.arguments.EntityArgument; import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.component.DataComponentType; @@ -33,7 +32,6 @@ import org.bukkit.craftbukkit.CraftRegistry; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.entity.Player; -import org.jspecify.annotations.NullMarked; import static java.util.Objects.requireNonNull; import static net.kyori.adventure.text.Component.join; @@ -48,28 +46,27 @@ import static net.kyori.adventure.text.format.TextColor.color; import static net.kyori.adventure.text.format.TextDecoration.ITALIC; -@NullMarked public final class DumpItemCommand { public static LiteralArgumentBuilder create() { return Commands.literal("dumpitem") .requires(PaperCommand.hasPermission("dumpitem")) + .executes(context -> { + if (!(context.getSource().getExecutor() instanceof Player player)) { + throw net.minecraft.commands.CommandSourceStack.ERROR_NOT_PLAYER.create(); + } + doDumpItem(player, false); + return Command.SINGLE_SUCCESS; + }) .then(Commands.literal("all") .executes(context -> { if (!(context.getSource().getExecutor() instanceof Player player)) { - throw EntityArgument.NO_PLAYERS_FOUND.create(); + throw net.minecraft.commands.CommandSourceStack.ERROR_NOT_PLAYER.create(); } doDumpItem(player, true); return Command.SINGLE_SUCCESS; }) - ) - .executes(context -> { - if (!(context.getSource().getExecutor() instanceof Player player)) { - throw EntityArgument.NO_PLAYERS_FOUND.create(); - } - doDumpItem(player, false); - return Command.SINGLE_SUCCESS; - }); + ); } @SuppressWarnings({"unchecked", "OptionalAssignedToNull", "rawtypes"}) diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java index 3e5bda7b2110..2cddf5e8d818 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java @@ -1,4 +1,3 @@ - package io.papermc.paper.command.subcommands; import com.destroystokyo.paper.util.SneakyThrow; @@ -13,12 +12,10 @@ import java.io.PrintWriter; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; -import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; -import java.util.Collections; -import java.util.Set; +import java.util.Map; import java.util.concurrent.CompletableFuture; import net.kyori.adventure.text.Component; import net.minecraft.server.MinecraftServer; @@ -28,22 +25,26 @@ import org.bukkit.plugin.Plugin; import org.bukkit.plugin.RegisteredListener; import org.bukkit.util.StringUtil; -import org.jspecify.annotations.NullMarked; 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; -@NullMarked public final class DumpListenersCommand { private static final String COMMAND_ARGUMENT_TO_FILE = "tofile"; private static final Component HELP_MESSAGE = text("Usage: /paper dumplisteners " + COMMAND_ARGUMENT_TO_FILE + "|", RED); private static final SuggestionProvider EVENT_SUGGESTIONS = (context, builder) -> CompletableFuture.supplyAsync(() -> { - eventClassNames().stream() - .filter(name -> StringUtil.startsWithIgnoreCase(name, builder.getRemaining()) || StringUtil.startsWithIgnoreCase(name.substring(name.lastIndexOf('.') + 1), builder.getRemaining())) + eventClassNames().entrySet().stream() + .filter(entry -> { + if (StringUtil.startsWithIgnoreCase(entry.getKey(), builder.getRemaining()) || StringUtil.startsWithIgnoreCase(entry.getKey().substring(entry.getKey().lastIndexOf('.') + 1), builder.getRemaining())) { + return entry.getValue().getRegisteredListeners().length != 0; + } + return false; + }) + .map(Map.Entry::getKey) .forEach(builder::suggest); return builder.build(); }); @@ -51,9 +52,8 @@ public final class DumpListenersCommand { private static final MethodHandle EVENT_TYPES_HANDLE; static { try { - final Field eventTypesField = HandlerList.class.getDeclaredField("EVENT_TYPES"); - eventTypesField.setAccessible(true); - EVENT_TYPES_HANDLE = MethodHandles.lookup().unreflectGetter(eventTypesField); + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(HandlerList.class, MethodHandles.lookup()); + EVENT_TYPES_HANDLE = lookup.findStaticGetter(HandlerList.class, "EVENT_TYPES", Map.class); } catch (final ReflectiveOperationException e) { throw new RuntimeException(e); } @@ -62,6 +62,10 @@ public final class DumpListenersCommand { public static LiteralArgumentBuilder create() { return Commands.literal("dumplisteners") .requires(PaperCommand.hasPermission("dumplisteners")) + .executes(context -> { + context.getSource().getSender().sendMessage(HELP_MESSAGE); + return Command.SINGLE_SUCCESS; + }) .then(Commands.literal(COMMAND_ARGUMENT_TO_FILE) .executes(context -> { return dumpToFile(context.getSource().getSender()); @@ -70,14 +74,9 @@ public static LiteralArgumentBuilder create() { .then(Commands.argument("event", StringArgumentType.word()) .suggests(EVENT_SUGGESTIONS) .executes(context -> { - doDumpListeners(context.getSource().getSender(), StringArgumentType.getString(context, "event")); - return Command.SINGLE_SUCCESS; + return doDumpListeners(context.getSource().getSender(), StringArgumentType.getString(context, "event")); }) - ) - .executes(context -> { - context.getSource().getSender().sendMessage(HELP_MESSAGE); - return Command.SINGLE_SUCCESS; - }); + ); } private static int dumpToFile(final CommandSender sender) { @@ -90,15 +89,10 @@ private static int dumpToFile(final CommandSender sender) { try { Files.createDirectories(path.getParent()); try (final PrintWriter writer = new PrintWriter(path.toFile())) { - for (final String eventClass : eventClassNames()) { - final HandlerList handlers; - try { - handlers = (HandlerList) findClass(eventClass).getMethod("getHandlerList").invoke(null); - } catch (final ReflectiveOperationException e) { - continue; - } + for (final Map.Entry entry : eventClassNames().entrySet()) { + final HandlerList handlers = entry.getValue(); if (handlers.getRegisteredListeners().length != 0) { - writer.println(eventClass); + writer.println(entry.getKey()); } for (final RegisteredListener registeredListener : handlers.getRegisteredListeners()) { writer.println(" - " + registeredListener); @@ -154,19 +148,19 @@ private static int doDumpListeners(final CommandSender sender, final String clas } @SuppressWarnings("unchecked") - private static Set eventClassNames() { + private static Map eventClassNames() { try { - return (Set) EVENT_TYPES_HANDLE.invokeExact(); + return (Map) EVENT_TYPES_HANDLE.invokeExact(); } catch (final Throwable e) { SneakyThrow.sneaky(e); - return Collections.emptySet(); // Unreachable + return Map.of(); // Unreachable } } private static Class findClass(final String className) throws ClassNotFoundException { try { return Class.forName(className); - } catch (final ClassNotFoundException ignore) { + } catch (final ClassNotFoundException ignored) { for (final Plugin plugin : Bukkit.getServer().getPluginManager().getPlugins()) { if (!plugin.isEnabled()) { continue; diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java index 2c4c726cd390..35feec976ebb 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java @@ -43,13 +43,11 @@ import net.minecraft.server.MinecraftServer; import org.bukkit.command.CommandSender; import org.bukkit.plugin.Plugin; -import org.jspecify.annotations.NullMarked; 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; -@NullMarked public final class DumpPluginsCommand { public static LiteralArgumentBuilder create() { diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java index 0b6b31f2e2ac..9f13016f4552 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java @@ -2,6 +2,7 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import io.papermc.paper.command.PaperCommand; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; @@ -16,6 +17,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; +import net.minecraft.commands.arguments.EntityArgument; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerChunkCache; @@ -28,22 +30,33 @@ import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.craftbukkit.CraftWorld; -import org.jspecify.annotations.NullMarked; 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; -@NullMarked public final class EntityCommand { private static final Component HELP_MESSAGE = text("Use /paper entity [list] help for more information on a specific command", RED); - private static final Component LIST_HELP_MESSAGE = text("Use /paper entity list [filter] [worldName] to get entity info that matches the optional filter.", RED); + private static final Component LIST_HELP_MESSAGE = text("Use /paper entity list [filter] [world_key] to get entity info that matches the optional filter.", RED); public static LiteralArgumentBuilder create() { return Commands.literal("entity") .requires(PaperCommand.hasPermission("entity")) + .executes(context -> { + context.getSource().getSender().sendMessage(HELP_MESSAGE); + return Command.SINGLE_SUCCESS; + }) + .then(Commands.literal("help") + .executes(context -> { + context.getSource().getSender().sendMessage(HELP_MESSAGE); + return Command.SINGLE_SUCCESS; + }) + ) .then(Commands.literal("list") + .executes(context -> { + return listEntities(context.getSource().getSender(), "*", context.getSource().getLocation().getWorld()); + }) .then(Commands.literal("help") .executes(context -> { context.getSource().getSender().sendMessage(LIST_HELP_MESSAGE); @@ -95,33 +108,20 @@ public static LiteralArgumentBuilder create() { }) ) ) - .executes(context -> { - return listEntities(context.getSource().getSender(), "*", context.getSource().getLocation().getWorld()); - }) - ) - .then(Commands.literal("help") - .executes(context -> { - context.getSource().getSender().sendMessage(HELP_MESSAGE); - return Command.SINGLE_SUCCESS; - }) - ) - .executes(context -> { - context.getSource().getSender().sendMessage(HELP_MESSAGE); - return Command.SINGLE_SUCCESS; - }); + ); } /* * Ported from MinecraftForge - author: LexManos - License: LGPLv2.1 */ - private static int listEntities(final CommandSender sender, final String filter, final World bukkitWorld) { + private static int listEntities(final CommandSender sender, final String filter, final World bukkitWorld) throws CommandSyntaxException { final String cleanfilter = filter.replace("?", ".?").replace("*", ".*?"); Set names = BuiltInRegistries.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)); + sender.sendMessage(text("Usage: /paper entity list [filter] [world_key]", RED)); return 0; } @@ -145,8 +145,7 @@ private static int listEntities(final CommandSender sender, final String filter, Pair> info = list.get(name); int nonTicking = nonEntityTicking.getOrDefault(name, 0); if (info == null) { - sender.sendMessage(text("No entities found.", RED)); - return Command.SINGLE_SUCCESS; + throw EntityArgument.NO_ENTITIES_FOUND.create(); } sender.sendMessage("Entity: " + name + " Total Ticking: " + (info.getLeft() - nonTicking) + ", Total Non-Ticking: " + nonTicking); List> entitiesPerChunk = info.getRight().entrySet().stream() @@ -169,8 +168,7 @@ private static int listEntities(final CommandSender sender, final String filter, .toList(); if (info.isEmpty()) { - sender.sendMessage(text("No entities found.", RED)); - return Command.SINGLE_SUCCESS; + throw EntityArgument.NO_ENTITIES_FOUND.create(); } int count = info.stream().mapToInt(Pair::getRight).sum(); diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java index 8f3bad7e52b3..bb4a4204e86e 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java @@ -9,14 +9,12 @@ import java.time.LocalDateTime; import org.bukkit.command.CommandSender; import org.bukkit.craftbukkit.CraftServer; -import org.jspecify.annotations.NullMarked; 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; -@NullMarked public final class HeapDumpCommand { public static LiteralArgumentBuilder create() { diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java index 034f5db306b1..f39737719431 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java @@ -31,11 +31,9 @@ import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; -import org.jspecify.annotations.NullMarked; import static net.kyori.adventure.text.Component.text; -@NullMarked public final class MobcapsCommand { static final Map MOB_CATEGORY_COLORS = Maps.immutableEnumMap(Util.make(new EnumMap<>(MobCategory.class), map -> { @@ -52,6 +50,9 @@ public final class MobcapsCommand { public static LiteralArgumentBuilder createGlobal() { return Commands.literal("mobcaps") .requires(PaperCommand.hasPermission("mobcaps")) + .executes(context -> { + return printMobcaps(context.getSource().getSender(), List.of(context.getSource().getLocation().getWorld())); + }) .then(Commands.literal("*") .executes(context -> { return printMobcaps(context.getSource().getSender(), Bukkit.getWorlds()); @@ -61,15 +62,18 @@ public static LiteralArgumentBuilder createGlobal() { .executes(context -> { return printMobcaps(context.getSource().getSender(), List.of(context.getArgument("world", World.class))); }) - ) - .executes(context -> { - return printMobcaps(context.getSource().getSender(), List.of(context.getSource().getLocation().getWorld())); - }); + ); } public static LiteralArgumentBuilder createPlayer() { return Commands.literal("playermobcaps") .requires(PaperCommand.hasPermission("playermobcaps")) + .executes(context -> { + if (!(context.getSource().getExecutor() instanceof Player player)) { + throw EntityArgument.NO_PLAYERS_FOUND.create(); + } + return printPlayerMobcaps(context.getSource().getSender(), player); + }) .then(Commands.argument("player", ArgumentTypes.player()) .executes(context -> { return printPlayerMobcaps( @@ -77,13 +81,7 @@ public static LiteralArgumentBuilder createPlayer() { context.getArgument("player", PlayerSelectorArgumentResolver.class).resolve(context.getSource()).getFirst() ); }) - ) - .executes(context -> { - if (!(context.getSource().getExecutor() instanceof Player player)) { - throw EntityArgument.NO_PLAYERS_FOUND.create(); - } - return printPlayerMobcaps(context.getSource().getSender(), player); - }); + ); } private static int printMobcaps(final CommandSender sender, final List worlds) { diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java index f57ddf634d2a..1ae07ffb60b2 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java @@ -7,14 +7,12 @@ import io.papermc.paper.command.brigadier.Commands; import net.minecraft.server.MinecraftServer; import org.bukkit.command.CommandSender; -import org.jspecify.annotations.NullMarked; 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 org.bukkit.command.Command.broadcastCommandMessage; -@NullMarked public final class ReloadCommand { public static LiteralArgumentBuilder create() { diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java index 3449f7f4e1b7..0ca3e5fc2aeb 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java @@ -18,29 +18,27 @@ import java.time.LocalDateTime; import net.minecraft.server.MinecraftServer; import org.bukkit.command.CommandSender; -import org.jspecify.annotations.NullMarked; import static net.kyori.adventure.text.Component.text; import static net.kyori.adventure.text.format.NamedTextColor.GOLD; import static net.kyori.adventure.text.format.NamedTextColor.GREEN; import static net.kyori.adventure.text.format.NamedTextColor.RED; -@NullMarked public final class SyncLoadInfoCommand { public static LiteralArgumentBuilder create() { return Commands.literal("syncloadinfo") .requires(PaperCommand.hasPermission("syncloadinfo").and($ -> SyncLoadFinder.ENABLED)) + .executes(context -> { + return dumpSyncLoadInfo(context.getSource().getSender()); + }) .then(Commands.literal("clear") .executes(context -> { SyncLoadFinder.clear(); context.getSource().getSender().sendMessage(text("Sync load data cleared.", GOLD)); return Command.SINGLE_SUCCESS; }) - ) - .executes(context -> { - return dumpSyncLoadInfo(context.getSource().getSender()); - }); + ); } private static int dumpSyncLoadInfo(final CommandSender sender) { diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java index e330c6770c41..e1104c1330b3 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java @@ -1,26 +1,16 @@ package io.papermc.paper.command.subcommands; -import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import io.papermc.paper.command.PaperCommand; +import io.papermc.paper.command.PaperVersionCommand; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; -import net.minecraft.server.MinecraftServer; -import org.jspecify.annotations.NullMarked; -@NullMarked public final class VersionCommand { public static LiteralArgumentBuilder create(String name) { return Commands.literal(name) .requires(PaperCommand.hasPermission("version")) - .executes(context -> { - final org.bukkit.command.Command redirect = MinecraftServer.getServer().server.getCommandMap().getCommand("version"); - if (redirect != null && redirect.execute(context.getSource().getSender(), "paper", new String[0])) { - return Command.SINGLE_SUCCESS; - } - - return 0; - }); + .executes(PaperVersionCommand::serverVersion); } } diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/package-info.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/package-info.java new file mode 100644 index 000000000000..f8198f5caacd --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package io.papermc.paper.command.subcommands; + +import org.jspecify.annotations.NullMarked; diff --git a/paper-server/src/main/java/org/spigotmc/TicksPerSecondCommand.java b/paper-server/src/main/java/org/spigotmc/TicksPerSecondCommand.java index 2756ca738b99..5eb0a3e02bcf 100644 --- a/paper-server/src/main/java/org/spigotmc/TicksPerSecondCommand.java +++ b/paper-server/src/main/java/org/spigotmc/TicksPerSecondCommand.java @@ -21,7 +21,7 @@ public class TicksPerSecondCommand extends Command { public TicksPerSecondCommand(String name) { super(name); this.description = "Gets the current ticks per second for the server"; - this.usageMessage = "/tps"; + this.usageMessage = "/tps [mem]"; this.setPermission("bukkit.command.tps"); } From bb8b86fe4d5e63ad3bb83a4c74205e118dc250dd Mon Sep 17 00:00:00 2001 From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> Date: Mon, 2 Feb 2026 21:42:16 +0100 Subject: [PATCH 8/9] use correct error message for playermobcaps and send message to sender instead of executor --- .../0001-Moonrise-optimisation-patches.patch | 13 +++++++------ .../paper/command/subcommands/DumpItemCommand.java | 11 ++++++----- .../paper/command/subcommands/MobcapsCommand.java | 3 +-- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch index 075bced4f834..c3c06ea9a182 100644 --- a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch @@ -22761,10 +22761,10 @@ index 0000000000000000000000000000000000000000..cc77c279492a0506e1b4d3447879a0b6 +} diff --git a/io/papermc/paper/command/subcommands/FixLightCommand.java b/io/papermc/paper/command/subcommands/FixLightCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..5ee1eb777e5382a2066ec73468ec4bac84db9673 +index 0000000000000000000000000000000000000000..ce31e1c2b1f7a03939f3a287b27db003d52a42da --- /dev/null +++ b/io/papermc/paper/command/subcommands/FixLightCommand.java -@@ -0,0 +1,109 @@ +@@ -0,0 +1,110 @@ +package io.papermc.paper.command.subcommands; + +import ca.spottedleaf.moonrise.patches.starlight.light.StarLightLightingProvider; @@ -22786,6 +22786,7 @@ index 0000000000000000000000000000000000000000..5ee1eb777e5382a2066ec73468ec4bac +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.status.ChunkStatus; ++import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; @@ -22809,7 +22810,7 @@ index 0000000000000000000000000000000000000000..5ee1eb777e5382a2066ec73468ec4bac + if (!(context.getSource().getExecutor() instanceof Player player)) { + throw net.minecraft.commands.CommandSourceStack.ERROR_NOT_PLAYER.create(); + } -+ doFixLight(player, 2); ++ doFixLight(player, 2, context.getSource().getSender()); + return Command.SINGLE_SUCCESS; + }) + .then(Commands.argument("radius", IntegerArgumentType.integer(0, MAX_RADIUS)) @@ -22817,14 +22818,14 @@ index 0000000000000000000000000000000000000000..5ee1eb777e5382a2066ec73468ec4bac + if (!(context.getSource().getExecutor() instanceof Player player)) { + throw net.minecraft.commands.CommandSourceStack.ERROR_NOT_PLAYER.create(); + } -+ doFixLight(player, IntegerArgumentType.getInteger(context, "radius")); ++ doFixLight(player, IntegerArgumentType.getInteger(context, "radius"), context.getSource().getSender()); + return Command.SINGLE_SUCCESS; + }) + ); + } + -+ private static void doFixLight(final Player sender, final int radius) { -+ ServerPlayer player = ((CraftPlayer) sender).getHandle(); ++ private static void doFixLight(final Player fromPlayer, final int radius, final CommandSender sender) { ++ ServerPlayer player = ((CraftPlayer) fromPlayer).getHandle(); + ServerLevel world = player.level(); + ThreadedLevelLightEngine lightEngine = world.getChunkSource().getLightEngine(); + starlightFixLight(player, world, lightEngine, radius, sender::sendMessage); diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java index 3c5630ded3fe..8ab8c25f9c79 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java @@ -29,6 +29,7 @@ import net.minecraft.nbt.Tag; import net.minecraft.resources.RegistryOps; import net.minecraft.world.item.ItemStack; +import org.bukkit.command.CommandSender; import org.bukkit.craftbukkit.CraftRegistry; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.entity.Player; @@ -55,7 +56,7 @@ public static LiteralArgumentBuilder create() { if (!(context.getSource().getExecutor() instanceof Player player)) { throw net.minecraft.commands.CommandSourceStack.ERROR_NOT_PLAYER.create(); } - doDumpItem(player, false); + doDumpItem(player, false, context.getSource().getSender()); return Command.SINGLE_SUCCESS; }) .then(Commands.literal("all") @@ -63,14 +64,14 @@ public static LiteralArgumentBuilder create() { if (!(context.getSource().getExecutor() instanceof Player player)) { throw net.minecraft.commands.CommandSourceStack.ERROR_NOT_PLAYER.create(); } - doDumpItem(player, true); + doDumpItem(player, true, context.getSource().getSender()); return Command.SINGLE_SUCCESS; }) ); } @SuppressWarnings({"unchecked", "OptionalAssignedToNull", "rawtypes"}) - private static void doDumpItem(final Player player, final boolean includeAllComponents) { + private static void doDumpItem(final Player player, final boolean includeAllComponents, final CommandSender sender) { final ItemStack itemStack = CraftItemStack.asNMSCopy(player.getInventory().getItemInMainHand()); final TextComponent.Builder visualOutput = text(); final StringBuilder itemCommandBuilder = new StringBuilder(); @@ -116,9 +117,9 @@ private static void doDumpItem(final Player player, final boolean includeAllComp .append(String.join(",", commandComponents)) .append("]"); } - player.sendMessage(visualOutput.build().compact()); + sender.sendMessage(visualOutput.build().compact()); final Component copyMsg = text("Click to copy item definition to clipboard for use with /give", GRAY, ITALIC); - player.sendMessage(copyMsg.clickEvent(copyToClipboard(itemCommandBuilder.toString()))); + sender.sendMessage(copyMsg.clickEvent(copyToClipboard(itemCommandBuilder.toString()))); } private static void writeComponentValue(final Consumer visualOutput, final Consumer commandOutput, final String path, final Tag serialized) { diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java index f39737719431..13221bcbb96f 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java @@ -18,7 +18,6 @@ import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextColor; -import net.minecraft.commands.arguments.EntityArgument; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; @@ -70,7 +69,7 @@ public static LiteralArgumentBuilder createPlayer() { .requires(PaperCommand.hasPermission("playermobcaps")) .executes(context -> { if (!(context.getSource().getExecutor() instanceof Player player)) { - throw EntityArgument.NO_PLAYERS_FOUND.create(); + throw net.minecraft.commands.CommandSourceStack.ERROR_NOT_PLAYER.create(); } return printPlayerMobcaps(context.getSource().getSender(), player); }) From a69c4f2aa03ca73ccef758ce1dd72a8ad6d6e5cc Mon Sep 17 00:00:00 2001 From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> Date: Sun, 15 Feb 2026 21:12:13 +0100 Subject: [PATCH 9/9] replace regex by entity type tags in entity command --- .../argument/VanillaArgumentProviderImpl.java | 18 ++-- .../command/subcommands/EntityCommand.java | 96 ++++--------------- 2 files changed, 27 insertions(+), 87 deletions(-) diff --git a/paper-server/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java b/paper-server/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java index 2c78c218f209..9e1f1ecacb2a 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java +++ b/paper-server/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java @@ -11,7 +11,6 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; -import com.mojang.datafixers.util.Either; import io.papermc.paper.adventure.PaperAdventure; import io.papermc.paper.command.brigadier.PaperCommands; import io.papermc.paper.command.brigadier.argument.predicate.BlockInWorldPredicate; @@ -36,7 +35,6 @@ import io.papermc.paper.registry.RegistryAccess; import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.TypedKey; -import io.papermc.paper.registry.tag.TagKey; import io.papermc.paper.util.MCUtil; import java.util.Collection; import java.util.Collections; @@ -69,7 +67,7 @@ import net.minecraft.commands.arguments.RangeArgument; import net.minecraft.commands.arguments.ResourceArgument; import net.minecraft.commands.arguments.ResourceKeyArgument; -import net.minecraft.commands.arguments.ResourceOrTagKeyArgument; +import net.minecraft.commands.arguments.ResourceOrTagArgument; import net.minecraft.commands.arguments.ScoreboardSlotArgument; import net.minecraft.commands.arguments.StyleArgument; import net.minecraft.commands.arguments.TemplateMirrorArgument; @@ -88,6 +86,8 @@ import net.minecraft.commands.arguments.item.ItemPredicateArgument; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; @@ -414,14 +414,12 @@ public ArgumentType> resourceKey(final RegistryKey registryKe ); } - // expose for paper entity list command instead of regex? - public static ArgumentType, TagKey>> resourceKeyOrTagKey(final RegistryKey registryKey) { + // expose to api? + public static ArgumentType>> resourceOrTag(final ResourceKey> registryKey) { return new NativeWrapperArgumentType<>( - ResourceOrTagKeyArgument.resourceOrTagKey(PaperRegistries.registryToNms(registryKey)), - result -> result.unwrap().mapBoth(key -> { - return TypedKey.create(registryKey, PaperAdventure.asAdventure(key.identifier())); - }, key -> { - return TagKey.create(registryKey, PaperAdventure.asAdventure(key.location())); + ResourceOrTagArgument.resourceOrTag(PaperCommands.INSTANCE.getBuildContext(), registryKey), + result -> result.unwrap().map(List::of, tag -> { + return tag.stream().toList(); }) ); } diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java index 9f13016f4552..73bc891c201f 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java @@ -1,24 +1,21 @@ package io.papermc.paper.command.subcommands; -import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.exceptions.CommandSyntaxException; import io.papermc.paper.command.PaperCommand; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; -import io.papermc.paper.command.brigadier.argument.RegistryArgumentExtractor; -import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.command.brigadier.argument.VanillaArgumentProviderImpl; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; -import net.kyori.adventure.text.event.HoverEvent; import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.core.Holder; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerLevel; @@ -26,56 +23,27 @@ import net.minecraft.world.level.ChunkPos; import org.apache.commons.lang3.tuple.MutablePair; import org.apache.commons.lang3.tuple.Pair; -import org.bukkit.HeightMap; import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.craftbukkit.CraftWorld; 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; public final class EntityCommand { - private static final Component HELP_MESSAGE = text("Use /paper entity [list] help for more information on a specific command", RED); - private static final Component LIST_HELP_MESSAGE = text("Use /paper entity list [filter] [world_key] to get entity info that matches the optional filter.", RED); - public static LiteralArgumentBuilder create() { return Commands.literal("entity") .requires(PaperCommand.hasPermission("entity")) - .executes(context -> { - context.getSource().getSender().sendMessage(HELP_MESSAGE); - return Command.SINGLE_SUCCESS; - }) - .then(Commands.literal("help") - .executes(context -> { - context.getSource().getSender().sendMessage(HELP_MESSAGE); - return Command.SINGLE_SUCCESS; - }) - ) .then(Commands.literal("list") .executes(context -> { - return listEntities(context.getSource().getSender(), "*", context.getSource().getLocation().getWorld()); + return listEntities(context.getSource().getSender(), BuiltInRegistries.ENTITY_TYPE.listElements().toList(), context.getSource().getLocation().getWorld()); }) - .then(Commands.literal("help") - .executes(context -> { - context.getSource().getSender().sendMessage(LIST_HELP_MESSAGE); - return Command.SINGLE_SUCCESS; - }) - ) - /* - .then(Commands.argument("filter", StringArgumentType.string()) - .suggests((context, builder) -> { - BuiltInRegistries.ENTITY_TYPE.keySet().stream() - .map(Object::toString) - .filter(type -> StringUtil.startsWithIgnoreCase(type, builder.getRemaining())) - .forEach(builder::suggest); - return builder.buildFuture(); - }) + .then(Commands.argument("entity-type", VanillaArgumentProviderImpl.resourceOrTag(Registries.ENTITY_TYPE)) .executes(context -> { return listEntities( context.getSource().getSender(), - StringArgumentType.getString(context, "filter"), + (List>>) context.getArgument("entity-type", List.class), context.getSource().getLocation().getWorld() ); }) @@ -83,26 +51,7 @@ public static LiteralArgumentBuilder create() { .executes(context -> { return listEntities( context.getSource().getSender(), - StringArgumentType.getString(context, "filter"), - context.getArgument("world", World.class) - ); - }) - ) - )*/ - // string argument is super strict for some reason for unquoted stuff this help mitigate it (ideally should allow entity type tags too then the regex can be dropped I think) - .then(Commands.argument("entity-type", ArgumentTypes.resourceKey(RegistryKey.ENTITY_TYPE)) - .executes(context -> { - return listEntities( - context.getSource().getSender(), - RegistryArgumentExtractor.getTypedKey(context, RegistryKey.ENTITY_TYPE, "entity-type").asString(), - context.getSource().getLocation().getWorld() - ); - }) - .then(Commands.argument("world", ArgumentTypes.world()) - .executes(context -> { - return listEntities( - context.getSource().getSender(), - RegistryArgumentExtractor.getTypedKey(context, RegistryKey.ENTITY_TYPE, "entity-type").asString(), + (List>>) context.getArgument("entity-type", List.class), context.getArgument("world", World.class) ); }) @@ -114,17 +63,7 @@ public static LiteralArgumentBuilder create() { /* * Ported from MinecraftForge - author: LexManos - License: LGPLv2.1 */ - private static int listEntities(final CommandSender sender, final String filter, final World bukkitWorld) throws CommandSyntaxException { - final String cleanfilter = filter.replace("?", ".?").replace("*", ".*?"); - Set names = BuiltInRegistries.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] [world_key]", RED)); - return 0; - } - + private static int listEntities(final CommandSender sender, final List>> entities, final World bukkitWorld) throws CommandSyntaxException { Map>> list = new HashMap<>(); ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); Map nonEntityTicking = new HashMap<>(); @@ -140,14 +79,14 @@ private static int listEntities(final CommandSender sender, final String filter, nonEntityTicking.merge(key, 1, Integer::sum); } }); - if (names.size() == 1) { - Identifier name = names.iterator().next(); + if (entities.size() == 1) { + Identifier name = entities.getFirst().unwrapKey().orElseThrow().identifier(); Pair> info = list.get(name); int nonTicking = nonEntityTicking.getOrDefault(name, 0); if (info == null) { throw EntityArgument.NO_ENTITIES_FOUND.create(); } - sender.sendMessage("Entity: " + name + " Total Ticking: " + (info.getLeft() - nonTicking) + ", Total Non-Ticking: " + nonTicking); + sender.sendPlainMessage("Entity: " + name + " Total Ticking: " + (info.getLeft() - nonTicking) + ", Total Non-Ticking: " + nonTicking); List> entitiesPerChunk = 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).toList(); @@ -155,12 +94,13 @@ private static int listEntities(final CommandSender sender, final String filter, final int x = (e.getKey().x << 4) + 8; final int z = (e.getKey().z << 4) + 8; final Component message = text(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z + (chunkProviderServer.isPositionTicking(e.getKey().toLong()) ? " (Ticking)" : " (Non-Ticking)")) - .hoverEvent(HoverEvent.showText(text("Click to teleport to chunk", GREEN))) - .clickEvent(ClickEvent.runCommand("/minecraft:execute in " + world.getWorld().getKey() + " run tp " + x + " " + (world.getWorld().getHighestBlockYAt(x, z, HeightMap.MOTION_BLOCKING) + 1) + " " + z)); + .hoverEvent(text("Click to teleport to chunk", GREEN)) + .clickEvent(ClickEvent.runCommand("/minecraft:execute in " + world.getWorld().getKey() + " run tp " + x + " ~ " + z)); sender.sendMessage(message); }); return entitiesPerChunk.size(); } else { + List names = entities.stream().map(type -> type.unwrapKey().orElseThrow().identifier()).toList(); List> info = list.entrySet().stream() .filter(e -> names.contains(e.getKey())) .map(e -> Pair.of(e.getKey(), e.getValue().left)) @@ -173,12 +113,14 @@ private static int listEntities(final CommandSender sender, final String filter, 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); + sender.sendPlainMessage("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(text(" " + (e.getValue() - nonTicking) + " (" + nonTicking + ") " + ": " + e.getKey()) + .hoverEvent(text("Click to inspect " + e.getKey(), GREEN)) + .clickEvent(ClickEvent.suggestCommand("/paper:paper entity list " + e.getKey()))); }); - sender.sendMessage("* First number is ticking entities, second number is non-ticking entities"); + sender.sendPlainMessage("* First number is ticking entities, second number is non-ticking entities"); return info.size(); } }