feat(bukkit/paper): add root command deletion support (#371)

This commit is contained in:
Alexander Söderberg 2022-06-09 05:28:08 +02:00 committed by Jason
parent 17491c17c7
commit 2572b73c4b
10 changed files with 177 additions and 18 deletions

View file

@ -37,13 +37,16 @@ import cloud.commandframework.permission.CommandPermission;
import cloud.commandframework.permission.Permission;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletionException;
import java.util.logging.Level;
import org.apiguardian.api.API;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginIdentifiableCommand;
import org.bukkit.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
final class BukkitCommand<C> extends org.bukkit.command.Command implements PluginIdentifiableCommand {
@ -58,6 +61,8 @@ final class BukkitCommand<C> extends org.bukkit.command.Command implements Plugi
private final BukkitCommandManager<C> manager;
private final Command<C> cloudCommand;
private boolean disabled;
BukkitCommand(
final @NonNull String label,
final @NonNull List<@NonNull String> aliases,
@ -77,6 +82,7 @@ final class BukkitCommand<C> extends org.bukkit.command.Command implements Plugi
if (this.command.getOwningCommand() != null) {
this.setPermission(this.command.getOwningCommand().getCommandPermission().toString());
}
this.disabled = false;
}
@Override
@ -191,22 +197,38 @@ final class BukkitCommand<C> extends org.bukkit.command.Command implements Plugi
@Override
public @NonNull String getUsage() {
return this.manager.getCommandSyntaxFormatter().apply(
Collections.singletonList(this.namedNode().getValue()),
Collections.singletonList(Objects.requireNonNull(this.namedNode().getValue())),
this.namedNode()
);
}
@Override
public boolean testPermissionSilent(final @NonNull CommandSender target) {
final CommandPermission permission = (CommandPermission) this.namedNode()
final CommandTree.Node<CommandArgument<C, ?>> node = this.namedNode();
if (this.disabled || node == null) {
return false;
}
final CommandPermission permission = (CommandPermission) node
.getNodeMeta()
.getOrDefault("permission", Permission.empty());
return this.manager.hasPermission(this.manager.getCommandSenderMapper().apply(target), permission);
}
private CommandTree.Node<CommandArgument<C, ?>> namedNode() {
return this.manager.getCommandTree().getNamedNode(this.command.getName());
@API(status = API.Status.INTERNAL, since = "1.7.0")
void disable() {
this.disabled = true;
}
@Override
public boolean isRegistered() {
// This allows us to prevent the command from showing
// in Bukkit help topics.
return !this.disabled;
}
private CommandTree.@Nullable Node<CommandArgument<C, ?>> namedNode() {
return this.manager.getCommandTree().getNamedNode(this.command.getName());
}
}

View file

@ -130,6 +130,7 @@ public class BukkitCommandManager<C> extends CommandManager<C> implements Brigad
/* Register capabilities */
CloudBukkitCapabilities.CAPABLE.forEach(this::registerCapability);
this.registerCapability(CloudCapability.StandardCapabilities.ROOT_COMMAND_DELETION);
/* Register Bukkit Preprocessor */
this.registerCommandPreProcessor(new BukkitCommandPreprocessor<>(this));

View file

@ -37,10 +37,12 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apiguardian.api.API;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandMap;
import org.bukkit.command.PluginIdentifiableCommand;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.entity.Player;
import org.bukkit.help.GenericCommandHelpTopic;
import org.checkerframework.checker.nullness.qual.NonNull;
@ -53,7 +55,7 @@ public class BukkitPluginRegistrationHandler<C> implements CommandRegistrationHa
private BukkitCommandManager<C> bukkitCommandManager;
private CommandMap commandMap;
BukkitPluginRegistrationHandler() {
protected BukkitPluginRegistrationHandler() {
}
final void initialize(final @NonNull BukkitCommandManager<C> bukkitCommandManager) throws Exception {
@ -93,7 +95,7 @@ public class BukkitPluginRegistrationHandler<C> implements CommandRegistrationHa
if (this.bukkitCommandManager.getSetting(CommandManager.ManagerSettings.OVERRIDE_EXISTING_COMMANDS)) {
this.bukkitCommands.remove(label);
aliases.forEach(alias -> this.bukkitCommands.remove(alias));
aliases.forEach(this.bukkitCommands::remove);
}
final Set<String> newAliases = new HashSet<>();
@ -126,6 +128,46 @@ public class BukkitPluginRegistrationHandler<C> implements CommandRegistrationHa
return true;
}
@Override
@SuppressWarnings("unchecked")
public final void unregisterRootCommand(
final @NonNull StaticArgument<?> rootCommand
) {
final org.bukkit.command.Command registeredCommand = this.registeredCommands.get(rootCommand);
if (registeredCommand == null) {
return;
}
((BukkitCommand<C>) registeredCommand).disable();
final List<String> aliases = new ArrayList<>(rootCommand.getAlternativeAliases());
final Set<String> registeredAliases = new HashSet<>();
for (final String alias : aliases) {
registeredAliases.add(this.getNamespacedLabel(alias));
if (this.bukkitCommandOrAliasExists(alias)) {
registeredAliases.add(alias);
}
}
if (this.bukkitCommandExists(rootCommand.getName())) {
registeredAliases.add(rootCommand.getName());
}
registeredAliases.add(this.getNamespacedLabel(rootCommand.getName()));
this.bukkitCommands.remove(rootCommand.getName());
this.bukkitCommands.remove(this.getNamespacedLabel(rootCommand.getName()));
this.recognizedAliases.removeAll(registeredAliases);
if (this.bukkitCommandManager.getSplitAliases()) {
registeredAliases.forEach(this::unregisterExternal);
}
this.registeredCommands.remove(rootCommand);
// Once the command has been unregistered, we need to refresh the command list for all online players.
Bukkit.getOnlinePlayers().forEach(Player::updateCommands);
}
private @NonNull String getNamespacedLabel(final @NonNull String label) {
return String.format("%s:%s", this.bukkitCommandManager.getOwningPlugin().getName(), label).toLowerCase();
}
@ -147,6 +189,10 @@ public class BukkitPluginRegistrationHandler<C> implements CommandRegistrationHa
) {
}
@API(status = API.Status.STABLE, since = "1.7.0")
protected void unregisterExternal(final @NonNull String label) {
}
/**
* Returns true if a command exists in the Bukkit command map, is not an alias, and is not owned by us.
*

View file

@ -27,6 +27,7 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.checkerframework.checker.nullness.qual.NonNull;
@ -47,4 +48,10 @@ final class CloudBukkitListener<C> implements Listener {
this.bukkitCommandManager.lockIfBrigadierCapable();
}
@EventHandler(priority = EventPriority.HIGHEST)
void onPluginDisable(final @NonNull PluginDisableEvent event) {
if (event.getPlugin().equals(this.bukkitCommandManager.getOwningPlugin())) {
this.bukkitCommandManager.rootCommands().forEach(this.bukkitCommandManager::deleteRootCommand);
}
}
}

View file

@ -29,7 +29,11 @@ import cloud.commandframework.bukkit.internal.BukkitBackwardsBrigadierSenderMapp
import cloud.commandframework.context.CommandContext;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import me.lucko.commodore.Commodore;
import me.lucko.commodore.CommodoreProvider;
import org.bukkit.Bukkit;
@ -74,6 +78,11 @@ class CloudCommodoreManager<C> extends BukkitPluginRegistrationHandler<C> {
this.registerWithCommodore(label, (Command<C>) command);
}
@Override
protected void unregisterExternal(final @NonNull String label) {
this.unregisterWithCommodore(label);
}
protected @NonNull CloudBrigadierManager brigadierManager() {
return this.brigadierManager;
}
@ -84,6 +93,11 @@ class CloudCommodoreManager<C> extends BukkitPluginRegistrationHandler<C> {
) {
final LiteralCommandNode<?> literalCommandNode = this.brigadierManager
.createLiteralCommandNode(label, command, (o, p) -> {
// We need to check that the command still exists...
if (this.commandManager.getCommandTree().getNamedNode(label) == null) {
return false;
}
final CommandSender sender = this.commodore.getBukkitSender(o);
return this.commandManager.hasPermission(this.commandManager.getCommandSenderMapper().apply(sender), p);
}, false, o -> 1);
@ -95,6 +109,35 @@ class CloudCommodoreManager<C> extends BukkitPluginRegistrationHandler<C> {
}
}
private void unregisterWithCommodore(
final @NonNull String label
) {
final CommandNode node = this.commodore.getDispatcher().findNode(Collections.singletonList(label));
if (node == null) {
return;
}
try {
final Class<? extends Commodore> commodoreImpl = (Class<? extends Commodore>) Class.forName("me.lucko.commodore.CommodoreImpl");
final Method removeChild = commodoreImpl.getDeclaredMethod("removeChild", RootCommandNode.class, String.class);
removeChild.setAccessible(true);
removeChild.invoke(
null /* static method */,
this.commodore.getDispatcher().getRoot(),
node.getName()
);
final Field registeredNodes = commodoreImpl.getDeclaredField("registeredNodes");
registeredNodes.setAccessible(true);
((List<LiteralCommandNode<?>>) registeredNodes.get(this.commodore)).remove(node);
} catch (final Exception e) {
throw new RuntimeException(String.format("Failed to unregister command '%s' with commodore", label), e);
}
}
private void mergeChildren(@Nullable final CommandNode<?> existingNode, @Nullable final CommandNode<?> node) {
for (final CommandNode child : node.getChildren()) {
final CommandNode<?> existingChild = existingNode.getChild(child.getName());