2020-04-24 03:25:58 +02:00
|
|
|
package net.minestom.server.command;
|
2020-04-05 10:15:21 +02:00
|
|
|
|
2020-08-03 10:35:46 +02:00
|
|
|
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
|
|
|
import it.unimi.dsi.fastutil.ints.IntList;
|
2021-02-09 01:11:47 +01:00
|
|
|
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
|
|
|
|
import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
|
2021-01-19 18:25:54 +01:00
|
|
|
import net.minestom.server.MinecraftServer;
|
2021-02-11 02:51:42 +01:00
|
|
|
import net.minestom.server.command.builder.*;
|
2021-02-11 00:04:42 +01:00
|
|
|
import net.minestom.server.command.builder.arguments.Argument;
|
2020-07-10 18:12:29 +02:00
|
|
|
import net.minestom.server.command.builder.condition.CommandCondition;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.entity.Player;
|
2020-05-07 15:46:21 +02:00
|
|
|
import net.minestom.server.event.player.PlayerCommandEvent;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
|
|
|
|
import net.minestom.server.utils.ArrayUtils;
|
2020-11-02 04:13:43 +01:00
|
|
|
import net.minestom.server.utils.callback.CommandCallback;
|
2020-05-23 04:20:01 +02:00
|
|
|
import net.minestom.server.utils.validate.Check;
|
2021-01-08 03:07:37 +01:00
|
|
|
import org.apache.commons.lang3.StringUtils;
|
2020-10-24 16:58:27 +02:00
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
import org.jetbrains.annotations.Nullable;
|
2020-04-05 10:15:21 +02:00
|
|
|
|
2020-11-20 14:07:09 +01:00
|
|
|
import java.io.BufferedReader;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStreamReader;
|
2020-06-21 14:01:03 +02:00
|
|
|
import java.util.*;
|
2021-02-20 13:10:12 +01:00
|
|
|
import java.util.stream.Collectors;
|
2020-04-05 10:15:21 +02:00
|
|
|
|
2020-10-11 18:35:32 +02:00
|
|
|
/**
|
|
|
|
* Manager used to register {@link Command} and {@link CommandProcessor}.
|
|
|
|
* <p>
|
|
|
|
* It is also possible to simulate a command using {@link #execute(CommandSender, String)}.
|
|
|
|
*/
|
|
|
|
public final class CommandManager {
|
2020-04-05 10:15:21 +02:00
|
|
|
|
2020-08-04 04:21:11 +02:00
|
|
|
public static final String COMMAND_PREFIX = "/";
|
|
|
|
|
2020-12-29 00:04:15 +01:00
|
|
|
private volatile boolean running = true;
|
2020-04-05 10:15:21 +02:00
|
|
|
|
2021-02-08 03:42:35 +01:00
|
|
|
private final ServerSender serverSender = new ServerSender();
|
2020-10-22 12:55:53 +02:00
|
|
|
private final ConsoleSender consoleSender = new ConsoleSender();
|
2020-06-21 14:01:03 +02:00
|
|
|
|
2020-10-22 12:55:53 +02:00
|
|
|
private final CommandDispatcher dispatcher = new CommandDispatcher();
|
|
|
|
private final Map<String, CommandProcessor> commandProcessorMap = new HashMap<>();
|
2020-04-05 10:15:21 +02:00
|
|
|
|
2020-11-02 04:13:43 +01:00
|
|
|
private CommandCallback unknownCommandCallback;
|
|
|
|
|
2020-06-21 14:01:03 +02:00
|
|
|
public CommandManager() {
|
2020-07-01 21:03:53 +02:00
|
|
|
}
|
|
|
|
|
2020-07-23 07:36:49 +02:00
|
|
|
/**
|
2020-10-15 21:16:31 +02:00
|
|
|
* Stops the console responsible for the console commands processing.
|
2020-07-23 07:36:49 +02:00
|
|
|
* <p>
|
2020-10-15 21:16:31 +02:00
|
|
|
* WARNING: it cannot be re-run later.
|
2020-07-23 07:36:49 +02:00
|
|
|
*/
|
2020-07-01 21:03:53 +02:00
|
|
|
public void stopConsoleThread() {
|
|
|
|
running = false;
|
2020-06-21 14:01:03 +02:00
|
|
|
}
|
|
|
|
|
2020-07-21 18:04:02 +02:00
|
|
|
/**
|
2020-10-15 21:16:31 +02:00
|
|
|
* Registers a {@link Command}.
|
2020-07-21 18:04:02 +02:00
|
|
|
*
|
|
|
|
* @param command the command to register
|
2020-11-14 07:06:46 +01:00
|
|
|
* @throws IllegalStateException if a command with the same name already exists
|
2020-07-21 18:04:02 +02:00
|
|
|
*/
|
2020-11-14 01:39:51 +01:00
|
|
|
public synchronized void register(@NotNull Command command) {
|
|
|
|
Check.stateCondition(commandExists(command.getName()),
|
|
|
|
"A command with the name " + command.getName() + " is already registered!");
|
2021-02-09 15:26:53 +01:00
|
|
|
if (command.getAliases() != null) {
|
|
|
|
for (String alias : command.getAliases()) {
|
|
|
|
Check.stateCondition(commandExists(alias),
|
|
|
|
"A command with the name " + alias + " is already registered!");
|
|
|
|
}
|
2020-11-14 01:39:51 +01:00
|
|
|
}
|
2020-04-05 10:15:21 +02:00
|
|
|
this.dispatcher.register(command);
|
|
|
|
}
|
|
|
|
|
2020-11-06 16:03:08 +01:00
|
|
|
/**
|
|
|
|
* Removes a command from the currently registered commands.
|
|
|
|
* Does nothing if the command was not registered before
|
|
|
|
*
|
|
|
|
* @param command the command to remove
|
|
|
|
*/
|
|
|
|
public void unregister(@NotNull Command command) {
|
|
|
|
this.dispatcher.unregister(command);
|
|
|
|
}
|
|
|
|
|
2020-08-04 06:14:42 +02:00
|
|
|
/**
|
2020-10-15 21:16:31 +02:00
|
|
|
* Gets the {@link Command} registered by {@link #register(Command)}.
|
2020-08-04 06:14:42 +02:00
|
|
|
*
|
|
|
|
* @param commandName the command name
|
|
|
|
* @return the command associated with the name, null if not any
|
|
|
|
*/
|
2020-10-24 16:58:27 +02:00
|
|
|
@Nullable
|
|
|
|
public Command getCommand(@NotNull String commandName) {
|
2020-08-04 06:14:42 +02:00
|
|
|
return dispatcher.findCommand(commandName);
|
|
|
|
}
|
|
|
|
|
2020-07-21 18:04:02 +02:00
|
|
|
/**
|
2020-10-15 21:16:31 +02:00
|
|
|
* Registers a {@link CommandProcessor}.
|
2020-07-21 18:04:02 +02:00
|
|
|
*
|
|
|
|
* @param commandProcessor the command to register
|
2020-11-14 07:06:46 +01:00
|
|
|
* @throws IllegalStateException if a command with the same name already exists
|
2020-07-21 18:04:02 +02:00
|
|
|
*/
|
2020-11-14 01:39:51 +01:00
|
|
|
public synchronized void register(@NotNull CommandProcessor commandProcessor) {
|
|
|
|
final String commandName = commandProcessor.getCommandName().toLowerCase();
|
|
|
|
Check.stateCondition(commandExists(commandName),
|
|
|
|
"A command with the name " + commandName + " is already registered!");
|
|
|
|
this.commandProcessorMap.put(commandName, commandProcessor);
|
2020-08-04 04:21:11 +02:00
|
|
|
// Register aliases
|
|
|
|
final String[] aliases = commandProcessor.getAliases();
|
|
|
|
if (aliases != null && aliases.length > 0) {
|
|
|
|
for (String alias : aliases) {
|
2020-11-14 01:39:51 +01:00
|
|
|
Check.stateCondition(commandExists(alias),
|
|
|
|
"A command with the name " + alias + " is already registered!");
|
|
|
|
|
2020-08-04 04:21:11 +02:00
|
|
|
this.commandProcessorMap.put(alias.toLowerCase(), commandProcessor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-10-15 21:16:31 +02:00
|
|
|
* Gets the {@link CommandProcessor} registered by {@link #register(CommandProcessor)}.
|
2020-08-04 04:21:11 +02:00
|
|
|
*
|
|
|
|
* @param commandName the command name
|
|
|
|
* @return the command associated with the name, null if not any
|
|
|
|
*/
|
2020-10-24 16:58:27 +02:00
|
|
|
@Nullable
|
|
|
|
public CommandProcessor getCommandProcessor(@NotNull String commandName) {
|
2020-08-04 04:21:11 +02:00
|
|
|
return commandProcessorMap.get(commandName.toLowerCase());
|
2020-04-05 10:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-11-14 01:39:51 +01:00
|
|
|
/**
|
|
|
|
* Gets if a command with the name {@code commandName} already exists or name.
|
|
|
|
*
|
|
|
|
* @param commandName the command name to check
|
|
|
|
* @return true if the command does exist
|
|
|
|
*/
|
|
|
|
public boolean commandExists(@NotNull String commandName) {
|
|
|
|
commandName = commandName.toLowerCase();
|
|
|
|
return dispatcher.findCommand(commandName) != null ||
|
|
|
|
commandProcessorMap.get(commandName) != null;
|
|
|
|
}
|
|
|
|
|
2020-07-21 18:04:02 +02:00
|
|
|
/**
|
2020-10-15 21:16:31 +02:00
|
|
|
* Executes a command for a {@link ConsoleSender}.
|
2020-07-21 18:04:02 +02:00
|
|
|
*
|
|
|
|
* @param sender the sender of the command
|
|
|
|
* @param command the raw command string (without the command prefix)
|
2021-02-11 00:04:42 +01:00
|
|
|
* @return the execution result
|
2020-07-21 18:04:02 +02:00
|
|
|
*/
|
2021-02-11 00:04:42 +01:00
|
|
|
@NotNull
|
|
|
|
public CommandResult execute(@NotNull CommandSender sender, @NotNull String command) {
|
2020-04-05 10:15:21 +02:00
|
|
|
|
2020-10-12 02:56:30 +02:00
|
|
|
// Command event
|
2020-06-21 14:01:03 +02:00
|
|
|
if (sender instanceof Player) {
|
|
|
|
Player player = (Player) sender;
|
2020-04-29 19:23:47 +02:00
|
|
|
|
2020-06-21 14:01:03 +02:00
|
|
|
PlayerCommandEvent playerCommandEvent = new PlayerCommandEvent(player, command);
|
|
|
|
player.callEvent(PlayerCommandEvent.class, playerCommandEvent);
|
2020-04-29 19:23:47 +02:00
|
|
|
|
2020-06-21 14:01:03 +02:00
|
|
|
if (playerCommandEvent.isCancelled())
|
2021-02-11 03:00:13 +01:00
|
|
|
return CommandResult.of(CommandResult.Type.CANCELLED, command);
|
2020-06-21 14:01:03 +02:00
|
|
|
|
|
|
|
command = playerCommandEvent.getCommand();
|
|
|
|
}
|
2020-04-29 19:23:47 +02:00
|
|
|
|
2020-10-12 02:56:30 +02:00
|
|
|
// Process the command
|
2020-12-15 09:30:09 +01:00
|
|
|
|
|
|
|
{
|
2020-07-21 18:04:02 +02:00
|
|
|
// Check for rich-command
|
2021-02-11 00:04:42 +01:00
|
|
|
final CommandResult result = this.dispatcher.execute(sender, command);
|
|
|
|
if (result.getType() != CommandResult.Type.UNKNOWN) {
|
|
|
|
return result;
|
2020-12-15 09:30:09 +01:00
|
|
|
} else {
|
|
|
|
// Check for legacy-command
|
2021-01-08 03:07:37 +01:00
|
|
|
final String[] splitCommand = command.split(StringUtils.SPACE);
|
2020-12-15 09:30:09 +01:00
|
|
|
final String commandName = splitCommand[0];
|
|
|
|
final CommandProcessor commandProcessor = commandProcessorMap.get(commandName.toLowerCase());
|
|
|
|
if (commandProcessor == null) {
|
|
|
|
if (unknownCommandCallback != null) {
|
|
|
|
this.unknownCommandCallback.apply(sender, command);
|
|
|
|
}
|
2021-02-22 11:26:16 +01:00
|
|
|
return CommandResult.of(CommandResult.Type.UNKNOWN, command);
|
2020-11-02 04:13:43 +01:00
|
|
|
}
|
2020-04-05 10:15:21 +02:00
|
|
|
|
2020-12-15 09:30:09 +01:00
|
|
|
// Execute the legacy-command
|
2021-01-08 03:07:37 +01:00
|
|
|
final String[] args = command.substring(command.indexOf(StringUtils.SPACE) + 1).split(StringUtils.SPACE);
|
2021-02-08 03:42:35 +01:00
|
|
|
commandProcessor.process(sender, commandName, args);
|
2021-02-11 03:00:13 +01:00
|
|
|
return CommandResult.of(CommandResult.Type.SUCCESS, command);
|
2020-12-15 09:30:09 +01:00
|
|
|
}
|
2020-04-05 10:15:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-08 03:42:35 +01:00
|
|
|
/**
|
|
|
|
* Executes the command using a {@link ServerSender} to do not
|
|
|
|
* print the command messages, and rely instead on the command return data.
|
2021-02-08 04:08:22 +01:00
|
|
|
*
|
|
|
|
* @see #execute(CommandSender, String)
|
2021-02-08 03:42:35 +01:00
|
|
|
*/
|
2021-02-11 20:36:02 +01:00
|
|
|
@NotNull
|
2021-02-11 00:04:42 +01:00
|
|
|
public CommandResult executeServerCommand(@NotNull String command) {
|
2021-02-08 03:42:35 +01:00
|
|
|
return execute(serverSender, command);
|
|
|
|
}
|
|
|
|
|
2021-02-11 02:51:42 +01:00
|
|
|
@NotNull
|
|
|
|
public CommandDispatcher getDispatcher() {
|
|
|
|
return dispatcher;
|
|
|
|
}
|
|
|
|
|
2020-11-02 04:13:43 +01:00
|
|
|
/**
|
|
|
|
* Gets the callback executed once an unknown command is run.
|
|
|
|
*
|
|
|
|
* @return the unknown command callback, null if not any
|
|
|
|
*/
|
|
|
|
@Nullable
|
|
|
|
public CommandCallback getUnknownCommandCallback() {
|
|
|
|
return unknownCommandCallback;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the callback executed once an unknown command is run.
|
|
|
|
*
|
|
|
|
* @param unknownCommandCallback the new unknown command callback,
|
|
|
|
* setting it to null mean that nothing will be executed
|
|
|
|
*/
|
|
|
|
public void setUnknownCommandCallback(@Nullable CommandCallback unknownCommandCallback) {
|
|
|
|
this.unknownCommandCallback = unknownCommandCallback;
|
|
|
|
}
|
|
|
|
|
2020-07-21 18:04:02 +02:00
|
|
|
/**
|
2020-10-15 21:16:31 +02:00
|
|
|
* Gets the {@link ConsoleSender} (which is used as a {@link CommandSender}).
|
2020-07-21 18:04:02 +02:00
|
|
|
*
|
2020-10-11 18:35:32 +02:00
|
|
|
* @return the {@link ConsoleSender}
|
2020-07-21 18:04:02 +02:00
|
|
|
*/
|
2020-10-24 16:58:27 +02:00
|
|
|
@NotNull
|
2020-06-21 14:01:03 +02:00
|
|
|
public ConsoleSender getConsoleSender() {
|
|
|
|
return consoleSender;
|
|
|
|
}
|
|
|
|
|
2021-01-21 11:37:54 +01:00
|
|
|
/**
|
|
|
|
* Starts the thread responsible for executing commands from the console.
|
|
|
|
*/
|
|
|
|
public void startConsoleThread() {
|
|
|
|
Thread consoleThread = new Thread(() -> {
|
|
|
|
BufferedReader bi = new BufferedReader(new InputStreamReader(System.in));
|
|
|
|
while (running) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
if (bi.ready()) {
|
|
|
|
final String command = bi.readLine();
|
|
|
|
execute(consoleSender, command);
|
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
|
|
MinecraftServer.getExceptionManager().handleException(e);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prevent permanent looping
|
|
|
|
try {
|
|
|
|
Thread.sleep(200);
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
MinecraftServer.getExceptionManager().handleException(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
bi.close();
|
|
|
|
} catch (IOException e) {
|
|
|
|
MinecraftServer.getExceptionManager().handleException(e);
|
|
|
|
}
|
|
|
|
}, "ConsoleCommand-Thread");
|
|
|
|
consoleThread.setDaemon(true);
|
|
|
|
consoleThread.start();
|
|
|
|
}
|
|
|
|
|
2020-07-21 18:04:02 +02:00
|
|
|
/**
|
2020-10-15 21:16:31 +02:00
|
|
|
* Gets the {@link DeclareCommandsPacket} for a specific player.
|
2020-07-21 18:04:02 +02:00
|
|
|
* <p>
|
2020-11-04 04:45:46 +01:00
|
|
|
* Can be used to update a player auto-completion list.
|
2020-07-21 18:04:02 +02:00
|
|
|
*
|
|
|
|
* @param player the player to get the commands packet
|
|
|
|
* @return the {@link DeclareCommandsPacket} for {@code player}
|
|
|
|
*/
|
2020-10-24 16:58:27 +02:00
|
|
|
@NotNull
|
|
|
|
public DeclareCommandsPacket createDeclareCommandsPacket(@NotNull Player player) {
|
2020-07-10 18:12:29 +02:00
|
|
|
return buildPacket(player);
|
2020-04-05 10:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-08-03 06:36:42 +02:00
|
|
|
/**
|
2020-10-15 21:16:31 +02:00
|
|
|
* Builds the {@link DeclareCommandsPacket} for a {@link Player}.
|
2020-08-03 06:36:42 +02:00
|
|
|
*
|
|
|
|
* @param player the player to build the packet for
|
|
|
|
* @return the commands packet for the specific player
|
|
|
|
*/
|
2020-10-24 16:58:27 +02:00
|
|
|
@NotNull
|
|
|
|
private DeclareCommandsPacket buildPacket(@NotNull Player player) {
|
2020-07-10 16:37:18 +02:00
|
|
|
DeclareCommandsPacket declareCommandsPacket = new DeclareCommandsPacket();
|
2020-04-05 10:15:21 +02:00
|
|
|
|
|
|
|
List<DeclareCommandsPacket.Node> nodes = new ArrayList<>();
|
2020-07-10 18:12:29 +02:00
|
|
|
// Contains the children of the main node (all commands name)
|
2020-08-03 10:35:46 +02:00
|
|
|
IntList rootChildren = new IntArrayList();
|
2020-04-05 10:15:21 +02:00
|
|
|
|
2021-02-11 00:58:10 +01:00
|
|
|
// Root node
|
|
|
|
DeclareCommandsPacket.Node rootNode = new DeclareCommandsPacket.Node();
|
|
|
|
rootNode.flags = 0;
|
|
|
|
nodes.add(rootNode);
|
|
|
|
|
2020-10-28 01:02:12 +01:00
|
|
|
// Brigadier-like commands
|
2020-07-14 13:35:07 +02:00
|
|
|
for (Command command : dispatcher.getCommands()) {
|
2021-02-20 08:59:15 +01:00
|
|
|
serializeCommand(player, command, nodes, rootChildren);
|
2020-07-10 18:12:29 +02:00
|
|
|
}
|
2020-07-10 22:50:05 +02:00
|
|
|
|
2020-08-04 06:28:21 +02:00
|
|
|
// Pair<CommandName,EnabledTracking>
|
2021-02-09 01:11:47 +01:00
|
|
|
final Object2BooleanMap<String> commandsPair = new Object2BooleanOpenHashMap<>();
|
2020-07-10 18:12:29 +02:00
|
|
|
for (CommandProcessor commandProcessor : commandProcessorMap.values()) {
|
2020-08-04 06:28:21 +02:00
|
|
|
final boolean enableTracking = commandProcessor.enableWritingTracking();
|
2020-07-10 18:12:29 +02:00
|
|
|
// Do not show command if return false
|
|
|
|
if (!commandProcessor.hasAccess(player))
|
|
|
|
continue;
|
2020-04-05 10:15:21 +02:00
|
|
|
|
2021-02-09 01:11:47 +01:00
|
|
|
commandsPair.put(commandProcessor.getCommandName(), enableTracking);
|
2020-08-03 06:36:42 +02:00
|
|
|
final String[] aliases = commandProcessor.getAliases();
|
2020-07-10 18:12:29 +02:00
|
|
|
if (aliases == null || aliases.length == 0)
|
|
|
|
continue;
|
|
|
|
for (String alias : aliases) {
|
2021-02-09 01:11:47 +01:00
|
|
|
commandsPair.put(alias, enableTracking);
|
2020-07-10 18:12:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-09 01:11:47 +01:00
|
|
|
for (Object2BooleanMap.Entry<String> entry : commandsPair.object2BooleanEntrySet()) {
|
|
|
|
final String name = entry.getKey();
|
|
|
|
final boolean tracking = entry.getBooleanValue();
|
2020-08-04 04:21:11 +02:00
|
|
|
// Server suggestion (ask_server)
|
|
|
|
{
|
|
|
|
DeclareCommandsPacket.Node tabNode = new DeclareCommandsPacket.Node();
|
2021-02-11 03:00:13 +01:00
|
|
|
tabNode.flags = DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.ARGUMENT,
|
|
|
|
true, false, tracking);
|
2020-08-04 06:28:21 +02:00
|
|
|
tabNode.name = tracking ? "tab_completion" : "args";
|
2020-08-04 04:21:11 +02:00
|
|
|
tabNode.parser = "brigadier:string";
|
|
|
|
tabNode.properties = packetWriter -> packetWriter.writeVarInt(2); // Greedy phrase
|
|
|
|
tabNode.children = new int[0];
|
2020-08-04 06:28:21 +02:00
|
|
|
if (tracking) {
|
|
|
|
tabNode.suggestionsType = "minecraft:ask_server";
|
|
|
|
}
|
2020-08-04 04:21:11 +02:00
|
|
|
|
|
|
|
nodes.add(tabNode);
|
|
|
|
}
|
|
|
|
|
2020-04-05 10:15:21 +02:00
|
|
|
DeclareCommandsPacket.Node literalNode = new DeclareCommandsPacket.Node();
|
2021-02-11 03:00:13 +01:00
|
|
|
literalNode.flags = DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.LITERAL,
|
|
|
|
true, false, false);
|
2020-08-04 06:28:21 +02:00
|
|
|
literalNode.name = name;
|
2020-08-04 04:21:11 +02:00
|
|
|
literalNode.children = new int[]{nodes.size() - 1};
|
2020-04-05 10:15:21 +02:00
|
|
|
|
2021-02-12 00:00:44 +01:00
|
|
|
addCommandNameNode(literalNode, rootChildren, nodes);
|
2020-04-05 10:15:21 +02:00
|
|
|
}
|
|
|
|
|
2021-02-11 00:58:10 +01:00
|
|
|
// Add root node children
|
2020-04-05 10:15:21 +02:00
|
|
|
rootNode.children = ArrayUtils.toArray(rootChildren);
|
|
|
|
|
2020-05-25 12:25:39 +02:00
|
|
|
declareCommandsPacket.nodes = nodes.toArray(new DeclareCommandsPacket.Node[0]);
|
2021-02-11 00:58:10 +01:00
|
|
|
declareCommandsPacket.rootIndex = 0;
|
2020-07-10 16:37:18 +02:00
|
|
|
|
|
|
|
return declareCommandsPacket;
|
2020-04-05 10:15:21 +02:00
|
|
|
}
|
|
|
|
|
2021-02-20 08:59:15 +01:00
|
|
|
private int serializeCommand(CommandSender sender, Command command,
|
|
|
|
List<DeclareCommandsPacket.Node> nodes,
|
|
|
|
IntList rootChildren) {
|
|
|
|
// Check if player should see this command
|
|
|
|
final CommandCondition commandCondition = command.getCondition();
|
|
|
|
if (commandCondition != null) {
|
|
|
|
// Do not show command if return false
|
|
|
|
if (!commandCondition.canUse(sender, null)) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The main root of this command
|
|
|
|
IntList cmdChildren = new IntArrayList();
|
|
|
|
final Collection<CommandSyntax> syntaxes = command.getSyntaxes();
|
|
|
|
|
|
|
|
// Create command for main name
|
|
|
|
final DeclareCommandsPacket.Node mainNode = createCommand(sender, nodes, cmdChildren,
|
|
|
|
command.getName(), syntaxes, rootChildren);
|
|
|
|
final int mainNodeIndex = nodes.indexOf(mainNode);
|
|
|
|
|
|
|
|
// Serialize all the subcommands
|
|
|
|
for (Command subcommand : command.getSubcommands()) {
|
|
|
|
final int subNodeIndex = serializeCommand(sender, subcommand, nodes, cmdChildren);
|
|
|
|
if (subNodeIndex != -1) {
|
|
|
|
mainNode.children = ArrayUtils.concatenateIntArrays(mainNode.children, new int[]{subNodeIndex});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use redirection to hook aliases with the command
|
|
|
|
final String[] aliases = command.getAliases();
|
|
|
|
if (aliases != null) {
|
|
|
|
for (String alias : aliases) {
|
|
|
|
DeclareCommandsPacket.Node aliasNode = new DeclareCommandsPacket.Node();
|
|
|
|
aliasNode.flags = DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.LITERAL,
|
|
|
|
false, true, false);
|
|
|
|
aliasNode.name = alias;
|
|
|
|
aliasNode.redirectedNode = mainNodeIndex;
|
|
|
|
|
|
|
|
addCommandNameNode(aliasNode, rootChildren, nodes);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return mainNodeIndex;
|
|
|
|
}
|
|
|
|
|
2020-08-03 06:36:42 +02:00
|
|
|
/**
|
2020-10-28 01:02:12 +01:00
|
|
|
* Adds the command's syntaxes to the nodes list.
|
2020-08-03 06:36:42 +02:00
|
|
|
*
|
2020-11-04 04:45:46 +01:00
|
|
|
* @param sender the potential sender of the command
|
2020-08-03 06:36:42 +02:00
|
|
|
* @param nodes the nodes of the packet
|
|
|
|
* @param cmdChildren the main root of this command
|
|
|
|
* @param name the name of the command (or the alias)
|
|
|
|
* @param syntaxes the syntaxes of the command
|
|
|
|
* @param rootChildren the children of the main node (all commands name)
|
2021-02-09 15:25:18 +01:00
|
|
|
* @return The index of the main node for alias redirection
|
2020-08-03 06:36:42 +02:00
|
|
|
*/
|
2021-02-20 08:59:15 +01:00
|
|
|
private DeclareCommandsPacket.Node createCommand(@NotNull CommandSender sender,
|
|
|
|
@NotNull List<DeclareCommandsPacket.Node> nodes,
|
|
|
|
@NotNull IntList cmdChildren,
|
|
|
|
@NotNull String name,
|
|
|
|
@NotNull Collection<CommandSyntax> syntaxes,
|
|
|
|
@NotNull IntList rootChildren) {
|
2020-07-10 18:12:29 +02:00
|
|
|
|
|
|
|
DeclareCommandsPacket.Node literalNode = createMainNode(name, syntaxes.isEmpty());
|
|
|
|
|
2021-02-13 08:43:38 +01:00
|
|
|
final int literalNodeId = addCommandNameNode(literalNode, rootChildren, nodes);
|
2020-07-10 18:12:29 +02:00
|
|
|
|
2021-02-20 19:18:28 +01:00
|
|
|
// Contains the arguments of the already-parsed syntaxes
|
|
|
|
List<Argument<?>[]> syntaxesArguments = new ArrayList<>();
|
|
|
|
// Contains the nodes of an argument
|
|
|
|
Map<Argument<?>, List<DeclareCommandsPacket.Node[]>> storedArgumentsNodes = new HashMap<>();
|
|
|
|
|
2021-02-20 13:10:12 +01:00
|
|
|
// Sort syntaxes by argument count. Brigadier requires it.
|
|
|
|
syntaxes = syntaxes.stream().sorted(Comparator.comparingInt(o -> -o.getArguments().length)).collect(Collectors.toList());
|
2020-07-10 18:12:29 +02:00
|
|
|
for (CommandSyntax syntax : syntaxes) {
|
2020-11-04 04:45:46 +01:00
|
|
|
final CommandCondition commandCondition = syntax.getCommandCondition();
|
|
|
|
if (commandCondition != null && !commandCondition.canUse(sender, null)) {
|
|
|
|
// Sender does not have the right to use this syntax, ignore it
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-07-10 18:12:29 +02:00
|
|
|
// Represent the last nodes computed in the last iteration
|
2021-02-11 06:37:09 +01:00
|
|
|
DeclareCommandsPacket.Node[] lastNodes = new DeclareCommandsPacket.Node[]{literalNode};
|
2020-07-10 18:12:29 +02:00
|
|
|
|
|
|
|
// Represent the children of the last node
|
2021-02-11 06:37:09 +01:00
|
|
|
IntList argChildren = cmdChildren;
|
|
|
|
|
2021-02-13 08:43:38 +01:00
|
|
|
NodeMaker nodeMaker = new NodeMaker(lastNodes, literalNodeId);
|
2021-02-12 00:00:44 +01:00
|
|
|
int lastArgumentNodeIndex = nodeMaker.getNodesCount();
|
2020-07-10 18:12:29 +02:00
|
|
|
|
2021-01-08 23:47:31 +01:00
|
|
|
final Argument<?>[] arguments = syntax.getArguments();
|
2020-07-10 18:12:29 +02:00
|
|
|
for (int i = 0; i < arguments.length; i++) {
|
2021-01-08 23:47:31 +01:00
|
|
|
final Argument<?> argument = arguments[i];
|
2020-07-10 18:12:29 +02:00
|
|
|
final boolean isLast = i == arguments.length - 1;
|
|
|
|
|
2021-02-20 19:18:28 +01:00
|
|
|
// Search previously parsed syntaxes to find identical part in order to create a node between those
|
|
|
|
{
|
|
|
|
// Find shared part
|
|
|
|
boolean foundSharedPart = false;
|
|
|
|
for (Argument<?>[] parsedArguments : syntaxesArguments) {
|
|
|
|
final int index = i + 1;
|
|
|
|
if (ArrayUtils.sameStart(arguments, parsedArguments, index)) {
|
|
|
|
final Argument<?> sharedArgument = parsedArguments[i];
|
|
|
|
final List<DeclareCommandsPacket.Node[]> storedNodes = storedArgumentsNodes.get(sharedArgument);
|
|
|
|
|
|
|
|
argChildren = new IntArrayList();
|
|
|
|
lastNodes = storedNodes.get(index);
|
|
|
|
foundSharedPart = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (foundSharedPart) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-11 06:37:09 +01:00
|
|
|
// Process the nodes for the argument
|
|
|
|
{
|
|
|
|
argument.processNodes(nodeMaker, isLast);
|
|
|
|
|
|
|
|
// Each node array represent a layer
|
|
|
|
final List<DeclareCommandsPacket.Node[]> nodesLayer = nodeMaker.getNodes();
|
2021-02-20 19:18:28 +01:00
|
|
|
storedArgumentsNodes.put(argument, nodesLayer);
|
2021-02-11 06:37:09 +01:00
|
|
|
for (int nodeIndex = lastArgumentNodeIndex; nodeIndex < nodesLayer.size(); nodeIndex++) {
|
2021-02-11 20:36:02 +01:00
|
|
|
final NodeMaker.ConfiguredNodes configuredNodes = nodeMaker.getConfiguredNodes().get(nodeIndex);
|
|
|
|
final NodeMaker.Options options = configuredNodes.getOptions();
|
2021-02-11 06:37:09 +01:00
|
|
|
final DeclareCommandsPacket.Node[] argumentNodes = nodesLayer.get(nodeIndex);
|
|
|
|
|
|
|
|
for (DeclareCommandsPacket.Node argumentNode : argumentNodes) {
|
|
|
|
final int childId = nodes.size();
|
2021-02-11 20:36:02 +01:00
|
|
|
nodeMaker.getNodeIdsMap().put(argumentNode, childId);
|
2021-02-11 06:37:09 +01:00
|
|
|
argChildren.add(childId);
|
|
|
|
|
|
|
|
// Append to the last node
|
|
|
|
{
|
|
|
|
final int[] children = ArrayUtils.toArray(argChildren);
|
|
|
|
for (DeclareCommandsPacket.Node lastNode : lastNodes) {
|
|
|
|
lastNode.children = lastNode.children == null ?
|
|
|
|
children :
|
|
|
|
ArrayUtils.concatenateIntArrays(lastNode.children, children);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nodes.add(argumentNode);
|
|
|
|
}
|
2021-02-09 00:33:01 +01:00
|
|
|
|
2021-02-11 20:36:02 +01:00
|
|
|
if (options.shouldUpdateLastNode()) {
|
|
|
|
// 'previousNodes' used if the nodes options require to overwrite the parent
|
|
|
|
final DeclareCommandsPacket.Node[] previousNodes = options.getPreviousNodes();
|
|
|
|
|
|
|
|
lastNodes = previousNodes != null ? previousNodes : argumentNodes;
|
|
|
|
argChildren = new IntArrayList();
|
|
|
|
}
|
2021-02-09 00:33:01 +01:00
|
|
|
}
|
2021-02-11 06:37:09 +01:00
|
|
|
|
|
|
|
// Used to do not re-compute the previous arguments
|
|
|
|
lastArgumentNodeIndex = nodesLayer.size();
|
2020-07-10 18:12:29 +02:00
|
|
|
}
|
|
|
|
}
|
2021-02-20 19:18:28 +01:00
|
|
|
syntaxesArguments.add(arguments);
|
2020-07-10 18:12:29 +02:00
|
|
|
}
|
2021-02-12 00:00:44 +01:00
|
|
|
|
|
|
|
literalNode.children = ArrayUtils.toArray(cmdChildren);
|
2021-02-20 08:59:15 +01:00
|
|
|
return literalNode;
|
2021-02-09 15:25:18 +01:00
|
|
|
|
2020-07-10 18:12:29 +02:00
|
|
|
}
|
|
|
|
|
2020-10-24 16:58:27 +02:00
|
|
|
@NotNull
|
|
|
|
private DeclareCommandsPacket.Node createMainNode(@NotNull String name, boolean executable) {
|
2020-07-10 18:12:29 +02:00
|
|
|
DeclareCommandsPacket.Node literalNode = new DeclareCommandsPacket.Node();
|
2021-02-11 03:00:13 +01:00
|
|
|
literalNode.flags = DeclareCommandsPacket.getFlag(DeclareCommandsPacket.NodeType.LITERAL, executable, false, false);
|
2020-07-10 18:12:29 +02:00
|
|
|
literalNode.name = name;
|
|
|
|
|
|
|
|
return literalNode;
|
|
|
|
}
|
2021-02-12 00:00:44 +01:00
|
|
|
|
2021-02-13 08:43:38 +01:00
|
|
|
private int addCommandNameNode(@NotNull DeclareCommandsPacket.Node commandNode,
|
|
|
|
@NotNull IntList rootChildren,
|
|
|
|
@NotNull List<DeclareCommandsPacket.Node> nodes) {
|
|
|
|
final int node = nodes.size();
|
|
|
|
rootChildren.add(node);
|
2021-02-12 00:00:44 +01:00
|
|
|
nodes.add(commandNode);
|
2021-02-13 08:43:38 +01:00
|
|
|
return node;
|
2021-02-12 00:00:44 +01:00
|
|
|
}
|
2020-04-05 10:15:21 +02:00
|
|
|
}
|