Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/main/java/ch/njol/skript/Skript.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import ch.njol.skript.classes.data.JavaClasses;
import ch.njol.skript.classes.data.SkriptClasses;
import ch.njol.skript.command.Commands;
import ch.njol.skript.command.brigadier.BrigadierModule;
import ch.njol.skript.doc.Documentation;
import ch.njol.skript.events.EvtSkript;
import ch.njol.skript.hooks.Hook;
Expand Down Expand Up @@ -101,6 +102,7 @@
import org.skriptlang.skript.bukkit.registration.BukkitRegistryKeys;
import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos;
import org.skriptlang.skript.bukkit.tags.TagModule;
import org.skriptlang.skript.lang.command.ArgumentTypeElement;
import org.skriptlang.skript.lang.comparator.Comparator;
import org.skriptlang.skript.lang.comparator.Comparators;
import org.skriptlang.skript.lang.converter.Converter;
Expand Down Expand Up @@ -586,6 +588,7 @@ public void onEnable() {
FurnaceModule.load();
LootTableModule.load();
skript.loadModules(new DamageSourceModule());
skript.loadModules(new BrigadierModule());
} catch (final Exception e) {
exception(e, "Could not load required .class files: " + e.getLocalizedMessage());
setEnabled(false);
Expand Down Expand Up @@ -1735,6 +1738,22 @@ public static boolean dispatchCommand(final CommandSender sender, final String c
}
}

/**
* Registers a new argument type.
*
* @param argumentClass class of the argument type
* @param patterns patterns
* @param <E> argument type
*/
public static <E extends ArgumentTypeElement<?>> void registerArgumentType(Class<E> argumentClass, String... patterns) {
checkAcceptRegistrations();
skript.syntaxRegistry().register(ArgumentTypeElement.REGISTRY_KEY, SyntaxInfo.builder(argumentClass)
.origin(getSyntaxOrigin(argumentClass))
.addPatterns(patterns)
.build()
);
}

// ================ LOGGING ================

public static boolean logNormal() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package ch.njol.skript.command.brigadier;

import ch.njol.skript.command.CommandEvent;
import com.mojang.brigadier.context.CommandContext;
import org.bukkit.command.CommandSender;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;

/**
* Event for executing brigadier commands.
*/
public class BrigadierCommandEvent extends CommandEvent {

private final CommandContext<CommandSender> context;

public BrigadierCommandEvent(CommandContext<CommandSender> context) {
super(context.getSource(), context.getRootNode().getName(),
context.getNodes().stream()
.map(arg -> arg.getRange().get(context.getInput()))
.toArray(String[]::new));
this.context = context;
}

/**
* @return command execution context
*/
public CommandContext<CommandSender> getContext() {
return context;
}

// Bukkit stuff
private final static HandlerList handlers = new HandlerList();

@Override
public @NotNull HandlerList getHandlers() {
return handlers;
}

public static HandlerList getHandlerList() {
return handlers;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package ch.njol.skript.command.brigadier;

import ch.njol.skript.Skript;
import com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent;
import com.google.common.base.Preconditions;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import org.bukkit.Bukkit;
import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import org.jetbrains.annotations.UnmodifiableView;
import org.skriptlang.skript.brigadier.RootSkriptCommandNode;
import org.skriptlang.skript.lang.command.CommandHandler;
import org.skriptlang.skript.lang.command.CommandSourceType;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

public class BrigadierCommandHandler implements CommandHandler<CommandSender>, Listener {

private final Map<String, RootSkriptCommandNode<CommandSender>> commands = new ConcurrentHashMap<>();
private @Nullable CommandDispatcher<CommandSender> dispatcher;

@Override
public synchronized boolean registerCommand(RootSkriptCommandNode<CommandSender> command) {
if (commands.containsKey(command.getLiteral())) {
Skript.error("Command " + command.getLiteral() + " is already registered");
return false;
}
commands.put(command.getLiteral(), command);
// TODO proper registration with aliases and help page
Bukkit.getCommandMap().getKnownCommands().put(command.getLiteral(), new WrappedBrigadierCommand(command));
dispatcher = null; // reset dispatcher
Bukkit.getScheduler().runTaskLater(Skript.getInstance(), PaperCommandUtils::syncCommands, 1);
return true;
}

@Override
public synchronized boolean unregisterCommand(RootSkriptCommandNode<CommandSender> command) {
boolean result = commands.remove(command.getLiteral(), command);
if (!result) return false;
Bukkit.getCommandMap().getKnownCommands().remove(command.getLiteral());
dispatcher = null; // reset dispatcher
Bukkit.getScheduler().runTaskLater(Skript.getInstance(), PaperCommandUtils::syncCommands, 1);
return true;
}

@Override
public @Nullable RootSkriptCommandNode<CommandSender> getCommand(String label) {
return commands.get(label);
}

@Override
public @UnmodifiableView Map<String, RootSkriptCommandNode<CommandSender>> getAllCommands() {
return Collections.unmodifiableMap(commands);
}

@Override
public boolean dispatchCommand(CommandSender source, String input) {
if (dispatcher == null) {
dispatcher = new CommandDispatcher<>();
commands.values().forEach(cmd -> dispatcher.register(cmd.flatToBuilder()));
}

// TODO
// if the command does not exist return false
// send error message to source similar to vanilla
try {
dispatcher.execute(input, source);
} catch (CommandSyntaxException exception) {
Skript.getInstance().getSLF4JLogger().info("Command syntax error", exception);
}
return true;
}

@Override
public @Unmodifiable Set<CommandSourceType> supportedTypes() {
return Set.of(
CommandSourceType.typed(Player.class, "player", "the player", "players", "the players"),
CommandSourceType.typed(ConsoleCommandSender.class, "console", "the console", "server", "the server"),
CommandSourceType.typed(BlockCommandSender.class, "block", "blocks"),
CommandSourceType.typed(Entity.class, "entity", "entities")
);
}

private static final @Nullable Field NODE_CHILDREN_FIELD;

static {
Field nodeChildrenField = null;
try {
nodeChildrenField = CommandNode.class.getDeclaredField("children");
nodeChildrenField.setAccessible(true);
} catch (Exception exception) {
if (Skript.debug())
throw Skript.exception(exception, "Failed to access the command node children field");
}
NODE_CHILDREN_FIELD = nodeChildrenField;
}

@EventHandler
@SuppressWarnings("UnstableApiUsage")
public void onAsyncPlayerSendCommands(AsyncPlayerSendCommandsEvent<@NotNull CommandSourceStack> event) {
if (!event.isAsynchronous() && event.hasFiredAsync())
return;
RootCommandNode<CommandSourceStack> rootNode = event.getCommandNode();
Consumer<CommandNode<CommandSourceStack>> bukkitNodeRemover = node -> {};
try {
Preconditions.checkNotNull(NODE_CHILDREN_FIELD);
//noinspection unchecked
Map<String, CommandNode<CommandSourceStack>> children = (Map<String, CommandNode<CommandSourceStack>>)
NODE_CHILDREN_FIELD.get(rootNode);
bukkitNodeRemover = node -> children.remove(node.getName());
} catch (Exception exception) {
if (Skript.debug())
throw Skript.exception(exception, "Failed to access the node children field");
}

for (RootSkriptCommandNode<CommandSender> node : commands.values()) {
CommandNode<CommandSourceStack> paperCompatible = convertToClientsidePaper(node.flat());
bukkitNodeRemover.accept(paperCompatible);
rootNode.addChild(paperCompatible);
}
}

// TODO suggestions

/**
* Converts given command node to a node that can be safely sent to the client.
*
* @param node node to convert
* @return node that can be sent to the client
*/
// TODO convert paper argument types to NMS argument types
// TODO filter out commands with requirements player does not meet (mirrors vanilla behaviour)
private CommandNode<CommandSourceStack> convertToClientsidePaper(CommandNode<CommandSender> node) {
ArgumentBuilder<CommandSourceStack, ?> builder;
if (node instanceof LiteralCommandNode<CommandSender> lcn) {
builder = LiteralArgumentBuilder.literal(lcn.getLiteral());
} else if (node instanceof ArgumentCommandNode<?,?> acn) {
builder = RequiredArgumentBuilder.argument(acn.getName(), acn.getType());
} else {
throw new IllegalArgumentException("Unsupported node implementation; only native Brigadier nodes "
+ "are supported");
}
if (node.getRequirement() != null)
builder.requires(stack -> node.getRequirement().test(new WrappedCommandSourceStack(stack)));
if (node.getRedirect() != null)
builder.forward(convertToClientsidePaper(node.getRedirect()), ctx -> Collections.emptyList(), node.isFork());
if (node.getCommand() != null)
builder.executes(ctx -> com.mojang.brigadier.Command.SINGLE_SUCCESS);
node.getChildren().forEach(child -> builder.then(convertToClientsidePaper(child)));
return builder.build();
}

private class WrappedBrigadierCommand extends Command {

protected WrappedBrigadierCommand(RootSkriptCommandNode<CommandSender> node) {
super(node.getName(), node.getDescription() != null ? node.getDescription() : "",
"" /* TODO usage */, new ArrayList<>(node.getAliases()));
}

@Override
public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String @NotNull [] args) {
return BrigadierCommandHandler.this.dispatchCommand(sender, getName() + " " + String.join(" ", args));
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ch.njol.skript.command.brigadier;

import ch.njol.skript.Skript;
import org.skriptlang.skript.addon.AddonModule;
import org.skriptlang.skript.addon.SkriptAddon;

import java.io.IOException;

/**
* Module for Brigadier commands.
*/
public class BrigadierModule implements AddonModule {

@Override
public void load(SkriptAddon addon) {
try {
Skript.getAddonInstance().loadClasses("ch.njol.skript.command.brigadier");
} catch (IOException e) {
throw new RuntimeException(e);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package ch.njol.skript.command.brigadier;

import com.mojang.brigadier.context.CommandContext;
import org.bukkit.command.CommandSender;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;

/**
* Event for retrieving command suggestions (tab completions).
*/
public class BrigadierSuggestionsEvent extends BrigadierCommandEvent {

private final List<String> suggestions = new ArrayList<>();

public BrigadierSuggestionsEvent(CommandContext<CommandSender> context) {
super(context);
}

/**
* @return suggestions
*/
public String[] getSuggestions() {
return suggestions.toArray(String[]::new);
}

/**
* @param suggestions new suggestions
*/
public void setSuggestions(List<String> suggestions) {
this.suggestions.clear();
if (suggestions != null)
this.suggestions.addAll(suggestions);
}

/**
* @param suggestions new suggestions
*/
public void setSuggestions(String... suggestions) {
setSuggestions(List.of(suggestions));
}

// Bukkit stuff
private final static HandlerList handlers = new HandlerList();

@Override
public @NotNull HandlerList getHandlers() {
return handlers;
}

public static HandlerList getHandlerList() {
return handlers;
}

}
Loading
Loading