This commit is contained in:
kento2 2026-02-17 16:18:06 +01:00
parent 4373584285
commit 5b5cb04f8b
46 changed files with 744 additions and 334 deletions

View file

@ -6,8 +6,11 @@ import de.kentoj.scrow.bukkit.services.command.CommandManager;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ScrowAPI {
@ -18,6 +21,8 @@ public class ScrowAPI {
private static EconomyService economyService;
@Getter
private static CommandManager commandManager;
@Getter
private static Scheduler minecraftScheduler;
@ApiStatus.Internal
public static void setDatabase(@Nullable MongoDatabase database) {
@ -33,5 +38,10 @@ public class ScrowAPI {
public static void setCommandManager(CommandManager commandManager) {
ScrowAPI.commandManager = commandManager;
}
@ApiStatus.Internal
public static void setMinecraftScheduler(Scheduler minecraftScheduler) {
ScrowAPI.minecraftScheduler = minecraftScheduler;
}
}

View file

@ -1,10 +1,12 @@
package de.kentoj.scrow.bukkit.services.command;
import de.kentoj.scrow.bukkit.services.command.arg.ArgumentType;
import de.kentoj.scrow.bukkit.services.command.type.ArgumentType;
import de.kentoj.scrow.bukkit.services.command.context.CommandContext;
import lombok.Value;
import java.util.function.Function;
// TODO convert to interface
@Value
public class CommandArgument<T> {
int index;

View file

@ -1,14 +0,0 @@
package de.kentoj.scrow.bukkit.services.command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
public interface CommandContext {
CommandSender getSender();
Player getSenderPlayer();
Entity getSenderEntity();
}

View file

@ -1,6 +0,0 @@
package de.kentoj.scrow.bukkit.services.command;
public interface CommandExecutionContext extends CommandContext {
<T> T getArg(String key);
}

View file

@ -1,9 +1,11 @@
package de.kentoj.scrow.bukkit.services.command;
import de.kentoj.scrow.bukkit.services.command.context.CommandContext;
public interface CommandExecutor {
/**
* @return true on success, 1 on failure
*/
void execute(CommandExecutionContext ctx);
void execute(CommandContext ctx);
}

View file

@ -1,5 +1,6 @@
package de.kentoj.scrow.bukkit.services.command;
import de.kentoj.scrow.bukkit.services.command.node.CommandNode;
import org.bukkit.plugin.Plugin;
public interface CommandManager {

View file

@ -1,40 +0,0 @@
package de.kentoj.scrow.bukkit.services.command;
import de.kentoj.scrow.bukkit.services.command.arg.ArgumentType;
import org.bukkit.permissions.Permission;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.function.Function;
// TODO javadoc
public interface CommandNode {
<T> void addArg(String name, ArgumentType<T> type, @Nullable Function<CommandContext, T> defaultValue);
default <T> void addArg(String name, ArgumentType<T> type) {
addArg(name, type, null);
}
void addSubCommand(CommandNode subCommand);
void setPermission(@Nullable Permission permission);
default void setPermission(String permission) {
setPermission(new Permission(permission));
}
@Nullable Permission getPermission();
@Nullable CommandArgument<?> getArg(int index);
List<CommandNode> getSubCommands();
String getName();
String[] getAliases();
String getDescription();
@Nullable CommandExecutor getExecutor();
}

View file

@ -0,0 +1,14 @@
package de.kentoj.scrow.bukkit.services.command;
public interface CommandReader {
boolean isEOF();
String readWord();
String readQuotedString();
int getCursor();
void setCursor(int cursor);
}

View file

@ -1,6 +0,0 @@
package de.kentoj.scrow.bukkit.services.command.arg;
public interface ArgumentType<S> {
S parseInput(String str);
}

View file

@ -1,28 +0,0 @@
package de.kentoj.scrow.bukkit.services.command.arg;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.util.UUID;
// FIXME module stuff
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class PlayerArgumentType {
public static final ArgumentType<Player> player = str -> {
if (str.length() == 36) {
var uuid = UUID.fromString(str);
return Bukkit.getPlayer(uuid);
} else {
return Bukkit.getPlayer(str);
}
};
public static final ArgumentType<OfflinePlayer> offlinePlayer = str -> {
var uuid = UUID.fromString(str);
return Bukkit.getOfflinePlayer(uuid);
};
}

View file

@ -0,0 +1,12 @@
package de.kentoj.scrow.bukkit.services.command.cmds2;
import de.kentoj.scrow.bukkit.services.command.context.CommandContext;
import de.kentoj.scrow.bukkit.services.command.type.ArgumentType;
import org.jetbrains.annotations.Nullable;
public interface CommandArgument<T> {
ArgumentType<T> getType();
@Nullable T getDefaultValue(CommandContext ctx);
}

View file

@ -0,0 +1,10 @@
package de.kentoj.scrow.bukkit.services.command.cmds2;
import de.kentoj.scrow.bukkit.services.command.context.CommandContext;
import java.util.Set;
public interface SuggestionProvider {
Set<String> suggest(CommandContext context);
}

View file

@ -0,0 +1,14 @@
package de.kentoj.scrow.bukkit.services.command.cmds2.node;
import de.kentoj.scrow.bukkit.services.command.context.CommandContext;
import de.kentoj.scrow.bukkit.services.command.type.ArgumentType;
import java.util.function.Function;
public non-sealed interface ArgumentCommandNode extends CommandNode {
<T> ArgumentType<T> getArgument(String name);
void addArgument(String name, ArgumentType<?> arg);
<T> void addArgument(String name, ArgumentType<T> arg, Function<CommandContext, T> getDefault);
}

View file

@ -0,0 +1,24 @@
package de.kentoj.scrow.bukkit.services.command.cmds2.node;
import de.kentoj.scrow.bukkit.services.command.CommandExecutor;
import de.kentoj.scrow.bukkit.services.command.cmds2.SuggestionProvider;
import org.jetbrains.annotations.Nullable;
public sealed interface CommandNode permits LiteralCommandNode, ArgumentCommandNode, RootCommandNode {
void setName(String name);
String getName();
void addSubNode(CommandNode node);
@Nullable CommandNode getSubNode(String rawArg);
boolean hasNextNode();
void setExecutor(@Nullable CommandExecutor executor);
@Nullable CommandExecutor getExecutor();
void setSuggestionProvider(SuggestionProvider suggestionProvider);
SuggestionProvider getSuggestionProvider();
void setDescription(@Nullable String description);
@Nullable String getDescription();
}

View file

@ -0,0 +1,10 @@
package de.kentoj.scrow.bukkit.services.command.cmds2.node;
import org.bukkit.permissions.Permission;
public non-sealed interface LiteralCommandNode extends CommandNode {
Permission getRequiredPermission();
void setRequiredPermission(Permission permission);
}

View file

@ -0,0 +1,12 @@
package de.kentoj.scrow.bukkit.services.command.cmds2.node;
import java.util.Set;
public non-sealed interface RootCommandNode extends CommandNode, LiteralCommandNode {
Set<String> getAliases();
void setAliases(Set<String> aliases);
void addAliases(String... aliases);
}

View file

@ -0,0 +1,27 @@
package de.kentoj.scrow.bukkit.services.command.context;
import de.kentoj.scrow.bukkit.services.command.exception.CommandSyntaxException;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
public interface CommandContext {
CommandSender getSender();
Player getSenderPlayer();
Entity getSenderEntity();
/**
* @return Object that is parsed from the raw argument
* @throws CommandSyntaxException if no value given and no default value supplied
*/
<T> T getArg(String key);
/**
* @return string argument used at invocation
*/
@Nullable String getRawArgument(String key);
}

View file

@ -0,0 +1,22 @@
package de.kentoj.scrow.bukkit.services.command.exception;
/**
* When a command has a syntax issue.
* {@code CommandSyntaxException::getMessage} should return a user-friendly message explaining the issue.
*/
public class CommandSyntaxException extends RuntimeException {
/**
* @param message user-friendly message
*/
public CommandSyntaxException(String message) {
super(message);
}
/**
* @param message user-friendly message
*/
public CommandSyntaxException(String message, Throwable throwable) {
super(message + ": " + throwable.getMessage());
}
}

View file

@ -0,0 +1,51 @@
package de.kentoj.scrow.bukkit.services.command.node;
import de.kentoj.scrow.bukkit.services.command.CommandExecutor;
import org.bukkit.permissions.Permission;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
// TODO javadoc
public interface CommandNode {
void setPermission(@Nullable Permission permission);
default void setPermission(String permission) {
setPermission(new Permission(permission));
}
void setDescription(String description);
void setAliases(Set<String> aliases);
void addAlias(String... aliases);
void setExecutor(@Nullable CommandExecutor executor);
@Nullable Permission getPermission();
String getName();
Set<String> getAliases();
String getDescription();
/**
* Function that should be called when invoking this command.
* The executor shall only be called when this node is the last CommandNode in the invocation.
* <pre>
* - RootCommand(CommandNode)
* - has executor
* - has sub-command(CommandNode)
* - has executor
* </pre>
* In this case, the sub-command is optional. If the sub-command is used, the sub-command's executor will be used.
* Else the RootCommand's executor will be used.
*/
@Nullable CommandExecutor getExecutor();
default boolean hasExecutor() {
return getExecutor() != null;
}
}

View file

@ -0,0 +1,10 @@
package de.kentoj.scrow.bukkit.services.command.node;
public interface CommandNodeFactory {
CommandNodeWithSubCommand createNodeWithSubCommands(String name);
CommandNodeWithArguments createNodeWithArguments(String name);
CommandNode createRootNode(String name);
}

View file

@ -0,0 +1,24 @@
package de.kentoj.scrow.bukkit.services.command.node;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.ApiStatus;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CommandNodeRepository {
private static CommandNodeFactory commandNodeFactory;
public static CommandNodeWithArguments withArguments(String name) {
return commandNodeFactory.createNodeWithArguments(name);
}
public static CommandNodeWithSubCommand withSubCommands(String name) {
return commandNodeFactory.createNodeWithSubCommands(name);
}
@ApiStatus.Internal
public static void setCommandNodeFactory(CommandNodeFactory commandNodeFactory) {
CommandNodeRepository.commandNodeFactory = commandNodeFactory;
}
}

View file

@ -0,0 +1,34 @@
package de.kentoj.scrow.bukkit.services.command.node;
import de.kentoj.scrow.bukkit.services.command.CommandArgument;
import de.kentoj.scrow.bukkit.services.command.context.CommandContext;
import de.kentoj.scrow.bukkit.services.command.type.ArgumentType;
import org.jetbrains.annotations.Nullable;
import java.util.function.Function;
/**
* A CommandNode which can have arguments
*/
public interface CommandNodeWithArguments extends CommandNode {
/**
* Adds an argument to the node.
* @param defaultValue Function returning a defaultValue if no value is given at invocation. \
* May be null if the argument should be required and not optional.
* @throws IllegalStateException if the previous argument is optional(defaultValue is set) and this is not.
*/
<T> void addArg(String name, ArgumentType<T> type, @Nullable Function<CommandContext, T> defaultValue);
/**
* Adds a required argument to the node
* @throws IllegalStateException if the previous argument is optional(defaultValue is set) and this is not.
*/
default <T> void addArg(String name, ArgumentType<T> type) {
addArg(name, type, null);
}
@Nullable CommandArgument<?> getArg(int index);
@Nullable CommandArgument<?> getArg(String name);
}

View file

@ -0,0 +1,20 @@
package de.kentoj.scrow.bukkit.services.command.node;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* A CommandNode which can have sub-commands
*/
public interface CommandNodeWithSubCommand extends CommandNode {
List<CommandNode> getSubCommands();
void addSubCommand(CommandNode subCommand);
/**
* @return SubCommand node where a name or alias matches label
*/
@Nullable CommandNode getSubCommand(String label);
}

View file

@ -0,0 +1,19 @@
package de.kentoj.scrow.bukkit.services.command.type;
import de.kentoj.scrow.bukkit.services.command.CommandReader;
import de.kentoj.scrow.bukkit.services.command.context.CommandContext;
import de.kentoj.scrow.bukkit.services.command.exception.CommandSyntaxException;
import java.util.Set;
public interface ArgumentType<T> {
/**
* @throws CommandSyntaxException if the given value may not be used
*/
void checkValue(CommandContext ctx, T value, String rawValue) throws CommandSyntaxException;
Set<String> getDefaultSuggestions(CommandContext ctx);
T parseInput(CommandReader reader);
}

View file

@ -0,0 +1,47 @@
package de.kentoj.scrow.bukkit.services.command.type;
import de.kentoj.scrow.bukkit.services.command.CommandReader;
import de.kentoj.scrow.bukkit.services.command.context.CommandContext;
import de.kentoj.scrow.bukkit.services.command.exception.CommandSyntaxException;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
// TODO add livingEntity
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class EntityArgumentType {
private static final ArgumentType<Player> player = new ArgumentType<>() {
@Override
public void checkValue(CommandContext ctx, Player value, String rawValue) throws CommandSyntaxException {
if (value == null)
throw new CommandSyntaxException("Player is not online -- " + rawValue);
}
@Override
public Set<String> getDefaultSuggestions(CommandContext ctx) {
return Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toSet());
}
@Override
public Player parseInput(CommandReader reader) {
final var word = reader.readWord();
if (word.length() == 36) {
var uuid = UUID.fromString(word);
return Bukkit.getPlayer(uuid);
} else {
return Bukkit.getPlayerExact(word);
}
}
};
public static ArgumentType<Player> player() {
return player;
}
}