Brigadier

This commit is contained in:
2026-05-19 19:01:22 -04:00
parent 3da6b2972e
commit 66476fe110
52 changed files with 1117 additions and 1172 deletions
@@ -1,10 +1,10 @@
package dev.plex.api.command;
import org.bukkit.command.Command;
import dev.plex.command.PlexCommand;
public interface CommandApi
{
void register(Command command);
void register(PlexCommand command);
void unregister(Command command);
void unregister(PlexCommand command);
}
@@ -1,307 +1,75 @@
package dev.plex.command;
import java.util.ArrayList;
import com.mojang.brigadier.tree.LiteralCommandNode;
import dev.plex.command.annotation.CommandParameters;
import dev.plex.command.annotation.CommandPermissions;
import dev.plex.command.exception.CommandFailException;
import dev.plex.command.exception.ConsoleMustDefinePlayerException;
import dev.plex.command.exception.ConsoleOnlyException;
import dev.plex.command.exception.PlayerNotBannedException;
import dev.plex.command.exception.PlayerNotFoundException;
import dev.plex.command.source.RequiredCommandSource;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/** Public base class for module commands. */
public abstract class PlexCommand extends Command
/**
* Public Brigadier command contract for Plex and Plex modules.
*/
public interface PlexCommand
{
private static Runtime runtime;
private final CommandParameters params;
private final CommandPermissions perms;
private final RequiredCommandSource commandSource;
LiteralCommandNode<CommandSourceStack> buildCommand();
public static void setRuntime(Runtime runtime)
default CommandParameters parameters()
{
PlexCommand.runtime = runtime;
}
public PlexCommand(boolean register)
{
super("");
this.params = getClass().getAnnotation(CommandParameters.class);
this.perms = getClass().getAnnotation(CommandPermissions.class);
if (params == null || perms == null)
CommandParameters parameters = getClass().getAnnotation(CommandParameters.class);
if (parameters == null)
{
throw new IllegalStateException("PlexCommand requires CommandParameters and CommandPermissions annotations");
throw new IllegalStateException(getClass().getName() + " requires a CommandParameters annotation");
}
setName(params.name());
setLabel(params.name());
setDescription(params.description());
setPermission(perms.permission());
setUsage(params.usage().replace("<command>", params.name()));
if (!params.aliases().isEmpty())
return parameters;
}
default CommandPermissions permissions()
{
CommandPermissions permissions = getClass().getAnnotation(CommandPermissions.class);
if (permissions == null)
{
setAliases(Arrays.asList(params.aliases().split(",")));
throw new IllegalStateException(getClass().getName() + " requires a CommandPermissions annotation");
}
this.commandSource = perms.source();
if (register)
return permissions;
}
default String getName()
{
return parameters().name();
}
default String getDescription()
{
return parameters().description();
}
default String getUsage()
{
return parameters().usage().replace("<command>", getName());
}
default String getPermission()
{
return permissions().permission();
}
default RequiredCommandSource getRequiredSource()
{
return permissions().source();
}
default List<String> getAliases()
{
String aliases = parameters().aliases();
if (aliases.isBlank())
{
requireRuntime().register(this);
return List.of();
}
}
public PlexCommand()
{
this(true);
}
protected abstract Component execute(@NotNull CommandSender sender, @Nullable Player playerSender, @NotNull String[] args);
@Override
public boolean execute(@NotNull CommandSender sender, @NotNull String label, String[] args)
{
if (!matches(label))
{
return false;
}
if (commandSource == RequiredCommandSource.CONSOLE && sender instanceof Player)
{
send(sender, messageComponent("noPermissionInGame"));
return true;
}
if (commandSource == RequiredCommandSource.IN_GAME && sender instanceof ConsoleCommandSender)
{
send(sender, messageComponent("noPermissionConsole"));
return true;
}
if (!perms.permission().isEmpty() && sender instanceof Player player && !player.hasPermission(perms.permission()))
{
send(sender, messageComponent("noPermissionNode", perms.permission()));
return true;
}
try
{
Component component = execute(sender, isConsole(sender) ? null : (Player)sender, args);
if (component != null)
{
send(sender, component);
}
}
catch (PlayerNotFoundException | CommandFailException | ConsoleOnlyException |
ConsoleMustDefinePlayerException | PlayerNotBannedException | NumberFormatException ex)
{
send(sender, exceptionComponent(ex));
}
return true;
}
@NotNull
public abstract List<String> smartTabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException;
@NotNull
@Override
public List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException
{
return StringUtil.copyPartialMatches(args[args.length - 1], smartTabComplete(sender, alias, args), new ArrayList<>());
}
private boolean matches(String label)
{
return getName().equalsIgnoreCase(label) || getAliases().stream().anyMatch(alias -> alias.equalsIgnoreCase(label));
}
protected void send(Audience audience, String s)
{
audience.sendMessage(componentFromString(s));
}
protected void send(Audience audience, Component component)
{
audience.sendMessage(component);
}
protected boolean checkPermission(CommandSender sender, String permission)
{
return isConsole(sender) || checkPermission((Player)sender, permission);
}
protected boolean silentCheckPermission(CommandSender sender, String permission)
{
return isConsole(sender) || silentCheckPermission((Player)sender, permission);
}
protected boolean checkPermission(Player player, String permission)
{
if (!permission.isEmpty() && !player.hasPermission(permission))
{
throw new CommandFailException(messageString("noPermissionNode", permission));
}
return true;
}
protected boolean silentCheckPermission(Player player, String permission)
{
return permission.isEmpty() || player.hasPermission(permission);
}
protected UUID getUUID(CommandSender sender)
{
return sender instanceof Player player ? player.getUniqueId() : null;
}
protected boolean isConsole(CommandSender sender)
{
return !(sender instanceof Player);
}
protected Component messageComponent(String s, Object... objects)
{
return requireRuntime().messageComponent(s, objects);
}
protected Component messageComponent(String s, Component... objects)
{
return requireRuntime().messageComponent(s, objects);
}
@Override
public Component permissionMessage()
{
return messageComponent("noPermissionNode", getPermission());
}
protected String messageString(String s, Object... objects)
{
return requireRuntime().messageString(s, objects);
}
protected Component usage()
{
return messageComponent("correctUsagePrefix").append(componentFromString(getUsage()).color(NamedTextColor.GRAY));
}
protected Component usage(String s)
{
return messageComponent("correctUsagePrefix").append(componentFromString(s).color(NamedTextColor.GRAY));
}
private Component exceptionComponent(RuntimeException ex)
{
if (ex instanceof PlayerNotFoundException && "PlayerNotFoundException".equals(ex.getMessage()))
{
return messageComponent("playerNotFound");
}
if (ex instanceof PlayerNotBannedException && "PlayerNotBannedException".equals(ex.getMessage()))
{
return messageComponent("playerNotBanned");
}
if (ex instanceof ConsoleOnlyException && "ConsoleOnlyException".equals(ex.getMessage()))
{
return messageComponent("consoleOnly");
}
if (ex instanceof ConsoleMustDefinePlayerException && "ConsoleMustDefinePlayerException".equals(ex.getMessage()))
{
return messageComponent("consoleMustDefinePlayer");
}
return mmString(ex.getMessage());
}
protected Player getNonNullPlayer(String name)
{
Player player;
try
{
player = Bukkit.getPlayer(UUID.fromString(name));
}
catch (IllegalArgumentException ignored)
{
player = Bukkit.getPlayer(name);
}
if (player == null)
{
throw new PlayerNotFoundException();
}
return player;
}
protected World getNonNullWorld(String name)
{
World world = Bukkit.getWorld(name);
if (world == null)
{
throw new CommandFailException(messageString("worldNotFound"));
}
return world;
}
protected Component componentFromString(String s)
{
return LegacyComponentSerializer.legacyAmpersand().deserialize(s).colorIfAbsent(NamedTextColor.GRAY);
}
protected Component noColorComponentFromString(String s)
{
return LegacyComponentSerializer.legacyAmpersand().deserialize(s);
}
protected Component mmString(String s)
{
return requireRuntime().miniMessage(s);
}
protected void broadcast(String miniMessage)
{
requireRuntime().broadcast(miniMessage);
}
protected void broadcast(Component component)
{
requireRuntime().broadcast(component);
}
protected List<String> onlinePlayerNames()
{
return requireRuntime().onlinePlayerNames();
}
public CommandMap getMap()
{
return Bukkit.getCommandMap();
}
private static Runtime requireRuntime()
{
if (runtime == null)
{
throw new IllegalStateException("PlexCommand runtime has not been installed by Plex");
}
return runtime;
}
public interface Runtime
{
void register(Command command);
Component messageComponent(String entry, Object... objects);
Component messageComponent(String entry, Component... objects);
String messageString(String entry, Object... objects);
Component miniMessage(String input);
void broadcast(String miniMessage);
void broadcast(Component component);
List<String> onlinePlayerNames();
return Arrays.stream(aliases.split(","))
.map(String::trim)
.filter(alias -> !alias.isBlank())
.toList();
}
}
@@ -1,6 +1,7 @@
package dev.plex.module;
import dev.plex.api.PlexApi;
import dev.plex.command.PlexCommand;
import java.io.File;
import java.io.IOException;
@@ -13,7 +14,6 @@ import java.util.List;
import java.util.Locale;
import org.apache.logging.log4j.Logger;
import org.bukkit.command.Command;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
@@ -27,7 +27,7 @@ import org.jetbrains.annotations.Nullable;
*/
public abstract class PlexModule
{
private final List<Command> commands = new ArrayList<>();
private final List<PlexCommand> commands = new ArrayList<>();
private final List<Listener> listeners = new ArrayList<>();
private PlexApi api;
@@ -72,7 +72,7 @@ public abstract class PlexModule
HandlerList.unregisterAll(listener);
}
public void registerCommand(Command command)
public void registerCommand(PlexCommand command)
{
commands.add(command);
if (api != null)
@@ -81,7 +81,7 @@ public abstract class PlexModule
}
}
public void unregisterCommand(Command command)
public void unregisterCommand(PlexCommand command)
{
commands.remove(command);
if (api != null)
@@ -91,7 +91,7 @@ public abstract class PlexModule
}
@Nullable
public Command getCommand(String name)
public PlexCommand getCommand(String name)
{
return commands.stream()
.filter(command -> command.getName().equalsIgnoreCase(name) || command.getAliases().stream().map(String::toLowerCase).toList().contains(name.toLowerCase(Locale.ROOT)))
@@ -142,7 +142,7 @@ public abstract class PlexModule
}
}
public List<Command> getCommands()
public List<PlexCommand> getCommands()
{
return commands;
}