8000 Register commands as plugin commands on Paper 1.20.6 and above by DerEchtePilz · Pull Request #629 · CommandAPI/CommandAPI · GitHub
[go: up one dir, main page]

Skip to content

Register commands as plugin commands on Paper 1.20.6 and above #629

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ This is the current roadmap for the CommandAPI (as of 30th April 2024):
</ul>
<b>Bug Fixes:</b>
<ul>
<li>https://github.com/CommandAPI/CommandAPI/issues/578, https://github.com/CommandAPI/CommandAPI/issues/583, https://github.com/CommandAPI/CommandAPI/pull/629 Fixes <code>Bukkit#dispatchCommand()</code> not working after Paper's Brigadier API changes</li>
<li>Fixes <code>PotionEffectArgument.NamespacedKey</code> not having suggestions in some versions</li>
</ul>
</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ public record RegisteredCommand(
* @return The namespace for this command
*/
String namespace) {

public boolean shouldGenerateHelpTopic() {
return fullDescription.isPresent() || usageDescription.isPresent() || helpTopic.isPresent();
}

// As https://stackoverflow.com/a/32083420 mentions, Optional's hashCode, equals, and toString method don't work if the
// Optional wraps an array, like `Optional<String[]> usageDescription`, so we have to use the Arrays methods ourselves

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,14 +277,12 @@ private String[] getUsageList(RegisteredCommand currentCommand) {

void updateHelpForCommands(List<RegisteredCommand> commands) {
Map<String, HelpTopic> helpTopicsToAdd = new HashMap<>();
Set<String> namespacedCommandNames = new HashSet<>();

for (RegisteredCommand command : commands) {
if (getPaper().isPaperBrigAPI() && !command.shouldGenerateHelpTopic()) continue;

// Don't override the plugin help topic
String commandPrefix = generateCommandHelpPrefix(command.commandName());

// Namespaced commands shouldn't have a help topic, we should save the namespaced command name
namespacedCommandNames.add(generateCommandHelpPrefix(command.commandName(), command.namespace()));

StringBuilder aliasSb = new StringBuilder();
final String shortDescription;
Expand Down Expand Up @@ -347,21 +345,13 @@ void updateHelpForCommands(List<RegisteredCommand> commands) {
// Don't override the plugin help topic
commandPrefix = generateCommandHelpPrefix(alias);
helpTopic = generateHelpTopic(commandPrefix, shortDescription, currentAliasSb.toString().trim(), permission);

// Namespaced commands shouldn't have a help topic, we should save the namespaced alias name
namespacedCommandNames.add(generateCommandHelpPrefix(alias, command.namespace()));
}
helpTopicsToAdd.put(commandPrefix, helpTopic);
}
}

// We have to use helpTopics.put (instead of .addTopic) because we're overwriting an existing help topic, not adding a new help topic
getHelpMap().putAll(helpTopicsToAdd);

// We also have to remove help topics for namespaced command names
for (String namespacedCommandName : namespacedCommandNames) {
getHelpMap().remove(namespacedCommandName);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,21 @@
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.mojang.brigadier.tree.RootCommandNode;

import io.papermc.paper.plugin.configuration.PluginMeta;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.help.HelpTopic;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;

Expand All @@ -17,17 +30,66 @@
public class PaperCommandRegistration<Source> extends CommandRegistrationStrategy<Source> {
// References to necessary methods
private final Supplier<CommandDispatcher<Source>> getBrigadierDispatcher;
private final Runnable reloadHelpTopics;
private final Predicate<CommandNode<Source>> isBukkitCommand;

// Store registered commands nodes for eventual reloads
private final RootCommandNode<Source> registeredNodes = new RootCommandNode<>();

public PaperCommandRegistration(Supplier<CommandDispatcher<Source>> getBrigadierDispatcher, Predicate<CommandNode<Source>> isBukkitCommand) {
private static final Object paperCommandsInstance;
private static final Field dispatcherField;

private static final Constructor<?> pluginCommandNodeConstructor;
private static final Supplier<CommandDispatcher<?>> getPaperDispatcher;

static {
Object paperCommandsInstanceObject = null;
Field dispatcherFieldObject = null;

try {
paperCommandsInstanceObject = Class.forName("io.papermc.paper.command.brigadier.PaperCommands").getField("INSTANCE").get(null);
dispatcherFieldObject = Class.forName("io.papermc.paper.command.brigadier.PaperCommands").getDeclaredField("dispatcher");
} catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) {
// Doesn't happen, or rather, shouldn't happen
}

paperCommandsInstance = paperCommandsInstanceObject;
dispatcherField = dispatcherFieldObject;
dispatcherField.setAccessible(true);

getPaperDispatcher = () -> {
CommandDispatcher<?> commandDispatcher;
try {
commandDispatcher = (CommandDispatcher<?>) dispatcherField.get(paperCommandsInstance);
} catch (IllegalAccessException e) {
// This doesn't happen
commandDispatcher = null;
}
return commandDispatcher;
};

Constructor<?> commandNode;
try {
commandNode = Class.forName("io.papermc.paper.command.brigadier.PluginCommandNode").getDeclaredConstructor(String.class, PluginMeta.class, LiteralCommandNode.class, String.class);
} catch (ClassNotFoundException | NoSuchMethodException e) {
try {
// If this happens, plugin commands on Paper are not identified with the PluginCommandNode anymore
commandNode = Class.forName("io.papermc.paper.command.brigadier.PluginCommandMeta").getDeclaredConstructor(PluginMeta.class, String.class, List.class);
} catch (ClassNotFoundException | NoSuchMethodException e1) {
commandNode = null;
}
}
pluginCommandNodeConstructor = commandNode;
}

public PaperCommandRegistration(Supplier<CommandDispatcher<Source>> getBrigadierDispatcher, Runnable reloadHelpTopics, Predicate<CommandNode<Source>> isBukkitCommand) {
this.getBrigadierDispatcher = getBrigadierDispatcher;
this.reloadHelpTopics = reloadHelpTopics;
this.isBukkitCommand = isBukkitCommand;
}

// Provide access to internal functions that may be useful to developers

/**
* Checks if a Brigadier command node came from wrapping a Bukkit command
*
Expand All @@ -38,6 +100,11 @@ public boolean isBukkitCommand(CommandNode<Source> node) {
return isBukkitCommand.test(node);
}

@SuppressWarnings("unchecked")
public CommandDispatcher<Source> getPaperDispatcher() {
return (CommandDispatcher<Source>) getPaperDispatcher.get();
}

// Implement CommandRegistrationStrategy methods
@Override
public CommandDispatcher<Source> getBrigadierDispatcher() {
Expand All @@ -56,23 +123,24 @@ public void postCommandRegistration(RegisteredCommand registeredCommand, Literal

@Override
public LiteralCommandNode<Source> registerCommandNode(LiteralArgumentBuilder<Source> node, String namespace) {
LiteralCommandNode<Source> commandNode = getBrigadierDispatcher.get().register(node);
LiteralCommandNode<Source> namespacedCommandNode = CommandAPIHandler.getInstance().namespaceNode(commandNode, namespace);
LiteralCommandNode<Source> commandNode = asPluginCommand(node.build());
LiteralCommandNode<Source> namespacedCommandNode = asPluginCommand(CommandAPIHandler.getInstance().namespaceNode(commandNode, namespace));

// Add to registered command nodes
registeredNodes.addChild(commandNode);
registeredNodes.addChild(namespacedCommandNode);

// Namespace is not empty on Bukkit forks
getBrigadierDispatcher.get().getRoot().addChild(namespacedCommandNode);
// Register commands
getPaperDispatcher().getRoot().addChild(commandNode);
getPaperDispatcher().getRoot().addChild(namespacedCommandNode);
F438

return commandNode;
}

@Override
public void unregister(String commandName, boolean unregisterNamespaces, boolean unregisterBukkit) {
// Remove nodes from the dispatcher
removeBrigadierCommands(getBrigadierDispatcher.get().getRoot(), commandName, unregisterNamespaces,
removeBrigadierCommands(getPaperDispatcher().getRoot(), commandName, unregisterNamespaces,
// If we are unregistering a Bukkit command, ONLY unregister BukkitCommandNodes
// If we are unregistering a Vanilla command, DO NOT unregister BukkitCommandNodes
c -> !unregisterBukkit ^ isBukkitCommand.test(c));
Expand All @@ -89,9 +157,77 @@ public void unregister(String commandName, boolean unregisterNamespaces, boolean

@Override
public void preReloadDataPacks() {
RootCommandNode<Source> root = getBrigadierDispatcher.get().getRoot();
RootCommandNode<Source> root = getPaperDispatcher().getRoot();
for (CommandNode<Source> commandNode : registeredNodes.getChildren()) {
root.addChild(commandNode);
}
reloadHelpTopics.run();
CommandAPIBukkit.get().updateHelpForCommands(CommandAPI.getRegisteredCommands());
}

@SuppressWarnings("unchecked")
private LiteralCommandNode<Source> asPluginCommand(LiteralCommandNode<Source> commandNode) {
try {
if (pluginCommandNodeConstructor.getDeclaringClass().getSimpleName().equals("PluginCommandNode")) {
return (LiteralCommandNode<Source>) pluginCommandNodeConstructor.newInstance(
commandNode.getLiteral(),
CommandAPIBukkit.getConfiguration().getPlugin().getPluginMeta(),
commandNode,
getDescription(commandNode.getLiteral())
);
} else {
setPluginCommandMeta(commandNode);
return commandNode;
}
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}

private void setPluginCommandMeta(LiteralCommandNode<Source> node) {
try {
Field metaField = node.getClass().getSuperclass().getDeclaredField("pluginCommandMeta");
metaField.setAccessible(true);
metaField.set(node, pluginCommandNodeConstructor.newInstance(
CommandAPIBukkit.getConfiguration().getPlugin().getPluginMeta(),
getDescription(node.getLiteral()),
getAliasesForCommand(node.getLiteral())
));
} catch (NoSuchFieldException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
// This doesn't happen
}
}

private String getDescription(String commandName) {
String description = "";
for (RegisteredCommand command : CommandAPI.getRegisteredCommands()) {
String namespaceStripped = "";
if (commandName.contains(":")) {
namespaceStripped = commandName.split(":")[1];
} else {
namespaceStripped = commandName;
}
if (command.commandName().equals(namespaceStripped) || Arrays.asList(command.aliases()).contains(namespaceStripped)) {
Object helpTopic = command.helpTopic().orElse(null);
if (helpTopic != null) {
description = ((HelpTopic) helpTopic).getShortText();
10000
} else {
description = command.shortDescription().orElse("A command by the " + CommandAPIBukkit.getConfiguration().getPlugin().getName() + " plugin.");
}
break;
}
}
return description;
}

private List<String> getAliasesForCommand(String commandName) {
Set<String> aliases = new HashSet<>();
for (RegisteredCommand command : CommandAPI.getRegisteredCommands()) {
if (command.commandName().equals(commandName)) {
aliases.addAll(Arrays.asList(command.aliases()));
}
}
return new ArrayList<>(aliases);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class PaperImplementations {

private final boolean isPaperPresent;
private final boolean isFoliaPresent;
private final boolean isPaperBrigAPI;
private final NMS<?> nmsInstance;
private final Class<? extends CommandSender> feedbackForwardingCommandSender;
private final Class<? extends CommandSender> nullCommandSender;
Expand Down Expand Up @@ -54,6 +55,15 @@ public PaperImplementations(boolean isPaperPresent, boolean isFoliaPresent, NMS<
}

this.nullCommandSender = tempNullCommandSender;

boolean paperCommandSourceStackPresent;
try {
Class.forName("io.papermc.paper.command.brigadier.CommandSourceStack");
paperCommandSourceStackPresent = true;
} catch (ClassNotFoundException e) {
paperCommandSourceStackPresent = false;
}
this.isPaperBrigAPI = paperCommandSourceStackPresent;
}

/**
Expand Down Expand Up @@ -110,6 +120,13 @@ public CommandMap getCommandMap() {
public boolean isPaperPresent() {
return this.isPaperPresent;
}

/**
* @return whether we're running a Paper server with the Paper Brigadier command API
*/
public boolean isPaperBrigAPI() {
return this.isPaperBrigAPI;
}

/**
* @return whether we're using folia or not
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,12 @@ public CommandRegistrationStrategy<CommandSourceStack> createCommandRegistration
}
return new PaperCommandRegistration<>(
() -> this.<MinecraftServer>getMinecraftServer().getCommands().getDispatcher(),
() -> {
SimpleHelpMap helpMap = (SimpleHelpMap) Bukkit.getServer().getHelpMap();
helpMap.clear();
helpMap.initializeGeneralTopics();
helpMap.initializeCommands();
},
node -> bukkitCommandNode_bukkitBrigCommand.isInstance(node.getCommand())
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,12 @@ public CommandRegistrationStrategy<CommandSourceStack> createCommandRegistration
}
return new PaperCommandRegistration<>(
() -> this.<MinecraftServer>getMinecraftServer().getCommands().getDispatcher(),
() -> {
SimpleHelpMap helpMap = (SimpleHelpMap) Bukkit.getServer().getHelpMap();
helpMap.clear();
helpMap.initializeGeneralTopics();
helpMap.initializeCommands();
},
node -> bukkitCommandNode_bukkitBrigCommand.isInstance(node.getCommand())
);
}
Expand Down
D494
Original file line number Diff line number Diff line change
Expand Up @@ -1154,6 +1154,12 @@ public CommandRegistrationStrategy<CommandSourceStack> createCommandRegistration
}
return new PaperCommandRegistration<>(
() -> this.<MinecraftServer>getMinecraftServer().getCommands().getDispatcher(),
() -> {
SimpleHelpMap helpMap = (SimpleHelpMap) Bukkit.getServer().getHelpMap();
helpMap.clear();
helpMap.initializeGeneralTopics();
helpMap.initializeCommands();
},
node -> bukkitCommandNode_bukkitBrigCommand.isInstance(node.getCommand())
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,12 @@ public CommandRegistrationStrategy<CommandSourceStack> createCommandRegistration
}
return new PaperCommandRegistration<>(
() -> this.<MinecraftServer>getMinecraftServer().getCommands().getDispatcher(),
() -> {
SimpleHelpMap helpMap = (SimpleHelpMap) Bukkit.getServer().getHelpMap();
helpMap.clear();
helpMap.initializeGeneralTopics();
helpMap.initializeCommands();
},
node -> bukkitCommandNode_bukkitBrigCommand.isInstance(node.getCommand())
);
}
Expand Down
Loading
0