Paper/patches/server/0011-Paper-command.patch
Jake Potrebic 2fa8efce9b
Updated Upstream (Bukkit/CraftBukkit) (#9485)
Upstream has released updates that appear to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

Bukkit Changes:
82af5dc6 SPIGOT-7396: Add PlayerSignOpenEvent
3f0281ca SPIGOT-7063, PR-763: Add DragonBattle#initiateRespawn with custom EnderCrystals
f83c8df4 PR-873: Add PlayerRecipeBookClickEvent
14560d39 SPIGOT-7435: Add TeleportCause#EXIT_BED
2cc6db92 SPIGOT-7422, PR-887: Add API to set sherds on decorated pots
36022f02 PR-883: Add ItemFactory#getSpawnEgg
12eb5c46 PR-881: Update Scoreboard Javadocs, remove explicit exception throwing
f6d8d44a PR-882: Add modern time API methods to ban API
21a7b710 Upgrade some Maven plugins to reduce warnings
11fd1225 PR-886: Deprecate the SmithingRecipe constructor as it now does nothing
dbd1761d SPIGOT-7406: Improve documentation for getDragonBattle

CraftBukkit Changes:
d548daac2 SPIGOT-7446: BlockState#update not updating a spawner's type to null
70e0bc050 SPIGOT-7447: Fix --forceUpgrade
6752f1d63 SPIGOT-7396: Add PlayerSignOpenEvent
847b4cad5 SPIGOT-7063, PR-1071: Add DragonBattle#initiateRespawn with custom EnderCrystals
c335a555f PR-1212: Add PlayerRecipeBookClickEvent
4be756ecb SPIGOT-7445: Fix opening smithing inventory
db70bd6ed SPIGOT-7441: Fix issue placing certain items in creative/op
f7fa6d993 SPIGOT-7435: Add TeleportCause#EXIT_BED
b435e8e8d SPIGOT-7349: Player#setDisplayName not working when message/format unmodified
a2fafdd1d PR-1232: Re-add fix for player rotation
7cf863de1 PR-1233: Remove some old MC bug fixes now fixed in vanilla
08ec344ad Fix ChunkGenerator#generateCaves never being called
5daeb502a SPIGOT-7422, PR-1228: Add API to set sherds on decorated pots
52faa6b32 PR-1224: Add ItemFactory#getSpawnEgg
01cae71b7 SPIGOT-7429: Fix LEFT_CLICK_AIR not working for passable entities and spectators
a94277a18 PR-1223: Remove non-existent scoreboard display name/prefix/suffix limits
36b107660 PR-1225: Add modern time API methods to ban API
59ead25bc Upgrade some Maven plugins to reduce warnings
202fc5c4e Increase outdated build delay
ce545de57 SPIGOT-7398: TextDisplay#setInterpolationDuration incorrectly updates the line width

Spigot Changes:
b41c46db Rebuild patches
3374045a SPIGOT-7431: Fix EntityMountEvent returning opposite entities
0ca4eb66 Rebuild patches
2023-08-05 17:21:59 -07:00

664 lines
30 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Mon, 29 Feb 2016 21:02:09 -0600
Subject: [PATCH] Paper command
Co-authored-by: Zach Brown <zach.brown@destroystokyo.com>
diff --git a/src/main/java/io/papermc/paper/command/CommandUtil.java b/src/main/java/io/papermc/paper/command/CommandUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..953c30500892e5f0c55b8597bc708ea85bf56d6e
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/CommandUtil.java
@@ -0,0 +1,69 @@
+package io.papermc.paper.command;
+
+import com.google.common.base.Functions;
+import com.google.common.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<String> 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<String> getListMatchingLast(
+ final CommandSender sender,
+ final String[] strings,
+ final Collection<?> collection
+ ) {
+ String last = strings[strings.length - 1];
+ ArrayList<String> 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/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..e7931b6cb2a46d07ac017f620b807e3474dc481e
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
@@ -0,0 +1,144 @@
+package io.papermc.paper.command;
+
+import io.papermc.paper.command.subcommands.*;
+import it.unimi.dsi.fastutil.Pair;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import net.minecraft.Util;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.permissions.Permission;
+import org.bukkit.permissions.PermissionDefault;
+import org.bukkit.plugin.PluginManager;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
+
+@DefaultQualifier(NonNull.class)
+public final class PaperCommand extends Command {
+ static final String BASE_PERM = "bukkit.command.paper.";
+ // subcommand label -> subcommand
+ private static final Map<String, PaperSubcommand> SUBCOMMANDS = Util.make(() -> {
+ final Map<Set<String>, 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("callback"), new CallbackCommand());
+
+ 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<String> COMPLETABLE_SUBCOMMANDS = SUBCOMMANDS.entrySet().stream().filter(entry -> entry.getValue().tabCompletes()).map(Map.Entry::getKey).collect(Collectors.toSet());
+ // alias -> subcommand label
+ private static final Map<String, String> ALIASES = Util.make(() -> {
+ final Map<String, Set<String>> aliases = new HashMap<>();
+
+ aliases.put("version", Set.of("ver"));
+
+ return aliases.entrySet().stream()
+ .flatMap(entry -> entry.getValue().stream().map(s -> Map.entry(s, entry.getKey())))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ });
+
+ public PaperCommand(final String name) {
+ super(name);
+ this.description = "Paper related commands";
+ this.usageMessage = "/paper [" + String.join(" | ", SUBCOMMANDS.keySet()) + "]";
+ final List<String> permissions = new ArrayList<>();
+ permissions.add("bukkit.command.paper");
+ permissions.addAll(SUBCOMMANDS.keySet().stream().map(s -> BASE_PERM + s).toList());
+ this.setPermission(String.join(";", permissions));
+ final PluginManager pluginManager = Bukkit.getServer().getPluginManager();
+ for (final String perm : permissions) {
+ pluginManager.addPermission(new Permission(perm, PermissionDefault.OP));
+ }
+ }
+
+ private static boolean testPermission(final CommandSender sender, final String permission) {
+ if (sender.hasPermission(BASE_PERM + permission) || sender.hasPermission("bukkit.command.paper")) {
+ return true;
+ }
+ sender.sendMessage(text("I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.", RED));
+ return false;
+ }
+
+ @Override
+ public List<String> 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<String, PaperSubcommand> subCommand = resolveCommand(args[0]);
+ if (subCommand != null) {
+ return subCommand.second().tabComplete(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length));
+ }
+
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean execute(
+ final CommandSender sender,
+ final String commandLabel,
+ final String[] args
+ ) {
+ if (!testPermission(sender)) {
+ return true;
+ }
+
+ if (args.length == 0) {
+ sender.sendMessage(text("Usage: " + this.usageMessage, RED));
+ return false;
+ }
+ final @Nullable Pair<String, PaperSubcommand> subCommand = resolveCommand(args[0]);
+
+ if (subCommand == null) {
+ sender.sendMessage(text("Usage: " + this.usageMessage, RED));
+ return false;
+ }
+
+ if (!testPermission(sender, subCommand.first())) {
+ return true;
+ }
+ final String[] choppedArgs = Arrays.copyOfRange(args, 1, args.length);
+ return subCommand.second().execute(sender, subCommand.first(), choppedArgs);
+ }
+
+ private static @Nullable Pair<String, PaperSubcommand> resolveCommand(String label) {
+ label = label.toLowerCase(Locale.ENGLISH);
+ @Nullable PaperSubcommand subCommand = SUBCOMMANDS.get(label);
+ if (subCommand == null) {
+ final @Nullable String command = ALIASES.get(label);
+ if (command != null) {
+ label = command;
+ subCommand = SUBCOMMANDS.get(command);
+ }
+ }
+
+ if (subCommand != null) {
+ return Pair.of(label, subCommand);
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java
new file mode 100644
index 0000000000000000000000000000000000000000..6a00f3d38da8107825ab1d405f337fd077b09f72
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/PaperCommands.java
@@ -0,0 +1,27 @@
+package io.papermc.paper.command;
+
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.command.Command;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public final class PaperCommands {
+
+ private PaperCommands() {
+ }
+
+ private static final Map<String, Command> COMMANDS = new HashMap<>();
+ static {
+ COMMANDS.put("paper", new PaperCommand("paper"));
+ }
+
+ public static void registerCommands(final MinecraftServer server) {
+ COMMANDS.forEach((s, command) -> {
+ server.server.getCommandMap().register(s, "Paper", command);
+ });
+ }
+}
diff --git a/src/main/java/io/papermc/paper/command/PaperSubcommand.java b/src/main/java/io/papermc/paper/command/PaperSubcommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..7e9e0ff8639be135bf8575e375cbada5b57164e1
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/PaperSubcommand.java
@@ -0,0 +1,20 @@
+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<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
+ return Collections.emptyList();
+ }
+
+ default boolean tabCompletes() {
+ return true;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/command/subcommands/CallbackCommand.java b/src/main/java/io/papermc/paper/command/subcommands/CallbackCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..c5111eef4ba85ed96f7496b7db6954106fa05053
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/subcommands/CallbackCommand.java
@@ -0,0 +1,33 @@
+package io.papermc.paper.command.subcommands;
+
+import io.papermc.paper.adventure.providers.ClickCallbackProviderImpl;
+import io.papermc.paper.command.PaperSubcommand;
+import java.util.UUID;
+import org.bukkit.command.CommandSender;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public final class CallbackCommand implements PaperSubcommand {
+ @Override
+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
+ if (args.length != 1) {
+ return false;
+ }
+
+ final UUID id;
+ try {
+ id = UUID.fromString(args[0]);
+ } catch (final IllegalArgumentException ignored) {
+ return false;
+ }
+
+ ClickCallbackProviderImpl.CALLBACK_MANAGER.runCallback(sender, id);
+ return true;
+ }
+
+ @Override
+ public boolean tabCompletes() {
+ return false;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff99336e0b8131ae161cfa5c4fc83c6905e3dbc8
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java
@@ -0,0 +1,158 @@
+package io.papermc.paper.command.subcommands;
+
+import com.google.common.collect.Maps;
+import io.papermc.paper.command.CommandUtil;
+import io.papermc.paper.command.PaperSubcommand;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.event.ClickEvent;
+import net.kyori.adventure.text.event.HoverEvent;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.level.ServerChunkCache;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.level.ChunkPos;
+import org.apache.commons.lang3.tuple.MutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.bukkit.Bukkit;
+import org.bukkit.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 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;
+ }
+
+ @Override
+ public List<String> 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().stream().map(ResourceLocation::toString).sorted().toArray(String[]::new));
+ }
+ return Collections.emptyList();
+ }
+
+ /*
+ * Ported from MinecraftForge - author: LexManos <LexManos@gmail.com> - License: LGPLv2.1
+ */
+ private void listEntities(final CommandSender sender, final String[] args) {
+ // help
+ if (args.length < 1 || !args[0].toLowerCase(Locale.ENGLISH).equals("list")) {
+ sender.sendMessage(text("Use /paper entity [list] help for more information on a specific command", RED));
+ return;
+ }
+
+ if ("list".equals(args[0].toLowerCase(Locale.ENGLISH))) {
+ String filter = "*";
+ if (args.length > 1) {
+ if (args[1].toLowerCase(Locale.ENGLISH).equals("help")) {
+ sender.sendMessage(text("Use /paper entity list [filter] [worldName] to get entity info that matches the optional filter.", RED));
+ return;
+ }
+ filter = args[1];
+ }
+ final String cleanfilter = filter.replace("?", ".?").replace("*", ".*?");
+ Set<ResourceLocation> 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;
+ }
+ String worldName;
+ if (args.length > 2) {
+ worldName = args[2];
+ } else if (sender instanceof Player) {
+ worldName = ((Player) sender).getWorld().getName();
+ } else {
+ sender.sendMessage(text("Please specify the name of a world", RED));
+ sender.sendMessage(text("To do so without a filter, specify '*' as the filter", RED));
+ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED));
+ return;
+ }
+ Map<ResourceLocation, MutablePair<Integer, Map<ChunkPos, Integer>>> list = Maps.newHashMap();
+ @Nullable World bukkitWorld = Bukkit.getWorld(worldName);
+ if (bukkitWorld == null) {
+ sender.sendMessage(text("Could not load world for " + worldName + ". Please select a valid world.", RED));
+ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED));
+ return;
+ }
+ ServerLevel world = ((CraftWorld) bukkitWorld).getHandle();
+ Map<ResourceLocation, Integer> nonEntityTicking = Maps.newHashMap();
+ ServerChunkCache chunkProviderServer = world.getChunkSource();
+ world.getAllEntities().forEach(e -> {
+ ResourceLocation key = EntityType.getKey(e.getType());
+
+ MutablePair<Integer, Map<ChunkPos, Integer>> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap()));
+ ChunkPos chunk = e.chunkPosition();
+ info.left++;
+ info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1);
+ if (!chunkProviderServer.isPositionTicking(e)) {
+ nonEntityTicking.merge(key, 1, Integer::sum);
+ }
+ });
+ if (names.size() == 1) {
+ ResourceLocation name = names.iterator().next();
+ Pair<Integer, Map<ChunkPos, Integer>> 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<Pair<ResourceLocation, Integer>> info = list.entrySet().stream()
+ .filter(e -> names.contains(e.getKey()))
+ .map(e -> Pair.of(e.getKey(), e.getValue().left))
+ .sorted((a, b) -> !a.getRight().equals(b.getRight()) ? b.getRight() - a.getRight() : a.getKey().toString().compareTo(b.getKey().toString()))
+ .toList();
+
+ if (info.isEmpty()) {
+ sender.sendMessage(text("No entities found.", RED));
+ return;
+ }
+
+ int count = info.stream().mapToInt(Pair::getRight).sum();
+ int nonTickingCount = nonEntityTicking.values().stream().mapToInt(Integer::intValue).sum();
+ sender.sendMessage("Total Ticking: " + (count - nonTickingCount) + ", Total Non-Ticking: " + nonTickingCount);
+ info.forEach(e -> {
+ int nonTicking = nonEntityTicking.getOrDefault(e.getKey(), 0);
+ sender.sendMessage(" " + (e.getValue() - nonTicking) + " (" + nonTicking + ") " + ": " + e.getKey());
+ });
+ sender.sendMessage("* First number is ticking entities, second number is non-ticking entities");
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java b/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..cd2e4d792e972b8bf1e07b8961594a670ae949cf
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java
@@ -0,0 +1,38 @@
+package io.papermc.paper.command.subcommands;
+
+import io.papermc.paper.command.PaperSubcommand;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.craftbukkit.CraftServer;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
+import static net.kyori.adventure.text.format.NamedTextColor.YELLOW;
+
+@DefaultQualifier(NonNull.class)
+public final class HeapDumpCommand implements PaperSubcommand {
+ @Override
+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
+ this.dumpHeap(sender);
+ return true;
+ }
+
+ private void dumpHeap(final CommandSender sender) {
+ java.nio.file.Path dir = java.nio.file.Paths.get("./dumps");
+ String name = "heap-dump-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now());
+
+ Command.broadcastCommandMessage(sender, text("Writing JVM heap data...", YELLOW));
+
+ java.nio.file.Path file = CraftServer.dumpHeap(dir, name);
+ if (file != null) {
+ Command.broadcastCommandMessage(sender, text("Heap dump saved to " + file, GREEN));
+ } else {
+ Command.broadcastCommandMessage(sender, text("Failed to write heap dump, see server log for details", RED));
+ }
+ }
+}
diff --git a/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..bd68139ae635f2ad7ec8e7a21e0056a139c4c62e
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java
@@ -0,0 +1,33 @@
+package io.papermc.paper.command.subcommands;
+
+import io.papermc.paper.command.PaperSubcommand;
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.craftbukkit.CraftServer;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
+
+@DefaultQualifier(NonNull.class)
+public final class ReloadCommand implements PaperSubcommand {
+ @Override
+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
+ this.doReload(sender);
+ return true;
+ }
+
+ private void doReload(final CommandSender sender) {
+ Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", RED));
+ Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", RED));
+
+ MinecraftServer server = ((CraftServer) sender.getServer()).getServer();
+ server.paperConfigurations.reloadConfigs(server);
+ server.server.reloadCount++;
+
+ Command.broadcastCommandMessage(sender, text("Paper config reload complete.", GREEN));
+ }
+}
diff --git a/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java b/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..ae60bd96b5284d54676d8e7e4dd5d170b526ec1e
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java
@@ -0,0 +1,21 @@
+package io.papermc.paper.command.subcommands;
+
+import io.papermc.paper.command.PaperSubcommand;
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public final class VersionCommand implements PaperSubcommand {
+ @Override
+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
+ final @Nullable Command ver = MinecraftServer.getServer().server.getCommandMap().getCommand("version");
+ if (ver != null) {
+ ver.execute(sender, "paper", new String[0]);
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index 2ac30ef2dd72582f2b7f6a05b1f94b12b44fafc9..a01d423f44b3e547b4fa4d6654f9df636d69525f 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -186,6 +186,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
// Paper start
paperConfigurations.initializeGlobalConfiguration();
paperConfigurations.initializeWorldDefaultsConfiguration();
+ io.papermc.paper.command.PaperCommands.registerCommands(this);
// Paper end
this.setPvpAllowed(dedicatedserverproperties.pvp);
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index fef313265a5e3f2aea5ed8f767782f4157ec7435..06cfbdfaa849cac2decf8900bb0dc28a11f3fead 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -938,6 +938,7 @@ public final class CraftServer implements Server {
this.commandMap.clearCommands();
this.reloadData();
org.spigotmc.SpigotConfig.registerCommands(); // Spigot
+ io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper
this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*");
this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions");
@@ -2531,6 +2532,34 @@ public final class CraftServer implements Server {
// Paper end
// Paper start
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public static java.nio.file.Path dumpHeap(java.nio.file.Path dir, String name) {
+ try {
+ java.nio.file.Files.createDirectories(dir);
+
+ javax.management.MBeanServer server = java.lang.management.ManagementFactory.getPlatformMBeanServer();
+ java.nio.file.Path file;
+
+ try {
+ Class clazz = Class.forName("openj9.lang.management.OpenJ9DiagnosticsMXBean");
+ Object openj9Mbean = java.lang.management.ManagementFactory.newPlatformMXBeanProxy(server, "openj9.lang.management:type=OpenJ9Diagnostics", clazz);
+ java.lang.reflect.Method m = clazz.getMethod("triggerDumpToFile", String.class, String.class);
+ file = dir.resolve(name + ".phd");
+ m.invoke(openj9Mbean, "heap", file.toString());
+ } catch (ClassNotFoundException e) {
+ Class clazz = Class.forName("com.sun.management.HotSpotDiagnosticMXBean");
+ Object hotspotMBean = java.lang.management.ManagementFactory.newPlatformMXBeanProxy(server, "com.sun.management:type=HotSpotDiagnostic", clazz);
+ java.lang.reflect.Method m = clazz.getMethod("dumpHeap", String.class, boolean.class);
+ file = dir.resolve(name + ".hprof");
+ m.invoke(hotspotMBean, file.toString(), true);
+ }
+
+ return file;
+ } catch (Throwable t) {
+ Bukkit.getLogger().log(Level.SEVERE, "Could not write heap", t);
+ return null;
+ }
+ }
private Iterable<? extends net.kyori.adventure.audience.Audience> adventure$audiences;
@Override
public Iterable<? extends net.kyori.adventure.audience.Audience> audiences() {