feat: add annotation-driven command system (#12)

dependabot/gradle/org.projectlombok-lombok-1.18.34 1.2
William 5 months ago committed by GitHub
parent ab1b641471
commit 8c62c77bf3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -19,7 +19,7 @@
Versions are available on maven in the format `net.william278.uniform:ARTIFACT:VERSION`. See below for a table of supported platforms.
Note that Uniform versions omit the `v` prefix. Fabric versions are suffixed with the target Minecraft version (e.g. `1.1.11+1.21`) and also require Fabric API installed on the server. Sponge versions are suffixed with the target Sponge API version (e.g. `1.1.11+11`).
Note that Uniform versions omit the `v` prefix. Fabric versions are suffixed with the target Minecraft version (e.g. `1.2+1.21`) and also require Fabric API installed on the server. Sponge versions are suffixed with the target Sponge API version (e.g. `1.2+11`).
<table align="center">
<thead>
@ -89,7 +89,7 @@ Note that Uniform versions omit the `v` prefix. Fabric versions are suffixed wit
</tbody>
</table>
Example: To target Uniform on Bukkit, the artifact is `net.william278.uniform:uniform-bukkit:1.1.11` (check that this version is up-to-date &ndash; make sure you target the latest available!).
Example: To target Uniform on Bukkit, the artifact is `net.william278.uniform:uniform-bukkit:1.2` (check that this version is up-to-date &ndash; make sure you target the latest available!).
## Setup
Uniform is available [on Maven](https://repo.william278.net/#/releases/net/william278/uniform/). You can browse the Javadocs [here](https://repo.william278.net/javadoc/releases/net/william278/uniform/latest).
@ -104,7 +104,7 @@ repositories {
}
```
Then, add the dependency itself. Replace `VERSION` with the latest release version. (e.g., `1.1.11`) and `PLATFORM` with the platform you are targeting (e.g., `paper`). If you want to target pre-release "snapshot" versions (not recommended), you should use the `/snapshots` repository instead.
Then, add the dependency itself. Replace `VERSION` with the latest release version. (e.g., `1.2`) and `PLATFORM` with the platform you are targeting (e.g., `paper`). If you want to target pre-release "snapshot" versions (not recommended), you should use the `/snapshots` repository instead.
```groovy
dependencies {
@ -120,28 +120,53 @@ Uniform lets you create commands either natively per-platform, or cross-platform
Check `example-plugin` for a full example of a cross-platform command being registered on Paper.
### Platform-specific commands
Extend the platform-specific `PlatformCommand` class and add your Brigadier syntax.
### Cross-platform commands
Cross-platform commands can be created by registering `Command` objects; you can create these from `@CommandNode` annotated objects, or by extending `Command` and providing these yourself.
#### Using annotations
You can use the `@CommandNode` annotations to easily create cross-platform Brigadier commands (since: v1.2). This is the recommended way to create commands.
```java
public class ExampleCommand extends PaperCommand {
public ExampleCommand() {
super("example", "platform-specific");
command.setDefaultExecutor((context) -> {
context.getSource().getBukkitSender().sendMessage("Hello, world!");
});
addSyntax((context) -> {
context.getSource().getBukkitSender().sendMessage("Woah!!!!");
String arg = context.getArgument("message", String.class);
context.getSource().getBukkitSender()
.sendMessage(MiniMessage.miniMessage().deserialize(arg));
}, stringArg("message"));
@CommandNode(
value = "helloworld",
aliases = {"hello", "hi"},
description = "A simple hello world command",
permission = @PermissionNode(
value = "example.command.helloworld",
defaultValue = Permission.Default.TRUE
)
)
public class AnnotatedCommand {
@Syntax
public void execute(CommandUser user) {
user.getAudience().sendMessage(Component.text("Hello, world!"));
}
@Syntax
public void pongMessage(
CommandUser user,
@Argument(name = "message", parser = Argument.StringArg.class) String message
) {
user.getAudience().sendMessage(Component.text("Hello, " + message, NamedTextColor.GREEN));
}
@CommandNode(
value = "subcommand",
aliases = {"sub", "hi"}
)
static class SubCommand {
@Syntax
public void execute(CommandUser user) {
user.getAudience().sendMessage(Component.text("Subcommand executed!"));
}
}
}
```
### Cross-platform commands
Target `uniform-common` and extend the `Command` class. You'll want to use `BaseCommand#getUser` to get a platform-agnostic User from which you can acquire the adventure `Audience` to send messages to.
#### By extending the Command class.
You can also extend the `Command` class to create a Command object you can register. You'll want to use `BaseCommand#getUser` to get a platform-agnostic User from which you can acquire the adventure `Audience` to send messages to.
```java
public class ExampleCrossPlatCommand extends Command {
@ -178,6 +203,27 @@ public class ExampleCrossPlatCommand extends Command {
}
```
### Platform-specific commands
If you need platform-specific features, extend the platform-specific `PlatformCommand` class and add your Brigadier syntax.
```java
public class ExampleCommand extends PaperCommand {
public ExampleCommand() {
super("example", "platform-specific");
command.setDefaultExecutor((context) -> {
context.getSource().getBukkitSender().sendMessage("Hello, world!");
});
addSyntax((context) -> {
context.getSource().getBukkitSender().sendMessage("Woah!!!!");
String arg = context.getArgument("message", String.class);
context.getSource().getBukkitSender()
.sendMessage(MiniMessage.miniMessage().deserialize(arg));
}, stringArg("message"));
}
}
```
### Registering
Then, register the command with the platform-specific Uniform instance (e.g. `FabricUniform.getInstance()`, `PaperUniform.getInstance()`, etc...)

@ -199,6 +199,21 @@ public abstract class BaseCommand<S> {
return arg(name, FloatArgumentType.floatArg(min, max));
}
@NotNull
public static <S> ArgumentElement<S, Double> doubleNum(@NotNull String name) {
return arg(name, DoubleArgumentType.doubleArg());
}
@NotNull
public static <S> ArgumentElement<S, Double> doubleNum(@NotNull String name, double min) {
return arg(name, DoubleArgumentType.doubleArg(min));
}
@NotNull
public static <S> ArgumentElement<S, Double> doubleNum(@NotNull String name, double min, double max) {
return arg(name, DoubleArgumentType.doubleArg(min, max));
}
@NotNull
public static <S> ArgumentElement<S, Boolean> bool(@NotNull String name) {
return arg(name, BoolArgumentType.bool());

@ -21,16 +21,32 @@
package net.william278.uniform;
import lombok.*;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import net.william278.uniform.annotations.Argument;
import net.william278.uniform.annotations.CommandDescription;
import net.william278.uniform.annotations.CommandNode;
import net.william278.uniform.annotations.Syntax;
import net.william278.uniform.element.ArgumentElement;
import net.william278.uniform.element.CommandElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import static net.william278.uniform.CommandExecutor.methodToExecutor;
@Getter
@Setter
@RequiredArgsConstructor
@AllArgsConstructor
public abstract class Command implements CommandProvider {
@ -44,7 +60,117 @@ public abstract class Command implements CommandProvider {
return Optional.ofNullable(permission);
}
public record SubCommand(@NotNull String name, @NotNull List<String> aliases, @Nullable Permission permission, @NotNull CommandProvider provider) {
Command(@NotNull String name) {
this.name = name;
}
Command(@Nullable CommandNode node) {
if (node == null) {
throw new IllegalArgumentException("@CommandNode annotation is required on annotated command/sub-commands");
}
this.name = node.value();
this.aliases = List.of(node.aliases());
this.description = node.description();
Permission.annotated(node.permission()).ifPresent(this::setPermission);
}
static class AnnotatedCommand extends Command {
private final Object annotated;
AnnotatedCommand(@NotNull Object annotated) {
super(annotated.getClass().getAnnotation(CommandNode.class));
this.annotated = annotated;
this.setDescriptionFromAnnotation();
}
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public void provide(@NotNull BaseCommand<?> cmd) {
// Add syntaxes
for (Method method : annotated.getClass().getDeclaredMethods()) {
method.setAccessible(true);
final Syntax syntax = method.getAnnotation(Syntax.class);
if (syntax == null) {
continue;
}
// Default executor
final CommandElement[] args = getMethodArguments(method);
if (args.length == 0) {
cmd.setDefaultExecutor(methodToExecutor(method, annotated, cmd));
continue;
}
// Conditional & unconditional syntax
final Optional<Predicate> perm = Permission.annotated(syntax.permission()).map(p -> p.toPredicate(cmd));
final CommandExecutor executor = methodToExecutor(method, annotated, cmd);
if (perm.isPresent()) {
cmd.addConditionalSyntax(perm.get(), executor, args);
continue;
}
cmd.addSyntax(executor, args);
}
// Add subcommands
for (Class<?> subClass : annotated.getClass().getDeclaredClasses()) {
if (subClass.getAnnotation(CommandNode.class) == null) {
continue;
}
try {
cmd.addSubCommand(new AnnotatedCommand(
subClass.getDeclaredConstructor().newInstance()
));
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |
NoSuchMethodException e) {
throw new IllegalArgumentException(
"Failed to create sub-command instance (does it have a zero-arg constructor?)", e
);
}
}
}
@NotNull
private static CommandElement<?>[] getMethodArguments(@NotNull Method method) {
try {
final List<ArgumentElement<?, ?>> elements = new ArrayList<>();
for (Parameter param : method.getParameters()) {
final Argument arg = param.getAnnotation(Argument.class);
if (arg == null) {
continue;
}
// Pass parser properties if needed
if (arg.parserProperties().length == 0) {
elements.add(arg.parser().getDeclaredConstructor()
.newInstance().provide(arg.name()));
continue;
}
elements.add(arg.parser().getDeclaredConstructor(String[].class)
.newInstance((Object) arg.parserProperties()).provide(arg.name()));
}
return elements.toArray(new ArgumentElement[0]);
} catch (Throwable e) {
throw new IllegalArgumentException("Failed to create argument elements from method parameters", e);
}
}
private void setDescriptionFromAnnotation() {
Arrays.stream(annotated.getClass().getFields())
.filter(f -> f.getAnnotation(CommandDescription.class) != null)
.findFirst().ifPresent(f -> {
try {
f.setAccessible(true);
this.setDescription((String) f.get(annotated));
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Failed to set command description from field", e);
}
});
}
}
public record SubCommand(@NotNull String name, @NotNull List<String> aliases, @Nullable Permission permission,
@NotNull CommandProvider provider) {
public SubCommand(@NotNull String name, @NotNull List<String> aliases, @NotNull CommandProvider provider) {
this(name, aliases, null, provider);
}

@ -22,10 +22,53 @@
package net.william278.uniform;
import com.mojang.brigadier.context.CommandContext;
import net.william278.uniform.annotations.Argument;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public interface CommandExecutor<S> {
void execute(@NotNull CommandContext<S> context);
@NotNull
static <S> CommandExecutor<S> methodToExecutor(@NotNull Method method, @NotNull Object instance,
@NotNull BaseCommand<?> cmd) {
return (context) -> {
try {
method.invoke(instance, injectParams(method, context, cmd));
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException("Failed to invoke command executor from annotated method", e);
}
};
}
@Nullable
private static Object @NotNull [] injectParams(@NotNull Method method, @NotNull CommandContext<?> context,
@NotNull BaseCommand<?> cmd) {
final Object[] params = new Object[method.getParameterCount()];
for (int i = 0; i < method.getParameterCount(); i++) {
final Parameter param = method.getParameters()[i];
final Class<?> type = param.getType();
final Argument arg = param.getAnnotation(Argument.class);
if (arg != null) {
params[i] = context.getArgument(arg.name(), type);
continue;
}
if (type.isAssignableFrom(CommandUser.class)) {
params[i] = cmd.getUser(context.getSource());
continue;
}
if (type.isAssignableFrom(context.getClass())) {
params[i] = context;
continue;
}
params[i] = null;
}
return params;
}
}

@ -21,8 +21,10 @@
package net.william278.uniform;
import net.william278.uniform.annotations.PermissionNode;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
import java.util.function.Predicate;
@SuppressWarnings("unused")
@ -47,6 +49,14 @@ public record Permission(@NotNull String node, @NotNull Default defaultValue) {
return new Permission(node, Default.FALSE);
}
@NotNull
static Optional<Permission> annotated(@NotNull PermissionNode annotation) {
if (annotation.value().isBlank()) {
return Optional.empty();
}
return Optional.of(new Permission(annotation.value(), annotation.defaultValue()));
}
public enum Default {
IF_OP,
TRUE,

@ -23,11 +23,18 @@ package net.william278.uniform;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.function.Function;
public interface Uniform {
void register(Command... commands);
void register(@NotNull Command... commands);
default void register(@NotNull Object... annotated) {
register(Arrays.stream(annotated)
.map(c -> c instanceof Command cmd ? cmd : new Command.AnnotatedCommand(c))
.toArray(Command[]::new));
}
@SuppressWarnings("unchecked")
<S, T extends BaseCommand<S>> void register(T... commands);

@ -0,0 +1,177 @@
/*
* This file is part of Uniform, licensed under the GNU General Public License v3.0.
*
* Copyright (c) Tofaa2
* Copyright (c) William278 <will27528@gmail.com>
* Copyright (c) contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.william278.uniform.annotations;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import net.william278.uniform.BaseCommand;
import net.william278.uniform.element.ArgumentElement;
import org.jetbrains.annotations.NotNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Argument {
String name();
Class<? extends ArgumentProvider<?>> parser();
String[] parserProperties() default {};
@NoArgsConstructor
abstract class ArgumentProvider<S> {
public abstract ArgumentElement<?, S> provide(@NotNull String name);
}
class StringArg extends ArgumentProvider<String> {
@Override
public ArgumentElement<?, String> provide(@NotNull String name) {
return BaseCommand.string(name);
}
}
class WordArg extends ArgumentProvider<String> {
@Override
public ArgumentElement<?, String> provide(@NotNull String name) {
return BaseCommand.word(name);
}
}
class BooleanArg extends ArgumentProvider<Boolean> {
@Override
public ArgumentElement<?, Boolean> provide(@NotNull String name) {
return BaseCommand.bool(name);
}
}
class GreedyStringArg extends ArgumentProvider<String> {
@Override
public ArgumentElement<?, String> provide(@NotNull String name) {
return BaseCommand.greedyString(name);
}
}
class IntegerArg extends ArgumentProvider<Integer> {
@Override
public ArgumentElement<?, Integer> provide(@NotNull String name) {
return BaseCommand.intNum(name);
}
}
@AllArgsConstructor
class BoundedIntegerArg extends ArgumentProvider<Integer> {
private final int min;
private final Integer max;
public BoundedIntegerArg(@NotNull String[] properties) {
if (properties.length == 0) {
throw new IllegalArgumentException("BoundedIntegerArg requires at least one property (min, max)");
}
this.min = Integer.parseInt(properties[0]);
if (properties.length == 1) {
this.max = null;
return;
}
this.max = Integer.parseInt(properties[1]);
}
@Override
public ArgumentElement<?, Integer> provide(@NotNull String name) {
if (max == null) {
return BaseCommand.intNum(name, min);
}
return BaseCommand.intNum(name, min, max);
}
}
class FloatArg extends ArgumentProvider<Float> {
@Override
public ArgumentElement<?, Float> provide(@NotNull String name) {
return BaseCommand.floatNum(name);
}
}
@AllArgsConstructor
class BoundedFloatArg extends ArgumentProvider<Float> {
private final float min;
private final Float max;
public BoundedFloatArg(@NotNull String[] properties) {
if (properties.length == 0) {
throw new IllegalArgumentException("BoundedFloatArg requires at least one property (min, max)");
}
this.min = Float.parseFloat(properties[0]);
if (properties.length == 1) {
this.max = null;
return;
}
this.max = Float.parseFloat(properties[1]);
}
@Override
public ArgumentElement<?, Float> provide(@NotNull String name) {
if (max == null) {
return BaseCommand.floatNum(name, min);
}
return BaseCommand.floatNum(name, min, max);
}
}
class DoubleArg extends ArgumentProvider<Double> {
@Override
public ArgumentElement<?, Double> provide(@NotNull String name) {
return BaseCommand.doubleNum(name);
}
}
@AllArgsConstructor
class BoundedDoubleArg extends ArgumentProvider<Double> {
private final double min;
private final Double max;
public BoundedDoubleArg(@NotNull String[] properties) {
if (properties.length == 0) {
throw new IllegalArgumentException("BoundedDoubleArg requires at least one property (min, max)");
}
this.min = Double.parseDouble(properties[0]);
if (properties.length == 1) {
this.max = null;
return;
}
this.max = Double.parseDouble(properties[1]);
}
@Override
public ArgumentElement<?, Double> provide(@NotNull String name) {
if (max == null) {
return BaseCommand.doubleNum(name, min);
}
return BaseCommand.doubleNum(name, min, max);
}
}
}

@ -0,0 +1,33 @@
/*
* This file is part of Uniform, licensed under the GNU General Public License v3.0.
*
* Copyright (c) Tofaa2
* Copyright (c) William278 <will27528@gmail.com>
* Copyright (c) contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.william278.uniform.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CommandDescription {
}

@ -0,0 +1,38 @@
/*
* This file is part of Uniform, licensed under the GNU General Public License v3.0.
*
* Copyright (c) Tofaa2
* Copyright (c) William278 <will27528@gmail.com>
* Copyright (c) contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.william278.uniform.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CommandNode {
String value();
String[] aliases() default {};
String description() default "";
PermissionNode permission() default @PermissionNode("");
}

@ -0,0 +1,38 @@
/*
* This file is part of Uniform, licensed under the GNU General Public License v3.0.
*
* Copyright (c) Tofaa2
* Copyright (c) William278 <will27528@gmail.com>
* Copyright (c) contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.william278.uniform.annotations;
import net.william278.uniform.Permission;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionNode {
String value();
Permission.Default defaultValue() default Permission.Default.FALSE;
}

@ -0,0 +1,33 @@
/*
* This file is part of Uniform, licensed under the GNU General Public License v3.0.
*
* Copyright (c) Tofaa2
* Copyright (c) William278 <will27528@gmail.com>
* Copyright (c) contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.william278.uniform.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Syntax {
PermissionNode permission() default @PermissionNode("");
}

@ -11,10 +11,10 @@ dependencies {
tasks {
runServer {
minecraftVersion("1.20.4")
minecraftVersion("1.21")
downloadPlugins {
url('https://download.luckperms.net/1549/bukkit/loader/LuckPerms-Bukkit-5.4.134.jar')
// url('https://download.luckperms.net/1549/bukkit/loader/LuckPerms-Bukkit-5.4.134.jar')
}
}
}

@ -0,0 +1,96 @@
/*
* This file is part of Uniform, licensed under the GNU General Public License v3.0.
*
* Copyright (c) Tofaa2
* Copyright (c) William278 <will27528@gmail.com>
* Copyright (c) contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.william278.uniform;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.william278.uniform.annotations.*;
import net.william278.uniform.element.ArgumentElement;
import org.jetbrains.annotations.NotNull;
import java.util.Locale;
@CommandNode(
value = "annotated",
permission = @PermissionNode(value = "uniform.annotated", defaultValue = Permission.Default.TRUE)
)
public class AnnotatedCommand {
@CommandDescription // Can use @CommandDescription instead of @CommandNode field for localization support here
public final String DESCRIPTION = "An example Uniform annotated command";
public AnnotatedCommand() {
}
@Syntax
void defaultExecutor(CommandUser user) {
user.getAudience().sendMessage(Component.text("No arguments passed!"));
}
@CommandNode("ping")
static class Ping {
public Ping() {
}
@Syntax
public void pong(CommandUser user) {
user.getAudience().sendMessage(Component.text("Pong!"));
}
@Syntax
public void pongMessage(
CommandUser user,
@Argument(name = "message", parser = Argument.StringArg.class) String message
) {
user.getAudience().sendMessage(Component.text("Pong! " + message, NamedTextColor.GREEN));
}
@Syntax
public void pongMessageWithColor(
CommandUser user,
@Argument(name = "message", parser = Argument.StringArg.class) String message,
@Argument(name = "color", parser = ColorArg.class) NamedTextColor color
) {
user.getAudience().sendMessage(Component.text("Colored Pong! " + message, color));
}
public static class ColorArg extends Argument.ArgumentProvider<NamedTextColor> {
@Override
public ArgumentElement<?, NamedTextColor> provide(@NotNull String name) {
return new ArgumentElement<>(name, (r) -> {
final NamedTextColor color = NamedTextColor.NAMES.value(r.readString().toLowerCase(Locale.ENGLISH));
if (color == null) {
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create();
}
return color;
}, (context, builder) -> {
NamedTextColor.NAMES.keys().forEach(color -> builder.suggest(color.toLowerCase(Locale.ENGLISH)));
return builder.buildFuture();
});
}
}
}
}

@ -32,9 +32,10 @@ import java.util.Locale;
import static net.william278.uniform.BaseCommand.greedyString;
public class ExampleCommand extends Command {
// Example using the API to extend "Command"
public class ExtendedCommand extends Command {
public ExampleCommand() {
public ExtendedCommand() {
super("example");
setDescription("An example command for Uniform");
setAliases(List.of("helloworld"));
@ -62,7 +63,7 @@ public class ExampleCommand extends Command {
}, exampleCustomArg()));
}
private static <S> ArgumentElement<S, ExampleCommand.IceCreamFlavor> exampleCustomArg() {
private static <S> ArgumentElement<S, ExtendedCommand.IceCreamFlavor> exampleCustomArg() {
return new ArgumentElement<>("flavor", reader -> {
final String flavor = reader.readString();
try {
@ -76,7 +77,6 @@ public class ExampleCommand extends Command {
);
return builder.buildFuture();
});
}
enum IceCreamFlavor {

@ -30,7 +30,7 @@ public class UniformExample extends JavaPlugin {
@Override
public void onEnable() {
PaperUniform uniform = PaperUniform.getInstance(this);
uniform.register(new ExampleCommand());
uniform.register(new ExtendedCommand(), new AnnotatedCommand());
}
}

@ -3,6 +3,6 @@ javaVersion=17
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
org.gradle.daemon=true
library_version=1.1.11
library_version=1.2
library_archive=uniform
library_description=Cross-platform wrapper for making Brigadier commands, based on BrigadierWrapper by Tofaa2, itself inspired by emortalmcs command system.

@ -31,7 +31,6 @@ import net.william278.uniform.Permission;
import net.william278.uniform.Uniform;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.api.Game;
import org.spongepowered.api.command.Command.Raw;
import org.spongepowered.api.command.CommandCause;
import org.spongepowered.api.command.CommandCompletion;

Loading…
Cancel
Save