Skip to content

Commit

Permalink
Command registration improvements (See desc)
Browse files Browse the repository at this point in the history
Alright, so command registration should be finally documented. Command registration methods now return a boolean value to ensure that all commands have been registered successfully and each platform explains how registration works on it.
  • Loading branch information
xDec0de committed Dec 19, 2024
1 parent 5b2dee6 commit 082a180
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -295,56 +295,78 @@ public SimpleCommandMap getCommandMap() {
*/

/**
* Registers the specified {@code commands}, allowing them to be executed.
* Registers any amount of {@code commands}. This method registers commands
* in two phases depending on what type of commands you attempt to register:
* <p>
* <b>Important note</b>: SkyUtils registers commands in a pretty unusual
* way. If the name of the command you are trying to register is present
* on your <b>plugin.yml</b>, SkyUtils will just register it the "traditional"
* way, if not, it will use some reflection to register it through CraftBukkit's
* getCommandMap (Respecting encapsulation!), meaning that you can register commands without adding them
* to your <b>plugin.yml</b>. <i>However</i>, this may break on future versions
* of the game
* <b>Phase 1</b>: The "traditional" way of registering commands. Commands
* are obtained from your {@code plugin.yml} file with {@link JavaPlugin#getCommand(String)},
* then their {@link PluginCommand#setExecutor(CommandExecutor) executor} and
* {@link PluginCommand#setTabCompleter(TabCompleter) tab completer} are set.
* <p>
* The reflection approach may break on future versions, even though this is pretty unlikely (For Spigot at least),
* we are using a <b>public</b> method present on <b>CraftServer</b>, not the private commandMap field that
* some plugins use. Paper has deprecated SimpleCommandMap for removal as of 1.20 though, so we may need
* to make a check there if they were to change it, but we are aware of it, so no worries. Just
* make sure to update SkyUtils if that ever happens (This will be notified as an important update).
* <b>Phase 2</b>: This phase is completely skipped if no commands are left after phase 1.
* For those commands that aren't on your {@code plugin.yml}, the
* {@link #getCommandMap() command map} is obtained <i>(Details below)</i>. Then these
* remaining commands are all {@link SimpleCommandMap#registerAll(String, List) registered}
* on said {@link #getCommandMap() map}.
* <p>
* <b>About {@link #getCommandMap()}</b>: On Spigot, the method to get the command map
* is {@code public}, but part of the CraftServer class and not the {@link Server}
* {@code interface}. Which means that we invoke the method with reflection in order to
* obtain the instance. On Paper, this is not required as a patch exists to expose the
* command map, however, you need to use the Paper version of SkyUtils to obtain
* the map without using reflection.
*
* @param commands the list of {@link SkyCommand commands} to register.
* @param commands The {@link CustomSpigotCommand spigot commands} to register.
*
* @throws ClassCastException if any of the passed {@code commands} isn't an instance of {@link SpigotCommand}.
* @return {@code true} if all {@code commands} were registered successfully,
* {@code false} otherwise. Command registration may <b>only</b> fail on <b>phase 2</b>
* if the {@link #getCommandMap()} method fails and returns {@code null}. That means
* of course that if all of your commands are registered on <b>phase 1</b>, this
* method will always return {@code true}.
*
* @since SkyUtils 1.0.0
*/
@SuppressWarnings({"unchecked"})
public void registerCommands(CustomSpigotCommand<P, ? extends SpigotCommandSender>... commands) {
if (commands == null || commands.length == 0)
return;
final List<Command> remaining = new ArrayList<>();
@SafeVarargs
public final boolean registerCommands(@NotNull CustomSpigotCommand<P, ? extends SpigotCommandSender>... commands) {
final List<Command> phase2 = new ArrayList<>();
// Phase 1: Register via plugin.yml
for (CustomSpigotCommand<P, ?> command : commands) {
final PluginCommand plCommand = getPlugin().getCommand(command.getName());
if (plCommand != null) {
plCommand.setExecutor(command);
plCommand.setTabCompleter(command);
} else
remaining.add(command);
phase2.add(command);
}
if (remaining.isEmpty())
return;
final SimpleCommandMap commandMap = getCommandMap();
if (commandMap == null)
Bukkit.getPluginManager().disablePlugin(getPlugin());
else
commandMap.registerAll(getPlugin().getName(), remaining);
// Phase 2: Register via SimpleCommandMap
if (!phase2.isEmpty()) {
final SimpleCommandMap map = getCommandMap();
if (map == null)
return false;
map.registerAll(getPlugin().getName(), phase2);
}
return true;
}

/**
* Adapts all {@link GlobalCommand commands} to {@link AdaptedSpigotCommand},
* then registers all of them with the {@link #registerCommands(CustomSpigotCommand[])}
* method. Details about how command registration works on Spigot are provided on
* said method.
*
* @param commands The {@link GlobalCommand commands} to register.
*
* @return {@code true} if all commands were registered successfully,
* {@code false} otherwise.
*
* @since SkyUtils 1.0.0
*
* @see #registerCommands(CustomSpigotCommand[])
*/
@Override
@SuppressWarnings("unchecked")
public void registerCommands(@NotNull GlobalCommand<P>... commands) {
if (commands == null || commands.length == 0)
return;
registerCommands(SkyCollections.map(
public boolean registerCommands(@NotNull GlobalCommand<P>... commands) {
return registerCommands(SkyCollections.map(
commands,
new AdaptedSpigotCommand[commands.length],
cmd -> new AdaptedSpigotCommand<>(this, cmd))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import net.codersky.skyutils.spigot.SpigotUtils;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;

Expand All @@ -22,7 +23,7 @@ public boolean onCommand(@NotNull SpigotCommandSender sender, @NotNull String[]
return command.onCommand(sender, args);
}

@NotNull
@Nullable
@Override
public List<String> onTab(@NotNull SpigotCommandSender sender, @NotNull String[] args) {
return command.onTab(sender, args);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,20 +131,22 @@ public VelocityConsole getConsole() {
return MCPlatform.VELOCITY;
}

@Override
@SuppressWarnings("unchecked")
public void registerCommands(GlobalCommand<P>... commands) {
if (commands == null || commands.length == 0)
return;
registerCommands(SkyCollections.map(
commands,
new AdaptedVelocityCommand[commands.length],
cmd -> new AdaptedVelocityCommand<>(this, cmd))
);
}
/*
- Command registration
*/

@SuppressWarnings("unchecked")
public void registerCommands(CustomVelocityCommand<P, ? extends VelocityCommandSender>... commands) {
/**
* Registers all provided {@code commands} to the {@link #getProxy() proxy}.
* On the Velocity platform command registration is pretty straightforward
* and should just work without any issues.
*
* @param commands The {@link CustomVelocityCommand commands} to register.
*
* @return Always {@code true} as all commands are expected to register
* successfully on Velocity.
*/
@SafeVarargs
public final boolean registerCommands(CustomVelocityCommand<P, ? extends VelocityCommandSender>... commands) {
final CommandManager manager = getProxy().getCommandManager();
for (CustomVelocityCommand<P, ? extends VelocityCommandSender> command : commands) {
final CommandMeta meta = manager.metaBuilder(command.getName())
Expand All @@ -153,6 +155,32 @@ public void registerCommands(CustomVelocityCommand<P, ? extends VelocityCommandS
.build();
manager.register(meta, command);
}
return true;
}

/**
* Adapts all {@link GlobalCommand commands} to {@link AdaptedVelocityCommand},
* then registers all of them with the {@link #registerCommands(CustomVelocityCommand[])}
* method. Details about how command registration works on Velocity are provided on
* said method.
*
* @param commands The {@link GlobalCommand commands} to register.
*
* @return {@code true} if all commands were registered successfully,
* {@code false} otherwise.
*
* @since SkyUtils 1.0.0
*
* @see #registerCommands(CustomVelocityCommand[])
*/
@Override
@SuppressWarnings("unchecked")
public boolean registerCommands(@NotNull GlobalCommand<P>... commands) {
return registerCommands(SkyCollections.map(
commands,
new AdaptedVelocityCommand[commands.length],
cmd -> new AdaptedVelocityCommand<>(this, cmd))
);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import net.codersky.skyutils.cmd.SkyCommand;
import net.codersky.skyutils.velocity.VelocityUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;

Expand All @@ -21,8 +22,9 @@ public boolean onCommand(@NotNull VelocityCommandSender sender, @NotNull String[
return command.onCommand(sender, args);
}

@Nullable
@Override
public @NotNull List<String> onTab(@NotNull VelocityCommandSender sender, @NotNull String[] args) {
public List<String> onTab(@NotNull VelocityCommandSender sender, @NotNull String[] args) {
return command.onTab(sender, args);
}

Expand Down
9 changes: 8 additions & 1 deletion shared/src/main/java/net/codersky/skyutils/SkyUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,16 @@ public final String getSkyUtilsVersion() {
*
* @param commands The {@link GlobalCommand commands} to register.
*
* @return {@code true} if all {@code commands} were registered successfully,
* {@code false} otherwise. If commands fail to register, SkyUtils is at
* fault for any internal reason that will be notified to the console, blaming
* SkyUtils about the error and not your plugin, don't worry about that. This
* mostly depends on the platform and not on SkyUtils, this return value mostly
* exists as a <b>just in case</b> measure.
*
* @since SkyUtils 1.0.0
*/
public abstract void registerCommands(GlobalCommand<P>... commands);
public abstract boolean registerCommands(GlobalCommand<P>... commands);

/**
* Unregisters a command by {@code name}.
Expand Down

0 comments on commit 082a180

Please sign in to comment.