mirror of
https://github.com/PaperMC/Paper.git
synced 2024-11-30 04:02:50 +01:00
344 lines
17 KiB
Diff
344 lines
17 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Warrior <50800980+Warriorrrr@users.noreply.github.com>
|
|
Date: Tue, 25 Oct 2022 21:15:37 +0200
|
|
Subject: [PATCH] Add /paper dumplisteners command
|
|
|
|
Co-authored-by: TwoLeggedCat <80929284+TwoLeggedCat@users.noreply.github.com>
|
|
|
|
diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java
|
|
index c9bb2df0d884227576ed8d2e72219bbbd7ba827e..534d9c380f26d6cce3c99fa88ad2e15410535094 100644
|
|
--- a/src/main/java/io/papermc/paper/command/PaperCommand.java
|
|
+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
|
|
@@ -41,6 +41,7 @@ public final class PaperCommand extends Command {
|
|
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());
|
|
|
|
return commands.entrySet().stream()
|
|
.flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
|
|
diff --git a/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java b/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java
|
|
index efc4ec58ab4b3ceefd66b714d286a6187babc724..fae73bac86fea722be4c9178eda0779ea88d1884 100644
|
|
--- a/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java
|
|
+++ b/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java
|
|
@@ -1,19 +1,26 @@
|
|
package io.papermc.paper.command.subcommands;
|
|
|
|
import io.papermc.paper.adventure.PaperAdventure;
|
|
+import io.papermc.paper.command.CommandUtil;
|
|
import io.papermc.paper.command.PaperSubcommand;
|
|
import java.util.ArrayList;
|
|
+import java.util.Collections;
|
|
+import java.util.IdentityHashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
+import java.util.Set;
|
|
+import java.util.function.Consumer;
|
|
import net.kyori.adventure.text.Component;
|
|
import net.kyori.adventure.text.ComponentLike;
|
|
import net.kyori.adventure.text.JoinConfiguration;
|
|
import net.kyori.adventure.text.TextComponent;
|
|
import net.minecraft.core.Registry;
|
|
import net.minecraft.core.RegistryAccess;
|
|
+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.nbt.NbtOps;
|
|
import net.minecraft.nbt.NbtUtils;
|
|
@@ -25,6 +32,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 static net.kyori.adventure.text.Component.join;
|
|
@@ -36,17 +44,19 @@ import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
|
|
import static net.kyori.adventure.text.format.NamedTextColor.RED;
|
|
import static net.kyori.adventure.text.format.NamedTextColor.WHITE;
|
|
import static net.kyori.adventure.text.format.NamedTextColor.YELLOW;
|
|
+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);
|
|
+ this.doDumpItem(sender, args.length > 0 && "all".equals(args[0]));
|
|
return true;
|
|
}
|
|
|
|
- private void doDumpItem(final CommandSender sender) {
|
|
+ @SuppressWarnings({"unchecked", "OptionalAssignedToNull", "rawtypes"})
|
|
+ private void doDumpItem(final CommandSender sender, final boolean includeAllComponents) {
|
|
if (!(sender instanceof final Player player)) {
|
|
sender.sendMessage("Only players can use this command");
|
|
return;
|
|
@@ -57,38 +67,65 @@ public final class DumpItemCommand implements PaperSubcommand {
|
|
final String itemName = itemStack.getItemHolder().unwrapKey().orElseThrow().location().toString();
|
|
itemCommandBuilder.append(itemName);
|
|
visualOutput.append(text(itemName, YELLOW)); // item type
|
|
+ final Set<DataComponentType<?>> referencedComponentTypes = Collections.newSetFromMap(new IdentityHashMap<>());
|
|
final DataComponentPatch patch = itemStack.getComponentsPatch();
|
|
+ referencedComponentTypes.addAll(patch.entrySet().stream().map(Map.Entry::getKey).toList());
|
|
+ final DataComponentMap prototype = itemStack.getItem().components();
|
|
+ if (includeAllComponents) {
|
|
+ referencedComponentTypes.addAll(prototype.keySet());
|
|
+ }
|
|
|
|
final RegistryAccess.Frozen access = ((CraftServer) sender.getServer()).getServer().registryAccess();
|
|
final RegistryOps<Tag> ops = access.createSerializationContext(NbtOps.INSTANCE);
|
|
final Registry<DataComponentType<?>> registry = access.registryOrThrow(Registries.DATA_COMPONENT_TYPE);
|
|
- if (!patch.isEmpty()) {
|
|
- visualOutput.append(text("[", WHITE));
|
|
- itemCommandBuilder.append("[");
|
|
- final List<ComponentLike> componentComponents = new ArrayList<>();
|
|
- final List<String> commandComponents = new ArrayList<>();
|
|
- for (final Map.Entry<DataComponentType<?>, Optional<?>> entry : patch.entrySet()) {
|
|
- final String path = registry.getResourceKey(entry.getKey()).orElseThrow().location().getPath();
|
|
- if (entry.getValue().isEmpty()) {
|
|
+ final List<ComponentLike> componentComponents = new ArrayList<>();
|
|
+ final List<String> 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);
|
|
+ if (patchedValue != null) {
|
|
+ if (patchedValue.isEmpty()) {
|
|
componentComponents.add(text().append(text('!', RED), text(path, AQUA)));
|
|
commandComponents.add("!" + path);
|
|
} else {
|
|
- final Tag serialized = (Tag) ((DataComponentType) entry.getKey()).codecOrThrow().encodeStart(ops, entry.getValue().get()).getOrThrow();
|
|
- componentComponents.add(textOfChildren(
|
|
- text(path, AQUA),
|
|
- text("=", WHITE),
|
|
- PaperAdventure.asAdventure(NbtUtils.toPrettyComponent(serialized))
|
|
- ));
|
|
- commandComponents.add(path + "=" + serialized.getAsString());
|
|
+ final Tag serialized = (Tag) ((DataComponentType) type).codecOrThrow().encodeStart(ops, patchedValue.get()).getOrThrow();
|
|
+ writeComponentValue(componentComponents::add, commandComponents::add, path, serialized);
|
|
}
|
|
-
|
|
+ } else if (includeAllComponents && prototypeValue != null) {
|
|
+ final Tag serialized = prototypeValue.encodeValue(ops).getOrThrow();
|
|
+ writeComponentValue(componentComponents::add, commandComponents::add, path, serialized);
|
|
}
|
|
- visualOutput
|
|
- .append(join(JoinConfiguration.separator(text(",", WHITE)), componentComponents))
|
|
- .append(text("]", WHITE));
|
|
- itemCommandBuilder.append(String.join(",", commandComponents)).append("]");
|
|
+ }
|
|
+ if (!componentComponents.isEmpty()) {
|
|
+ visualOutput.append(
|
|
+ text("[", color(0x8910CE)),
|
|
+ join(JoinConfiguration.separator(text(",", GRAY)), componentComponents),
|
|
+ text("]", color(0x8910CE))
|
|
+ );
|
|
+ itemCommandBuilder
|
|
+ .append("[")
|
|
+ .append(String.join(",", commandComponents))
|
|
+ .append("]");
|
|
}
|
|
final Component hoverMsg = text("Click to copy item definition to clipboard for use with /pgive", GRAY, ITALIC);
|
|
player.sendMessage(visualOutput.build().compact().hoverEvent(hoverMsg).clickEvent(copyToClipboard(itemCommandBuilder.toString())));
|
|
}
|
|
+
|
|
+ private static void writeComponentValue(final Consumer<Component> visualOutput, final Consumer<String> commandOutput, final String path, final Tag serialized) {
|
|
+ visualOutput.accept(textOfChildren(
|
|
+ text(path, color(0xFF7FD7)),
|
|
+ text("=", WHITE),
|
|
+ PaperAdventure.asAdventure(NbtUtils.toPrettyComponent(serialized))
|
|
+ ));
|
|
+ commandOutput.accept(path + "=" + serialized.getAsString());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public List<String> 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/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java b/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..aa44d4685de3caee4131449bead7a084868ff976
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java
|
|
@@ -0,0 +1,172 @@
|
|
+package io.papermc.paper.command.subcommands;
|
|
+
|
|
+import com.destroystokyo.paper.util.SneakyThrow;
|
|
+import io.papermc.paper.command.PaperSubcommand;
|
|
+import java.io.File;
|
|
+import java.io.IOException;
|
|
+import java.io.PrintWriter;
|
|
+import java.lang.invoke.MethodHandle;
|
|
+import java.lang.invoke.MethodHandles;
|
|
+import java.lang.reflect.Field;
|
|
+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 net.kyori.adventure.text.Component;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import org.bukkit.Bukkit;
|
|
+import org.bukkit.command.CommandSender;
|
|
+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.framework.qual.DefaultQualifier;
|
|
+
|
|
+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;
|
|
+
|
|
+@DefaultQualifier(NonNull.class)
|
|
+public final class DumpListenersCommand implements PaperSubcommand {
|
|
+ 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);
|
|
+ } catch (final ReflectiveOperationException e) {
|
|
+ throw new RuntimeException(e);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
|
|
+ if (args.length >= 1 && args[0].equals("tofile")) {
|
|
+ this.dumpToFile(sender);
|
|
+ return true;
|
|
+ }
|
|
+ this.doDumpListeners(sender, args);
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ private void dumpToFile(final CommandSender sender) {
|
|
+ final File file = new File("debug/listeners-"
|
|
+ + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt");
|
|
+ file.getParentFile().mkdirs();
|
|
+ try (final PrintWriter writer = new PrintWriter(file)) {
|
|
+ for (final String eventClass : eventClassNames()) {
|
|
+ final HandlerList handlers;
|
|
+ try {
|
|
+ handlers = (HandlerList) findClass(eventClass).getMethod("getHandlerList").invoke(null);
|
|
+ } catch (final ReflectiveOperationException e) {
|
|
+ continue;
|
|
+ }
|
|
+ if (handlers.getRegisteredListeners().length != 0) {
|
|
+ writer.println(eventClass);
|
|
+ }
|
|
+ for (final RegisteredListener registeredListener : handlers.getRegisteredListeners()) {
|
|
+ writer.println(" - " + registeredListener);
|
|
+ }
|
|
+ }
|
|
+ } catch (final IOException ex) {
|
|
+ throw new RuntimeException(ex);
|
|
+ }
|
|
+ sender.sendMessage(text("Dumped listeners to " + file, GREEN));
|
|
+ }
|
|
+
|
|
+ private void doDumpListeners(final CommandSender sender, final String[] args) {
|
|
+ if (args.length == 0) {
|
|
+ sender.sendMessage(text("Usage: /paper dumplisteners tofile|<className>", RED));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ final HandlerList handlers = (HandlerList) findClass(args[0]).getMethod("getHandlerList").invoke(null);
|
|
+
|
|
+ if (handlers.getRegisteredListeners().length == 0) {
|
|
+ sender.sendMessage(text(args[0] + " does not have any registered listeners."));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ sender.sendMessage(text("Listeners for " + args[0] + ":"));
|
|
+
|
|
+ for (final RegisteredListener listener : handlers.getRegisteredListeners()) {
|
|
+ final Component hoverText = text("Priority: " + listener.getPriority().name() + " (" + listener.getPriority().getSlot() + ")", WHITE)
|
|
+ .append(newline())
|
|
+ .append(text("Listener: " + listener.getListener()))
|
|
+ .append(newline())
|
|
+ .append(text("Executor: " + listener.getExecutor()))
|
|
+ .append(newline())
|
|
+ .append(text("Ignoring cancelled: " + listener.isIgnoringCancelled()));
|
|
+
|
|
+ sender.sendMessage(text(listener.getPlugin().getName(), GREEN)
|
|
+ .append(space())
|
|
+ .append(text("(" + listener.getListener().getClass().getName() + ")", GRAY).hoverEvent(hoverText)));
|
|
+ }
|
|
+
|
|
+ sender.sendMessage(text("Total listeners: " + handlers.getRegisteredListeners().length));
|
|
+
|
|
+ } catch (final ClassNotFoundException e) {
|
|
+ sender.sendMessage(text("Unable to find a class named '" + args[0] + "'. Make sure to use the fully qualified name.", RED));
|
|
+ } catch (final NoSuchMethodException e) {
|
|
+ sender.sendMessage(text("Class '" + args[0] + "' does not have a valid getHandlerList method.", RED));
|
|
+ } catch (final ReflectiveOperationException e) {
|
|
+ sender.sendMessage(text("Something went wrong, see the console for more details.", RED));
|
|
+ MinecraftServer.LOGGER.warn("Error occurred while dumping listeners for class " + args[0], e);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public List<String> 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<String> suggestions() {
|
|
+ final List<String> ret = new ArrayList<>();
|
|
+ ret.add("tofile");
|
|
+ ret.addAll(eventClassNames());
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("unchecked")
|
|
+ private static Set<String> eventClassNames() {
|
|
+ try {
|
|
+ return (Set<String>) EVENT_TYPES_HANDLE.invokeExact();
|
|
+ } catch (final Throwable e) {
|
|
+ SneakyThrow.sneaky(e);
|
|
+ return Collections.emptySet(); // Unreachable
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static Class<?> findClass(final String className) throws ClassNotFoundException {
|
|
+ try {
|
|
+ return Class.forName(className);
|
|
+ } catch (final ClassNotFoundException ignore) {
|
|
+ for (final Plugin plugin : Bukkit.getServer().getPluginManager().getPlugins()) {
|
|
+ if (!plugin.isEnabled()) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ return Class.forName(className, false, plugin.getClass().getClassLoader());
|
|
+ } catch (final ClassNotFoundException ignore0) {
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ throw new ClassNotFoundException(className);
|
|
+ }
|
|
+}
|