Converted and reworked command builder, node, data
This commit is contained in:
parent
00852dd868
commit
8cf01f1137
6 changed files with 212 additions and 197 deletions
|
@ -4,11 +4,11 @@ import buttondevteam.core.MainPlugin
|
||||||
import buttondevteam.lib.TBMCCoreAPI
|
import buttondevteam.lib.TBMCCoreAPI
|
||||||
import buttondevteam.lib.chat.commands.*
|
import buttondevteam.lib.chat.commands.*
|
||||||
import buttondevteam.lib.chat.commands.CommandUtils.core
|
import buttondevteam.lib.chat.commands.CommandUtils.core
|
||||||
|
import buttondevteam.lib.chat.commands.CommandUtils.coreExecutable
|
||||||
import com.mojang.brigadier.CommandDispatcher
|
import com.mojang.brigadier.CommandDispatcher
|
||||||
import com.mojang.brigadier.arguments.*
|
import com.mojang.brigadier.arguments.*
|
||||||
import com.mojang.brigadier.builder.ArgumentBuilder
|
import com.mojang.brigadier.builder.ArgumentBuilder
|
||||||
import com.mojang.brigadier.context.CommandContext
|
import com.mojang.brigadier.context.CommandContext
|
||||||
import com.mojang.brigadier.context.ParsedCommandNode
|
|
||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException
|
import com.mojang.brigadier.exceptions.CommandSyntaxException
|
||||||
import com.mojang.brigadier.tree.CommandNode
|
import com.mojang.brigadier.tree.CommandNode
|
||||||
import com.mojang.brigadier.tree.LiteralCommandNode
|
import com.mojang.brigadier.tree.LiteralCommandNode
|
||||||
|
@ -157,12 +157,14 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
||||||
*/
|
*/
|
||||||
private fun getExecutableNode(method: Method, command: TC, ann: Subcommand, path: String, argHelpManager: CommandArgumentHelpManager<TC, TP>): LiteralCommandNode<TP> {
|
private fun getExecutableNode(method: Method, command: TC, ann: Subcommand, path: String, argHelpManager: CommandArgumentHelpManager<TC, TP>): LiteralCommandNode<TP> {
|
||||||
val (params, _) = getCommandParametersAndSender(method, argHelpManager) // Param order is important
|
val (params, _) = getCommandParametersAndSender(method, argHelpManager) // Param order is important
|
||||||
val paramMap = HashMap<String, CommandArgument?>()
|
val paramMap = HashMap<String, CommandArgument>()
|
||||||
for (param in params) {
|
for (param in params) {
|
||||||
paramMap[param.name] = param
|
paramMap[param.name] = param
|
||||||
}
|
}
|
||||||
val node = CoreCommandBuilder.literal<TP, TC>(path, params[0].type, paramMap, params, command)
|
val helpText = command.getHelpText(method, ann)
|
||||||
.helps(command.getHelpText(method, ann)).permits { sender: TP -> hasPermission(sender, command, method) }
|
val node = CoreCommandBuilder.literal(path, params[0].type, paramMap, params, command,
|
||||||
|
{ helpText }, // TODO: Help text getter support
|
||||||
|
{ sender: TP -> hasPermission(sender, command, method) })
|
||||||
.executes { context: CommandContext<TP> -> executeCommand(context) }
|
.executes { context: CommandContext<TP> -> executeCommand(context) }
|
||||||
var parent: ArgumentBuilder<TP, *> = node
|
var parent: ArgumentBuilder<TP, *> = node
|
||||||
for (param in params) { // Register parameters in the right order
|
for (param in params) { // Register parameters in the right order
|
||||||
|
@ -180,16 +182,23 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
||||||
* the main command node and the last part of the command path (that isn't registered yet)
|
* the main command node and the last part of the command path (that isn't registered yet)
|
||||||
*/
|
*/
|
||||||
private fun registerNodeFromPath(path: String): Triple<CommandNode<TP>, LiteralCommandNode<TP>?, String> {
|
private fun registerNodeFromPath(path: String): Triple<CommandNode<TP>, LiteralCommandNode<TP>?, String> {
|
||||||
val split = path.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
val split = path.split(" ")
|
||||||
var parent: CommandNode<TP> = dispatcher.root
|
var parent: CommandNode<TP> = dispatcher.root
|
||||||
var mainCommand: LiteralCommandNode<TP>? = null
|
var mainCommand: LiteralCommandNode<TP>? = null
|
||||||
for (i in 0 until split.size - 1) {
|
split.forEachIndexed { i, part ->
|
||||||
val part = split[i]
|
|
||||||
val child = parent.getChild(part)
|
val child = parent.getChild(part)
|
||||||
if (child == null) parent.addChild(CoreCommandBuilder.literalNoOp<TP, TC>(part).executes { context: CommandContext<TP> -> executeHelpText(context) }.build().also { parent = it }) else parent = child
|
if (child == null) parent.addChild(CoreCommandBuilder.literalNoOp<TP, TC>(part, getSubcommandList())
|
||||||
|
.executes(::executeHelpText).build().also { parent = it })
|
||||||
|
else parent = child
|
||||||
if (i == 0) mainCommand = parent as LiteralCommandNode<TP> // Has to be a literal, if not, well, error
|
if (i == 0) mainCommand = parent as LiteralCommandNode<TP> // Has to be a literal, if not, well, error
|
||||||
}
|
}
|
||||||
return Triple(parent, mainCommand, split[split.size - 1])
|
return Triple(parent, mainCommand, split.last())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSubcommandList(): (Any) -> Array<String> {
|
||||||
|
return {
|
||||||
|
arrayOf("TODO") // TODO: Subcommand list
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -200,17 +209,25 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
||||||
* @return Parameter data objects and the sender type
|
* @return Parameter data objects and the sender type
|
||||||
* @throws RuntimeException If there is no sender parameter declared in the method
|
* @throws RuntimeException If there is no sender parameter declared in the method
|
||||||
*/
|
*/
|
||||||
private fun getCommandParametersAndSender(method: Method, argHelpManager: CommandArgumentHelpManager<TC, TP>): Pair<List<CommandArgument>, Class<*>> {
|
private fun getCommandParametersAndSender(
|
||||||
|
method: Method,
|
||||||
|
argHelpManager: CommandArgumentHelpManager<TC, TP>
|
||||||
|
): Pair<List<CommandArgument>, Class<*>> {
|
||||||
val parameters = method.parameters
|
val parameters = method.parameters
|
||||||
if (parameters.isEmpty()) throw RuntimeException("No sender parameter for method '$method'")
|
if (parameters.isEmpty()) throw RuntimeException("No sender parameter for method '$method'")
|
||||||
val usage = argHelpManager.getParameterHelpForMethod(method)
|
val usage = argHelpManager.getParameterHelpForMethod(method)
|
||||||
val paramNames = usage?.split(" ")
|
val paramNames = usage?.split(" ")
|
||||||
return Pair(parameters.zip(paramNames ?: (1 until parameters.size).map { i -> "param$i" })
|
return Pair(
|
||||||
.map { (param, name) ->
|
parameters.zip(paramNames ?: (1 until parameters.size).map { i -> "param$i" })
|
||||||
val numAnn = param.getAnnotation(NumberArg::class.java)
|
.map { (param, name) ->
|
||||||
CommandArgument(name, param.type,
|
val numAnn = param.getAnnotation(NumberArg::class.java)
|
||||||
param.isVarArgs || param.isAnnotationPresent(TextArg::class.java),
|
CommandArgument(
|
||||||
if (numAnn == null) Pair(Double.MIN_VALUE, Double.MAX_VALUE) else Pair(numAnn.lowerLimit, numAnn.upperLimit),
|
name, param.type,
|
||||||
|
param.isVarArgs || param.isAnnotationPresent(TextArg::class.java),
|
||||||
|
if (numAnn == null) Pair(Double.MIN_VALUE, Double.MAX_VALUE) else Pair(
|
||||||
|
numAnn.lowerLimit,
|
||||||
|
numAnn.upperLimit
|
||||||
|
),
|
||||||
param.isAnnotationPresent(OptionalArg::class.java),
|
param.isAnnotationPresent(OptionalArg::class.java),
|
||||||
name)
|
name)
|
||||||
}, parameters[0].type)
|
}, parameters[0].type)
|
||||||
|
@ -253,7 +270,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
||||||
private fun executeHelpText(context: CommandContext<TP>): Int {
|
private fun executeHelpText(context: CommandContext<TP>): Int {
|
||||||
println("""
|
println("""
|
||||||
Nodes:
|
Nodes:
|
||||||
${context.nodes.stream().map { node: ParsedCommandNode<TP> -> node.node.name + "@" + node.range }.collect(Collectors.joining("\n"))}
|
${context.nodes.stream().map { node -> node.node.name + "@" + node.range }.collect(Collectors.joining("\n"))}
|
||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
@ -333,8 +350,10 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
||||||
*
|
*
|
||||||
* @return A set of command node objects containing the commands
|
* @return A set of command node objects containing the commands
|
||||||
*/
|
*/
|
||||||
val commandNodes: Set<CoreCommandNode<TP, TC>>
|
val commandNodes: Set<CoreCommandNode<TP, TC, NoOpSubcommandData>>
|
||||||
get() = dispatcher.root.children.stream().map { node: CommandNode<TP> -> node.core<TP, TC>() }.collect(Collectors.toUnmodifiableSet())
|
get() = dispatcher.root.children.stream()
|
||||||
|
.map { node: CommandNode<TP> -> node.core<TP, TC, NoOpSubcommandData>() }
|
||||||
|
.collect(Collectors.toUnmodifiableSet())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a node that belongs to the given command.
|
* Get a node that belongs to the given command.
|
||||||
|
@ -342,7 +361,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
||||||
* @param command The exact name of the command
|
* @param command The exact name of the command
|
||||||
* @return A command node
|
* @return A command node
|
||||||
*/
|
*/
|
||||||
fun getCommandNode(command: String?): CoreCommandNode<TP, TC> {
|
fun getCommandNode(command: String): CoreCommandNode<TP, TC, NoOpSubcommandData> { // TODO: What should this return? No-op? Executable? What's the use case?
|
||||||
return dispatcher.root.getChild(command).core()
|
return dispatcher.root.getChild(command).core()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,7 +371,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
||||||
* @param command The command class (object) to unregister
|
* @param command The command class (object) to unregister
|
||||||
*/
|
*/
|
||||||
fun unregisterCommand(command: ICommand2<TP>) {
|
fun unregisterCommand(command: ICommand2<TP>) {
|
||||||
dispatcher.root.children.removeIf { node: CommandNode<TP> -> node.core<TP, TC>().data.command === command }
|
dispatcher.root.children.removeIf { node: CommandNode<TP> -> node.coreExecutable<TP, TC>()?.data?.command === command }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -360,14 +379,18 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
||||||
*
|
*
|
||||||
* @param condition The condition for removing a given command
|
* @param condition The condition for removing a given command
|
||||||
*/
|
*/
|
||||||
fun unregisterCommandIf(condition: Predicate<CoreCommandNode<TP, TC>>, nested: Boolean) {
|
fun unregisterCommandIf(condition: Predicate<CoreCommandNode<TP, TC, SubcommandData<TC, TP>>>, nested: Boolean) {
|
||||||
dispatcher.root.children.removeIf { node: CommandNode<TP> -> condition.test(node.core()) }
|
dispatcher.root.children.removeIf { node -> node.coreExecutable<TP, TC>()?.let { condition.test(it) } ?: false }
|
||||||
if (nested) for (child in dispatcher.root.children) unregisterCommandIf(condition, child.core())
|
if (nested) for (child in dispatcher.root.children) unregisterCommandIf(condition, child.core())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unregisterCommandIf(condition: Predicate<CoreCommandNode<TP, TC>>, root: CoreCommandNode<TP, TC>) {
|
private fun unregisterCommandIf(
|
||||||
|
condition: Predicate<CoreCommandNode<TP, TC, SubcommandData<TC, TP>>>,
|
||||||
|
root: CoreCommandNode<TP, TC, NoOpSubcommandData>
|
||||||
|
) {
|
||||||
|
// TODO: Remvoe no-op nodes without children
|
||||||
// Can't use getCoreChildren() here because the collection needs to be modifiable
|
// Can't use getCoreChildren() here because the collection needs to be modifiable
|
||||||
root.children.removeIf { node: CommandNode<TP> -> condition.test(node.core()) }
|
root.children.removeIf { node -> node.coreExecutable<TP, TC>()?.let { condition.test(it) } ?: false }
|
||||||
for (child in root.coreChildren) unregisterCommandIf(condition, child)
|
for (child in root.children) unregisterCommandIf(condition, child.core())
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,74 +1,72 @@
|
||||||
package buttondevteam.lib.chat;
|
package buttondevteam.lib.chat
|
||||||
|
|
||||||
import buttondevteam.lib.chat.commands.CommandArgument;
|
import buttondevteam.lib.chat.commands.CommandArgument
|
||||||
import buttondevteam.lib.chat.commands.SubcommandData;
|
import buttondevteam.lib.chat.commands.NoOpSubcommandData
|
||||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
import buttondevteam.lib.chat.commands.SubcommandData
|
||||||
import com.mojang.brigadier.tree.CommandNode;
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder
|
||||||
|
|
||||||
import java.util.Map;
|
class CoreCommandBuilder<S : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcommandData> private constructor(
|
||||||
import java.util.function.Function;
|
literal: String,
|
||||||
|
val data: TSD
|
||||||
|
) : LiteralArgumentBuilder<S>(literal) {
|
||||||
|
|
||||||
public class CoreCommandBuilder<S extends Command2Sender, TC extends ICommand2<?>> extends LiteralArgumentBuilder<S> {
|
override fun getThis(): CoreCommandBuilder<S, TC, TSD> {
|
||||||
private final SubcommandData.SubcommandDataBuilder<TC, S> dataBuilder;
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
protected CoreCommandBuilder(String literal, Class<?> senderType, Map<String, CommandArgument> arguments, CommandArgument[] argumentsInOrder, TC command) {
|
override fun build(): CoreCommandNode<S, TC, TSD> {
|
||||||
super(literal);
|
val result = CoreCommandNode<_, TC, _>(
|
||||||
dataBuilder = SubcommandData.<TC, S>builder().senderType(senderType).arguments(arguments)
|
literal,
|
||||||
.argumentsInOrder(argumentsInOrder).command(command);
|
command,
|
||||||
}
|
requirement,
|
||||||
|
this.redirect,
|
||||||
|
this.redirectModifier,
|
||||||
|
this.isFork,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
for (node in arguments) {
|
||||||
|
result.addChild(node)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
companion object {
|
||||||
protected CoreCommandBuilder<S, TC> getThis() {
|
/**
|
||||||
return this;
|
* Start building an executable command node.
|
||||||
}
|
*
|
||||||
|
* @param name The subcommand name as written by the user
|
||||||
|
* @param senderType The expected command sender type based on the subcommand method
|
||||||
|
* @param arguments A map of the command arguments with their names as keys
|
||||||
|
* @param argumentsInOrder A list of the command arguments in the order they are expected
|
||||||
|
* @param command The command object that has this subcommand
|
||||||
|
* @param helpTextGetter Custom help text that can depend on the context. The function receives the sender as the command itself receives it.
|
||||||
|
*/
|
||||||
|
fun <S : Command2Sender, TC : ICommand2<*>> literal(
|
||||||
|
name: String,
|
||||||
|
senderType: Class<*>,
|
||||||
|
arguments: Map<String, CommandArgument>,
|
||||||
|
argumentsInOrder: List<CommandArgument>,
|
||||||
|
command: TC,
|
||||||
|
helpTextGetter: (Any) -> Array<String>,
|
||||||
|
hasPermission: (S) -> Boolean
|
||||||
|
): CoreCommandBuilder<S, TC, SubcommandData<TC, S>> {
|
||||||
|
return CoreCommandBuilder(
|
||||||
|
name,
|
||||||
|
SubcommandData(senderType, arguments, argumentsInOrder, command, helpTextGetter, hasPermission)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
public static <S extends Command2Sender, TC extends ICommand2<?>> CoreCommandBuilder<S, TC> literal(String name, Class<?> senderType, Map<String, CommandArgument> arguments, CommandArgument[] argumentsInOrder, TC command) {
|
/**
|
||||||
return new CoreCommandBuilder<>(name, senderType, arguments, argumentsInOrder, command);
|
* Start building a no-op command node.
|
||||||
}
|
*
|
||||||
|
* @param name The subcommand name as written by the user
|
||||||
public static <S extends Command2Sender, TC extends ICommand2<?>> CoreCommandBuilder<S, TC> literalNoOp(String name) {
|
* @param helpTextGetter Custom help text that can depend on the context. The function receives the sender as the command itself receives it.
|
||||||
return literal(name, Command2Sender.class, Map.of(), new CommandArgument[0], null);
|
*/
|
||||||
}
|
fun <S : Command2Sender, TC : ICommand2<*>> literalNoOp(
|
||||||
|
name: String,
|
||||||
/**
|
helpTextGetter: (Any) -> Array<String>,
|
||||||
* Static help text added through annotations. May be overwritten with the getter.
|
): CoreCommandBuilder<S, TC, NoOpSubcommandData> {
|
||||||
*
|
return CoreCommandBuilder(name, NoOpSubcommandData(helpTextGetter))
|
||||||
* @param helpText Help text shown to the user
|
}
|
||||||
* @return This instance
|
}
|
||||||
*/
|
}
|
||||||
public CoreCommandBuilder<S, TC> helps(String[] helpText) {
|
|
||||||
dataBuilder.staticHelpText(helpText);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom help text that depends on the context. Overwrites the static one.
|
|
||||||
* The function receives the sender but its type is not guaranteed to match the one at the subcommand.
|
|
||||||
* It will either match or be a Command2Sender, however.
|
|
||||||
*
|
|
||||||
* @param getter The getter function receiving the sender and returning the help text
|
|
||||||
* @return This instance
|
|
||||||
*/
|
|
||||||
public CoreCommandBuilder<S, TC> helps(Function<Object, String[]> getter) {
|
|
||||||
dataBuilder.helpTextGetter(getter);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CoreCommandBuilder<S, TC> permits(Function<S, Boolean> permChecker) {
|
|
||||||
dataBuilder.hasPermission(permChecker);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CoreCommandNode<S, TC> build() {
|
|
||||||
var result = new CoreCommandNode<S, TC>(this.getLiteral(), this.getCommand(), this.getRequirement(),
|
|
||||||
this.getRedirect(), this.getRedirectModifier(), this.isFork(),
|
|
||||||
dataBuilder.build());
|
|
||||||
|
|
||||||
for (CommandNode<S> node : this.getArguments()) {
|
|
||||||
result.addChild(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +1,18 @@
|
||||||
package buttondevteam.lib.chat;
|
package buttondevteam.lib.chat
|
||||||
|
|
||||||
import buttondevteam.lib.chat.commands.SubcommandData;
|
import buttondevteam.lib.chat.commands.NoOpSubcommandData
|
||||||
import com.mojang.brigadier.Command;
|
import com.mojang.brigadier.Command
|
||||||
import com.mojang.brigadier.RedirectModifier;
|
import com.mojang.brigadier.RedirectModifier
|
||||||
import com.mojang.brigadier.tree.CommandNode;
|
import com.mojang.brigadier.tree.CommandNode
|
||||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
import com.mojang.brigadier.tree.LiteralCommandNode
|
||||||
import lombok.Getter;
|
import java.util.function.Predicate
|
||||||
|
|
||||||
import java.util.Collection;
|
class CoreCommandNode<T : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcommandData>(
|
||||||
import java.util.function.Predicate;
|
literal: String,
|
||||||
import java.util.stream.Collectors;
|
command: Command<T>,
|
||||||
|
requirement: Predicate<T>,
|
||||||
public class CoreCommandNode<T extends Command2Sender, TC extends ICommand2<?>> extends LiteralCommandNode<T> {
|
redirect: CommandNode<T>,
|
||||||
@Getter
|
modifier: RedirectModifier<T>,
|
||||||
private final SubcommandData<TC, T> data;
|
forks: Boolean,
|
||||||
|
val data: TSD
|
||||||
public CoreCommandNode(String literal, Command<T> command, Predicate<T> requirement, CommandNode<T> redirect, RedirectModifier<T> modifier, boolean forks, SubcommandData<TC, T> data) {
|
) : LiteralCommandNode<T>(literal, command, requirement, redirect, modifier, forks)
|
||||||
super(literal, command, requirement, redirect, modifier, forks);
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see #getChildren()
|
|
||||||
*/
|
|
||||||
public Collection<CoreCommandNode<T, TC>> getCoreChildren() {
|
|
||||||
return super.getChildren().stream().map(node -> (CoreCommandNode<T, TC>) node).collect(Collectors.toUnmodifiableSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see #getChild(String)
|
|
||||||
*/
|
|
||||||
public CoreCommandNode<T, TC> getCoreChild(String name) {
|
|
||||||
return (CoreCommandNode<T, TC>) super.getChild(name);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,11 +15,23 @@ object CommandUtils {
|
||||||
* @return The command path starting with the replacement char.
|
* @return The command path starting with the replacement char.
|
||||||
*/
|
*/
|
||||||
fun getCommandPath(methodName: String, replaceChar: Char): String {
|
fun getCommandPath(methodName: String, replaceChar: Char): String {
|
||||||
return if (methodName == "def") "" else replaceChar.toString() + methodName.replace('_', replaceChar).lowercase(Locale.getDefault())
|
return if (methodName == "def") "" else replaceChar.toString() + methodName.replace('_', replaceChar)
|
||||||
|
.lowercase(Locale.getDefault())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Casts the node to whatever you say. Use responsibly.
|
||||||
|
*/
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <TP : Command2Sender, TC : ICommand2<*>> CommandNode<TP>.core(): CoreCommandNode<TP, TC> {
|
fun <TP : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcommandData> CommandNode<TP>.core(): CoreCommandNode<TP, TC, TSD> {
|
||||||
return this as CoreCommandNode<TP, TC>
|
return this as CoreCommandNode<TP, TC, TSD>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the node as an executable core command node or returns null if it's a no-op node.
|
||||||
|
*/
|
||||||
|
fun <TP : Command2Sender, TC : ICommand2<*>> CommandNode<TP>.coreExecutable(): CoreCommandNode<TP, TC, SubcommandData<TC, TP>>? {
|
||||||
|
val ret = core<TP, TC, NoOpSubcommandData>()
|
||||||
|
return if (ret.data is SubcommandData<*, *>) ret.core() else null
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package buttondevteam.lib.chat.commands
|
||||||
|
|
||||||
|
open class NoOpSubcommandData(
|
||||||
|
/**
|
||||||
|
* Custom help text that depends on the context. Overwrites the static one.
|
||||||
|
* The function receives the sender as the command itself receives it.
|
||||||
|
*/
|
||||||
|
private val helpTextGetter: (Any) -> Array<String>
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Get help text for this subcommand. Returns an empty array if it's not specified.
|
||||||
|
*
|
||||||
|
* @param sender The sender running the command
|
||||||
|
* @return Help text shown to the user
|
||||||
|
*/
|
||||||
|
fun getHelpText(sender: Any): Array<String> {
|
||||||
|
return helpTextGetter(sender)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,75 +1,56 @@
|
||||||
package buttondevteam.lib.chat.commands;
|
package buttondevteam.lib.chat.commands
|
||||||
|
|
||||||
import buttondevteam.lib.chat.Command2Sender;
|
import buttondevteam.lib.chat.Command2Sender
|
||||||
import buttondevteam.lib.chat.ICommand2;
|
import buttondevteam.lib.chat.ICommand2
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores information about the subcommand that can be used to construct the Brigadier setup and to get information while executing the command.
|
* Stores information about the subcommand that can be used to construct the Brigadier setup and to get information while executing the command.
|
||||||
*
|
*
|
||||||
* @param <TC> Command class type
|
* @param TC Command class type
|
||||||
|
* @param TP Command sender type
|
||||||
*/
|
*/
|
||||||
@Builder
|
class SubcommandData<TC : ICommand2<*>, TP : Command2Sender>(
|
||||||
@RequiredArgsConstructor
|
/**
|
||||||
public final class SubcommandData<TC extends ICommand2<?>, TP extends Command2Sender> {
|
* The type of the sender running the command.
|
||||||
/**
|
* The actual sender type may not be represented by Command2Sender (TP).
|
||||||
* The type of the sender running the command.
|
* In that case it has to match the expected type.
|
||||||
* The actual sender type may not be represented by Command2Sender (TP).
|
*/
|
||||||
* In that case it has to match the expected type.
|
val senderType: Class<*>,
|
||||||
*/
|
|
||||||
public final Class<?> senderType;
|
|
||||||
/**
|
|
||||||
* Command arguments collected from the subcommand method.
|
|
||||||
* Used to construct the arguments for Brigadier and to hold extra information.
|
|
||||||
*/
|
|
||||||
public final Map<String, CommandArgument> arguments;
|
|
||||||
/**
|
|
||||||
* Command arguments in the order they appear in code and in game.
|
|
||||||
*/
|
|
||||||
public final CommandArgument[] argumentsInOrder;
|
|
||||||
/**
|
|
||||||
* The original command class that this data belongs to. If null, that meaans only the help text can be used.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public final TC command;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static help text added through annotations. May be overwritten with the getter.
|
* Command arguments collected from the subcommand method.
|
||||||
*/
|
* Used to construct the arguments for Brigadier and to hold extra information.
|
||||||
private final String[] staticHelpText;
|
*/
|
||||||
/**
|
val arguments: Map<String, CommandArgument>,
|
||||||
* Custom help text that depends on the context. Overwrites the static one.
|
|
||||||
* The function receives the sender but its type is not guaranteed to match the one at the subcommand.
|
|
||||||
* It will either match or be a Command2Sender, however.
|
|
||||||
*/
|
|
||||||
private final Function<Object, String[]> helpTextGetter;
|
|
||||||
/**
|
|
||||||
* A function that determines whether the user has permission to run this subcommand.
|
|
||||||
*/
|
|
||||||
private final Function<TP, Boolean> hasPermission;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get help text for this subcommand.
|
* Command arguments in the order they appear in code and in game.
|
||||||
*
|
*/
|
||||||
* @param sender The sender running the command
|
val argumentsInOrder: List<CommandArgument>,
|
||||||
* @return Help text shown to the user
|
|
||||||
*/
|
|
||||||
public String[] getHelpText(Object sender) {
|
|
||||||
return staticHelpText == null ? helpTextGetter.apply(sender) : staticHelpText;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the user has permission to execute this subcommand.
|
* The original command class that this data belongs to.
|
||||||
*
|
*/
|
||||||
* @param sender The sender running the command
|
val command: TC,
|
||||||
* @return Whether the user has permission
|
/**
|
||||||
*/
|
* Custom help text that depends on the context. Overwrites the static one.
|
||||||
public boolean hasPermission(TP sender) {
|
* The function receives the sender as the command itself receives it.
|
||||||
return hasPermission.apply(sender);
|
*/
|
||||||
}
|
helpTextGetter: (Any) -> Array<String>,
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* A function that determines whether the user has permission to run this subcommand.
|
||||||
|
*/
|
||||||
|
private val permissionCheck: (TP) -> Boolean
|
||||||
|
) : NoOpSubcommandData(helpTextGetter) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the user has permission to execute this subcommand.
|
||||||
|
*
|
||||||
|
* @param sender The sender running the command
|
||||||
|
* @return Whether the user has permission
|
||||||
|
*/
|
||||||
|
fun hasPermission(sender: TP): Boolean {
|
||||||
|
return permissionCheck(sender)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue