fix-commodore (#27)

This commit is contained in:
Alexander Söderberg 2020-10-06 12:39:06 +02:00 committed by GitHub
parent 8f8f98b189
commit c3469706ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 435 additions and 11 deletions

View file

@ -21,6 +21,16 @@ checkstyle {
configFile file('config/checkstyle/checkstyle.xml')
}
gradle.taskGraph.whenReady {
gradle.taskGraph.allTasks.each {
if (it.project.name.contains('example')) {
it.onlyIf {
project.hasProperty('compile-examples')
}
}
}
}
allprojects {
apply plugin: 'idea'
apply plugin: 'checkstyle'

View file

@ -0,0 +1,37 @@
//
// MIT License
//
// Copyright (c) 2020 Alexander Söderberg & Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
package cloud.commandframework.annotations.specifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation used to make {@link cloud.commandframework.arguments.standard.StringArgument string arguments} greedy
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Greedy {
}

View file

@ -55,6 +55,10 @@ public final class StandardParameters {
* The command should be hidden from help menus, etc
*/
public static final ParserParameter<Boolean> HIDDEN = create("hidden", TypeToken.get(Boolean.class));
/**
* Indicates that a string argument should be greedy
*/
public static final ParserParameter<Boolean> GREEDY = create("greedy", TypeToken.get(Boolean.class));
private StandardParameters() {
}

View file

@ -23,6 +23,7 @@
//
package cloud.commandframework.arguments.parser;
import cloud.commandframework.annotations.specifier.Greedy;
import cloud.commandframework.arguments.standard.UUIDArgument;
import cloud.commandframework.annotations.specifier.Completions;
import cloud.commandframework.annotations.specifier.Range;
@ -101,9 +102,15 @@ public final class StandardParserRegistry<C> implements ParserRegistry<C> {
(double) options.get(StandardParameters.RANGE_MAX, Double.MAX_VALUE)));
this.registerParserSupplier(TypeToken.get(Character.class), options -> new CharArgument.CharacterParser<C>());
/* Make this one less awful */
this.registerParserSupplier(TypeToken.get(String.class), options -> new StringArgument.StringParser<C>(
StringArgument.StringMode.SINGLE, (context, s) ->
Arrays.asList(options.get(StandardParameters.COMPLETIONS, new String[0]))));
this.registerParserSupplier(TypeToken.get(String.class), options -> {
final boolean greedy = options.get(StandardParameters.GREEDY, false);
final StringArgument.StringMode stringMode = greedy
? StringArgument.StringMode.GREEDY
: StringArgument.StringMode.SINGLE;
return new StringArgument.StringParser<C>(
stringMode,
(context, s) -> Arrays.asList(options.get(StandardParameters.COMPLETIONS, new String[0])));
});
/* Add options to this */
this.registerParserSupplier(TypeToken.get(Boolean.class), options -> new BooleanArgument.BooleanParser<>(false));
this.registerParserSupplier(TypeToken.get(UUID.class), options -> new UUIDArgument.UUIDParser<>());
@ -276,4 +283,15 @@ public final class StandardParserRegistry<C> implements ParserRegistry<C> {
}
private static final class GreedyMapper implements BiFunction<@NonNull Greedy, @NonNull TypeToken<?>,
@NonNull ParserParameters> {
@Override
public @NonNull ParserParameters apply(@NonNull final Greedy greedy, @NonNull final TypeToken<?> typeToken) {
return ParserParameters.single(StandardParameters.GREEDY, true);
}
}
}

View file

@ -76,6 +76,8 @@ You can check whether or not the running server supports Brigadier, by using `bu
## cloud-paper
An example plugin using the `cloud-paper` API can be found [here](https://github.com/Sauilitired/cloud/tree/master/examples/example-bukkit).
`cloud-paper`works on all Bukkit derivatives and has graceful fallbacks for cases where Paper specific features are missing. It is initialized the same way as the Bukkit manager, except `PaperCommandManager`is used instead. When using Paper 1.15+ Brigadier mappings are available even without commodore present.
### dependency

View file

@ -256,6 +256,7 @@ public final class CloudBrigadierManager<C, S> {
* @param label Command label
* @param cloudCommand Cloud command instance
* @param permissionChecker Permission checker
* @param forceRegister Whether or not to force register an executor at every node
* @param executor Command executor
* @return Literal command node
*/
@ -263,6 +264,7 @@ public final class CloudBrigadierManager<C, S> {
final @NonNull Command<C> cloudCommand,
final @NonNull BiPredicate<@NonNull S,
@NonNull CommandPermission> permissionChecker,
final boolean forceRegister,
final com.mojang.brigadier.@NonNull Command<S> executor) {
final CommandTree.Node<CommandArgument<C, ?>> node = this.commandManager
.getCommandTree().getNamedNode(cloudCommand.getArguments().get(0).getName());
@ -272,10 +274,13 @@ public final class CloudBrigadierManager<C, S> {
.requires(sender -> permissionChecker.test(sender, (CommandPermission) node.getNodeMeta()
.getOrDefault("permission",
Permission.empty())));
if (forceRegister || (node.getValue() != null && node.getValue().getOwningCommand() != null)) {
literalArgumentBuilder.executes(executor);
}
literalArgumentBuilder.executes(executor);
final LiteralCommandNode<S> constructedRoot = literalArgumentBuilder.build();
for (final CommandTree.Node<CommandArgument<C, ?>> child : node.getChildren()) {
constructedRoot.addChild(this.constructCommandNode(true, child,
constructedRoot.addChild(this.constructCommandNode(forceRegister, child,
permissionChecker, executor, provider).build());
}
return constructedRoot;

View file

@ -72,14 +72,16 @@ public class BukkitPluginRegistrationHandler<C> implements CommandRegistrationHa
public final boolean registerCommand(final @NonNull Command<?> command) {
/* We only care about the root command argument */
final CommandArgument<?, ?> commandArgument = command.getArguments().get(0);
if (this.registeredCommands.containsKey(commandArgument)) {
if (!(this.bukkitCommandManager.getCommandRegistrationHandler() instanceof CloudCommodoreManager)
&& this.registeredCommands.containsKey(commandArgument)) {
return false;
}
final String label;
final String prefixedLabel = String.format("%s:%s", this.bukkitCommandManager.getOwningPlugin().getName(),
commandArgument.getName()).toLowerCase();
if (bukkitCommands.containsKey(commandArgument.getName())) {
if (!(this.bukkitCommandManager.getCommandRegistrationHandler() instanceof CloudCommodoreManager)
&& bukkitCommands.containsKey(commandArgument.getName())) {
label = prefixedLabel;
} else {
label = commandArgument.getName();

View file

@ -26,11 +26,17 @@ package cloud.commandframework.bukkit;
import cloud.commandframework.Command;
import cloud.commandframework.brigadier.CloudBrigadierManager;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.permission.CommandPermission;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import me.lucko.commodore.Commodore;
import me.lucko.commodore.CommodoreProvider;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Collections;
@SuppressWarnings("ALL")
class CloudCommodoreManager<C> extends BukkitPluginRegistrationHandler<C> {
@ -57,9 +63,28 @@ class CloudCommodoreManager<C> extends BukkitPluginRegistrationHandler<C> {
final @NonNull BukkitCommand<C> bukkitCommand) {
final com.mojang.brigadier.Command<?> cmd = o -> 1;
final LiteralCommandNode<?> literalCommandNode = this.brigadierManager
.createLiteralCommandNode(label, command, (o, p) -> true, cmd);
this.commodore.register(bukkitCommand, literalCommandNode, p ->
this.commandManager.hasPermission(commandManager.getCommandSenderMapper().apply(p),
command.getCommandPermission()));
.<Object>createLiteralCommandNode(label, command, (o, p) -> {
final CommandSender sender = this.commodore.getBukkitSender(o);
return this.commandManager.hasPermission(this.commandManager.getCommandSenderMapper().apply(sender),
(CommandPermission) p);
}, false, cmd);
final CommandNode existingNode = this.commodore.getDispatcher().findNode(Collections.singletonList(label));
if (existingNode != null) {
this.mergeChildren(existingNode, literalCommandNode);
} else {
this.commodore.register(literalCommandNode);
}
}
private void mergeChildren(@Nullable final CommandNode<?> existingNode, @Nullable final CommandNode<?> node) {
for (final CommandNode child : node.getChildren()) {
final CommandNode<?> existingChild = existingNode.getChild(child.getName());
if (existingChild == null) {
existingNode.addChild(child);
} else {
this.mergeChildren(existingChild, child);
}
}
}
}

View file

@ -66,4 +66,14 @@ public abstract class EntitySelector {
public @NonNull String getSelector() {
return this.selector;
}
/**
* Check whether the selector selected at least one entity
*
* @return {@code true} if at least one entity was selected, else {@code false}
*/
public boolean hasAny() {
return !this.entities.isEmpty();
}
}

View file

@ -72,7 +72,7 @@ final class VelocityPluginRegistrationHandler<C> implements CommandRegistrationH
this.brigadierManager.createLiteralCommandNode(command.getArguments().get(0).getName(), (Command<C>) command,
(c, p) -> this.manager.hasPermission(
this.manager.getCommandSenderMapper()
.apply(c), p),
.apply(c), p), true,
commandContext -> {
final CommandSource source = commandContext.getSource();
final String input = commandContext.getInput();

View file

@ -0,0 +1,276 @@
//
// MIT License
//
// Copyright (c) 2020 Alexander Söderberg & Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
package cloud.commandframework.examples.bukkit;
import cloud.commandframework.Command;
import cloud.commandframework.CommandTree;
import cloud.commandframework.Description;
import cloud.commandframework.MinecraftHelp;
import cloud.commandframework.annotations.AnnotationParser;
import cloud.commandframework.annotations.Argument;
import cloud.commandframework.annotations.CommandDescription;
import cloud.commandframework.annotations.CommandMethod;
import cloud.commandframework.annotations.CommandPermission;
import cloud.commandframework.annotations.Confirmation;
import cloud.commandframework.annotations.Flag;
import cloud.commandframework.annotations.specifier.Greedy;
import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.arguments.parser.ParserParameters;
import cloud.commandframework.arguments.parser.StandardParameters;
import cloud.commandframework.bukkit.BukkitCommandManager;
import cloud.commandframework.bukkit.BukkitCommandMetaBuilder;
import cloud.commandframework.bukkit.CloudBukkitCapabilities;
import cloud.commandframework.bukkit.arguments.selector.SingleEntitySelector;
import cloud.commandframework.bukkit.parsers.WorldArgument;
import cloud.commandframework.bukkit.parsers.selector.SingleEntitySelectorArgument;
import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.extra.confirmation.CommandConfirmationManager;
import cloud.commandframework.meta.CommandMeta;
import cloud.commandframework.paper.PaperCommandManager;
import cloud.commandframework.types.tuples.Triplet;
import io.leangen.geantyref.TypeToken;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.util.Vector;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
* Example plugin class
*/
public final class ExamplePlugin extends JavaPlugin {
private BukkitCommandManager<CommandSender> manager;
private BukkitAudiences bukkitAudiences;
private MinecraftHelp<CommandSender> minecraftHelp;
private CommandConfirmationManager<CommandSender> confirmationManager;
private AnnotationParser<CommandSender> annotationParser;
@Override
public void onEnable() {
//
// This is a function that will provide a command execution coordinator that parses and executes commands
// asynchronously
//
// final Function<CommandTree<CommandSender>, CommandExecutionCoordinator<CommandSender>> executionCoordinatorFunction =
// AsynchronousCommandExecutionCoordinator.<CommandSender>newBuilder().build();
//
// However, in this example it is fine for us to run everything synchronously
//
final Function<CommandTree<CommandSender>, CommandExecutionCoordinator<CommandSender>> executionCoordinatorFunction =
CommandExecutionCoordinator.simpleCoordinator();
//
// This function maps the command sender type of our choice to the bukkit command sender.
// However, in this example we use the Bukkit command sender, and so we just need to map it
// to itself
//
final Function<CommandSender, CommandSender> mapperFunction = Function.identity();
try {
this.manager = new PaperCommandManager<>(
/* Owning plugin */ this,
/* Coordinator function */ executionCoordinatorFunction,
/* Command Sender -> C */ mapperFunction,
/* C -> Command Sender */ mapperFunction
);
} catch (final Exception e) {
this.getLogger().severe("Failed to initialize the command manager");
/* Disable the plugin */
this.getServer().getPluginManager().disablePlugin(this);
return;
}
//
// Create a BukkitAudiences instance (adventure) in order to use the minecraft-extras
// help system
//
this.bukkitAudiences = BukkitAudiences.create(this);
//
// Create the Minecraft help menu system
//
this.minecraftHelp = new MinecraftHelp<>(
/* Help Prefix */ "/example help",
/* Audience mapper */ this.bukkitAudiences::sender,
/* Manager */ this.manager
);
//
// Register Brigadier mappings
//
if (manager.queryCapability(CloudBukkitCapabilities.BRIGADIER)) {
manager.registerBrigadier();
}
//
// Register asynchronous completions
//
if (manager.queryCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) {
((PaperCommandManager<CommandSender>) this.manager).registerAsynchronousCompletions();
}
//
// Create the confirmation manager. This allows us to require certain commands to be
// confirmed before they can be executed
//
this.confirmationManager = new CommandConfirmationManager<>(
/* Timeout */ 30L,
/* Timeout unit */ TimeUnit.SECONDS,
/* Action when confirmation is required */ context -> context.getCommandContext().getSender().sendMessage(
ChatColor.RED + "Confirmation required. Confirm using /example confirm."),
/* Action when no confirmation is pending */ sender -> sender.sendMessage(
ChatColor.RED + "You don't have any pending commands.")
);
//
// Register the confirmation processor. This will enable confirmations for commands that require it
//
this.confirmationManager.registerConfirmationProcessor(manager);
//
// Create the annotation parser. This allows you to define commands using methods annotated with
// @CommandMethod
//
final Function<ParserParameters, CommandMeta> commandMetaFunction = p ->
BukkitCommandMetaBuilder.builder()
// This will allow you to decorate commands with descriptions
.withDescription(p.get(StandardParameters.DESCRIPTION, "No description"))
.build();
this.annotationParser = new AnnotationParser<>(
/* Manager */ this.manager,
/* Command sender type */ CommandSender.class,
/* Mapper for command meta instances */ commandMetaFunction
);
//
// Create the commands
//
this.constructCommands();
}
private void constructCommands() {
//
// Parse all @CommandMethod-annotated methods
//
this.annotationParser.parse(this);
//
// Base command builder
//
final Command.Builder<CommandSender> builder = this.manager.commandBuilder("example");
//
// Add a confirmation command
//
this.manager.command(builder.literal("confirm")
.meta("description", "Confirm a pending command")
.handler(this.confirmationManager.createConfirmationExecutionHandler()));
//
// Create a world argument
//
final CommandArgument<CommandSender, World> worldArgument = WorldArgument.of("world");
//
// Create a teleportation command
//
this.manager.command(builder.literal("teleport")
.literal("me")
// Require a player sender
.withSenderType(Player.class)
.argument(worldArgument, Description.of("World name"))
.argumentTriplet(
"coords",
TypeToken.get(Vector.class),
Triplet.of("x", "y", "z"),
Triplet.of(Integer.class, Integer.class, Integer.class),
triplet -> new Vector(triplet.getFirst(), triplet.getSecond(), triplet.getThird()),
Description.of("Coordinates"))
.handler(context -> {
final Player player = (Player) context.getSender();
final World world = context.get(worldArgument);
final Vector coords = context.get("coords");
final Location location = coords.toLocation(world);
player.teleport(location);
}))
.command(builder.literal("teleport")
.literal("entity")
.withSenderType(Player.class)
.argument(SingleEntitySelectorArgument.of("entity"),
Description.of("Entity to teleport"))
.literal("here")
.handler(commandContext -> {
final Player player = (Player) commandContext.getSender();
final SingleEntitySelector singleEntitySelector = commandContext.get("entity");
if (singleEntitySelector.hasAny()) {
singleEntitySelector.getEntity().teleport(player);
player.sendMessage(ChatColor.GREEN + "The entity was teleported to you!");
} else {
player.sendMessage(ChatColor.RED + "No entity matched your query.");
}
}));
}
@CommandMethod("example help [query]")
@CommandDescription("Help menu")
private void commandHelp(final @NonNull CommandSender sender,
final @Argument("query") @Greedy String query) {
this.minecraftHelp.queryCommands(query == null ? "" : query, sender);
}
@Confirmation
@CommandMethod("example clear")
@CommandDescription("Clear your inventory")
@CommandPermission("example.clear")
private void commandClear(final @NonNull Player player) {
player.getInventory().clear();
this.bukkitAudiences.player(player)
.sendMessage(Component.text("Your inventory has been cleared", NamedTextColor.GOLD));
}
@CommandMethod("example give <material> <amount>")
@CommandDescription("Give yourself an item")
private void commandGive(final @NonNull Player player,
final @NonNull @Argument("material") Material material,
final @Argument("amount") int number,
final @Nullable @Flag("color") ChatColor nameColor) {
final ItemStack itemStack = new ItemStack(material, number);
String itemName = String.format("%s's %s",
player.getName(),
material.name()
.toLowerCase()
.replace('_', ' '));
if (nameColor != null) {
itemName = nameColor + itemName;
}
final ItemMeta meta = itemStack.getItemMeta();
if (meta != null) {
meta.setDisplayName(itemName);
itemStack.setItemMeta(meta);
}
player.getInventory().addItem(itemStack);
player.sendMessage(ChatColor.GREEN + String.format("You have been given %d x %s", number, material));
}
}

View file

@ -0,0 +1,28 @@
//
// MIT License
//
// Copyright (c) 2020 Alexander Söderberg & Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
/**
* Bukkit example plugin
*/
package cloud.commandframework.examples.bukkit;

View file

@ -0,0 +1,4 @@
name: ExamplePlugin
version: 1.0.0
api-version: 1.13
main: cloud.commandframework.examples.bukkit.ExamplePlugin

View file

@ -12,6 +12,7 @@ include(':cloud-minecraft-extras')
include(':cloud-cloudburst')
include(':cloud-javacord')
include(':cloud-jda')
include(':example-bukkit')
project(':cloud-bukkit').projectDir = file('cloud-minecraft/cloud-bukkit')
project(':cloud-paper').projectDir = file('cloud-minecraft/cloud-paper')
project(':cloud-brigadier').projectDir = file('cloud-minecraft/cloud-brigadier')
@ -21,3 +22,5 @@ project(':cloud-minecraft-extras').projectDir = file('cloud-minecraft/cloud-mine
project(':cloud-cloudburst').projectDir = file('cloud-minecraft/cloud-cloudburst')
project(':cloud-javacord').projectDir = file('cloud-discord/cloud-javacord')
project(':cloud-jda').projectDir = file('cloud-discord/cloud-jda')
project(':example-bukkit').projectDir = file('examples/example-bukkit')