Allow changing namespace of registered commands
Description
This suggestion started as a question by Adixe in discord
Currently, whenever a command is registered, it is automatically registered under the minecraft namespace. So, if you register a command /hello, you can also run it using /minecraft:hello. (https://commandapi.jorel.dev/8.5.1/intro.html#how-the-commandapi-works)
It would be nice if developers could control what namespace their commands get registered under.
Expected code
// default - runnable as /command or /minecraft:command
new CommandAPICommand("command").register();
// plugin namespace - runnable as /command or /pluginname:command
new CommandAPICommand("command").register(Plugin);
// custom namespace - runnable as /command or /custom:command
new CommandAPICommand("command").register("custom");
// no namespace - only runnable as /command
new CommandAPICommand("command").register("");
Extra details
The org.bukkit.SimpleCommandMap class handles tracking, tab-completing, and dispatching all commands on the server, including CommandAPICommands via the VanillaCommandWrapper class.
package org.bukkit.command;
public class SimpleCommandMap implements CommandMap {
protected final Map<String, Command> knownCommands = new HashMap();
// Pretty much what registering does
public register(String name, String nameSpace, Command command) {
this.knownCommands.put(nameSpace + ":" + name, command);
this.knownCommands.put(name, command);
}
@Nullable
public Command getCommand(@NotNull String name) {
Command target = (Command)this.knownCommands.get(name.toLowerCase(Locale.ENGLISH));
return target;
}
public boolean dispatch(@NotNull CommandSender sender, @NotNull String commandLine) throws CommandException {
String[] args = commandLine.split(" ");
if (args.length == 0) {
return false;
} else {
String sentCommandLabel = args[0].toLowerCase(Locale.ENGLISH);
Command target = this.getCommand(sentCommandLabel);
if (target == null) {
return false;
} else {
// execute the target Command
}
}
}
public List<String> tabComplete(@NotNull CommandSender sender, @NotNull String cmdLine, @Nullable Location location) {
int spaceIndex = cmdLine.indexOf(32);
if (spaceIndex == -1) {
ArrayList<String> completions = new ArrayList();
Map<String, Command> knownCommands = this.knownCommands;
// tab complete command names based on the knownCommands map
} else {
String commandName = cmdLine.substring(0, spaceIndex);
Command target = this.getCommand(commandName);
if (target == null) {
return null;
} else {
// tab complete based on what the target command says
}
}
}
}
The knownCommands HashMap determines which commands can be accessed. If a string exists in the map, there is a Command it can run, and if it is not in the map, Spigot shouldn't think it exists. Adding entries we want and removing entries we don't want (doesn't the removing part already happen when force unregistering commands?) should allow us to register exactly the namespaces we want.
Also, if a developer chooses to register their commands with their Plugin class, there could be a feature for looking up which commands are registered by which plugins.
For the syntax, I think using a syntax that follows the identifier (namespace:key) format would be more readable. So:
new CommandAPICommand("command").register();
new CommandAPICommand("namespace", "command").register();
new CommandAPICommand(plugin, "command").register();
(Can no-nampespace commands even exit?)
And this one might be considered a breaking change, but if namespace is no longer forced to be minecraft, I don't think it should be the default as it isn't a command provided by the base game. It seems the only reason it's used is that it wasn't changeable.
Unfortunately you can't auto determine the plugin, so maybe plugin? Though maybe requiring a namespace to be provided is an okay breaking change, since changing the default namespace would break command block that use the full version anyways?
The reason that minecraft is the default namespace is simply because registering a brigadier command uses the minecraft namespace by default for commands registered via the VanillaCommandWrapper class.
I'm fairly certain that no-namespace commands can exist, but I think that would cause more issues than it solves (i.e. command conflicts).
In 9.0.0, I think we have a way to determine which plugin we're calling from if you're shading the CommandAPI...
Another option would simply be to accept a NamespacedKey object.
Lastly, for converted commands, the CommandAPI has to register commands under some namespace that isn't the converted plugin's namespace. Hence, minecraft seems to do the job! I don't want to introduce a backwards-breaking change of the default namespace on account of every command block that depends on the CommandAPI.
In 9.0.0, I think we have a way to determine which plugin we're calling from if you're shading the CommandAPI...
@JorelAli, But is it changing the namespace of the registered command?
What if, you don't change anything, but you add a new way to make it possible to change the default (minecraft) namespace if a developer wants to? This could also apply to converted commands, I haven't really checked the related configuration and code for this, but I'm sure it's possible to add an optional namespace: <some-namespace> line somewhere.
While making sure this change won't break the existing APIs, if a plugin's developer decides to use the newly created API and change the namespace, the responsibility for broke namespaced usage of said commands is theirs.
Unfortunately you can't auto determine the plugin, so maybe
plugin? Though maybe requiring a namespace to be provided is an okay breaking change, since changing the default namespace would break command block that use the full version anyways?
@Minenash, I think changing the default namespace could cause some unexpected results.
For the syntax, I think using a syntax that follows the identifier (namespace:key) format would be more readable.
In my opinion the way proposed by @willkroboth (adding the namespace - or plugin, etc. - to an overridden register() method) is better approach, because:
- You won't confuse anyone by adding a namespace or plugin to an overridden constructor, as it only had the command name as parameter before. The namespace is not neccessary at all while constructing the command, furthermore, you don't need a namespace for sub-commands. (would it make sense?)
- You would be able to register the same command for multiple namespaces - or plugins -, without the need to create a method for or copy the whole command creation code.
Implemented in version 9.4.0.