Fixed getSubcommands() and other command fixes

- Command2MC tab completion still awaits - but then that might be all that's left before testing
This commit is contained in:
Norbi Peti 2023-04-18 01:52:40 +02:00
parent 30a2fe0b68
commit 8616ecbefc
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
12 changed files with 189 additions and 167 deletions

View file

@ -14,7 +14,7 @@ import java.util.Optional;
@CommandClass
public class ChromaCommand extends ICommand2MC {
public ChromaCommand() {
manager.addParamConverter(ButtonPlugin.class, name ->
getManager().addParamConverter(ButtonPlugin.class, name ->
(ButtonPlugin) Optional.ofNullable(Bukkit.getPluginManager().getPlugin(name))
.filter(plugin -> plugin instanceof ButtonPlugin).orElse(null),
"No Chroma plugin found by that name.", () -> Arrays.stream(Bukkit.getPluginManager().getPlugins())

View file

@ -57,7 +57,7 @@ class MainPlugin : ButtonPlugin() {
public override fun pluginEnable() {
instance = this
val pdf = description
if (!setupPermissions()) throw NullPointerException("No permission plugin found!")
setupPermissions()
if (!setupEconomy()) //Though Essentials always provides economy, but we don't require Essentials
logger.warning("No economy plugin found! Components using economy will not be registered.")
saveConfig()
@ -134,9 +134,8 @@ class MainPlugin : ButtonPlugin() {
logger.info("Player data saved.")
}
private fun setupPermissions(): Boolean {
permission = setupProvider(Permission::class.java)
return permission != null
private fun setupPermissions() {
permission = setupProvider(Permission::class.java) ?: throw NullPointerException("No permission plugin found!")
}
private fun setupEconomy(): Boolean {
@ -159,8 +158,7 @@ class MainPlugin : ButtonPlugin() {
companion object {
lateinit var instance: MainPlugin
@JvmField
var permission: Permission? = null
lateinit var permission: Permission
@JvmField
var ess: Essentials? = null

View file

@ -42,7 +42,4 @@ class PrimeRestartCommand : ICommand2MC() {
Bukkit.spigot().restart()
}
}
companion object {
}
}

View file

@ -77,7 +77,7 @@ abstract class Component<TP : JavaPlugin> {
* @param command Custom coded command class
*/
fun registerCommand(command: ICommand2MC) {
if (plugin is ButtonPlugin) command.registerToPlugin(plugin as ButtonPlugin)
if (plugin is ButtonPlugin) command.registerToPlugin(plugin as ButtonPlugin) // TODO: Require ButtonPlugin
command.registerToComponent(this)
ButtonPlugin.command2MC.registerCommand(command)
}
@ -102,8 +102,7 @@ abstract class Component<TP : JavaPlugin> {
*/
fun getConfigMap(key: String, defaultProvider: Map<String, Consumer<IHaveConfig>>): Map<String, IHaveConfig> {
val c: ConfigurationSection = config.config
var cs = c.getConfigurationSection(key)
if (cs == null) cs = c.createSection(key)
val cs = c.getConfigurationSection(key) ?: c.createSection(key)
val res = cs.getValues(false).entries.stream()
.filter { (_, value) -> value is ConfigurationSection }
.collect(Collectors.toMap(

View file

@ -133,13 +133,22 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
* @param command The command to register
* @return The Brigadier command node if you need it for something (like tab completion)
*/
protected fun registerCommandSuper(command: TC): LiteralCommandNode<TP> {
var mainCommandNode: LiteralCommandNode<TP>? = null
protected fun registerCommandSuper(command: TC): CoreCommandNode<TP, *> {
var mainCommandNode: CoreCommandNode<TP, *>? = null
for (meth in command.javaClass.methods) {
val ann = meth.getAnnotation(Subcommand::class.java) ?: continue
val fullPath = command.commandPath + CommandUtils.getCommandPath(meth.name, ' ')
val (lastNode, mainNode, remainingPath) = registerNodeFromPath(fullPath)
lastNode.addChild(getExecutableNode(meth, command, ann, remainingPath, CommandArgumentHelpManager(command), fullPath))
lastNode.addChild(
getExecutableNode(
meth,
command,
ann,
remainingPath,
CommandArgumentHelpManager(command),
fullPath
)
)
if (mainCommandNode == null) mainCommandNode = mainNode
else if (mainNode!!.name != mainCommandNode.name) {
MainPlugin.instance.logger.warning("Multiple commands are defined in the same class! This is not supported. Class: " + command.javaClass.simpleName)
@ -193,16 +202,17 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
* @return The last no-op node that can be used to register the executable node,
* 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>, CoreCommandNode<TP, *>?, String> {
val split = path.split(" ")
var parent: CommandNode<TP> = dispatcher.root
var mainCommand: LiteralCommandNode<TP>? = null
var mainCommand: CoreCommandNode<TP, *>? = null
split.forEachIndexed { i, part ->
val child = parent.getChild(part)
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 CoreCommandNode<TP, *> // Has to be our own literal node, if not, well, error
}
return Triple(parent, mainCommand, split.last())
}
@ -362,9 +372,9 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
*
* @return A set of command node objects containing the commands
*/
val commandNodes: Set<CoreCommandNode<TP, TC, NoOpSubcommandData>>
val commandNodes: Set<CoreCommandNode<TP, NoOpSubcommandData>>
get() = dispatcher.root.children.stream()
.map { node: CommandNode<TP> -> node.core<TP, TC, NoOpSubcommandData>() }
.map { node: CommandNode<TP> -> node.core<TP, NoOpSubcommandData>() }
.collect(Collectors.toUnmodifiableSet())
/**
@ -373,7 +383,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
* @param command The exact name of the command
* @return A command node
*/
fun getCommandNode(command: String): CoreCommandNode<TP, TC, NoOpSubcommandData> { // TODO: What should this return? No-op? Executable? What's the use case?
fun getCommandNode(command: String): CoreCommandNode<TP, NoOpSubcommandData> { // TODO: What should this return? No-op? Executable? What's the use case?
return dispatcher.root.getChild(command).core()
}
@ -391,14 +401,14 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
*
* @param condition The condition for removing a given command
*/
fun unregisterCommandIf(condition: Predicate<CoreCommandNode<TP, TC, SubcommandData<TC, TP>>>, nested: Boolean) {
fun unregisterCommandIf(condition: Predicate<CoreCommandNode<TP, SubcommandData<TC, TP>>>, nested: Boolean) {
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())
}
private fun unregisterCommandIf(
condition: Predicate<CoreCommandNode<TP, TC, SubcommandData<TC, TP>>>,
root: CoreCommandNode<TP, TC, NoOpSubcommandData>
condition: Predicate<CoreCommandNode<TP, SubcommandData<TC, TP>>>,
root: CoreCommandNode<TP, NoOpSubcommandData>
) {
// TODO: Remvoe no-op nodes without children
// Can't use getCoreChildren() here because the collection needs to be modifiable
@ -408,8 +418,24 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
/**
* Get all subcommands of the specified command. Only returns executable nodes.
*
* @param mainCommand The command to get the subcommands of
* @param deep Whether to get all subcommands recursively or only the direct children
*/
fun getSubcommands(mainCommand: LiteralCommandNode<TP>): List<CoreCommandNode<TP, TC, SubcommandData<TC, TP>>> {
return dispatcher.root.children.mapNotNull { it.coreExecutable<TP, TC>() } // TODO: Needs more depth
fun getSubcommands(
mainCommand: LiteralCommandNode<TP>,
deep: Boolean = true
): List<CoreCommandNode<TP, SubcommandData<TC, TP>>> {
return getSubcommands(mainCommand, deep, mainCommand.core())
}
private fun getSubcommands(
mainCommand: LiteralCommandNode<TP>,
deep: Boolean = true,
root: CoreCommandNode<TP, NoOpSubcommandData>
): List<CoreCommandNode<TP, SubcommandData<TC, TP>>> {
return root.children.mapNotNull { it.coreExecutable<TP, TC>() } +
if (deep) root.children.flatMap { getSubcommands(mainCommand, deep, it.core()) } else emptyList()
}
}

View file

@ -5,6 +5,7 @@ import buttondevteam.lib.TBMCCoreAPI
import buttondevteam.lib.architecture.ButtonPlugin
import buttondevteam.lib.architecture.Component
import buttondevteam.lib.chat.commands.CommandUtils
import buttondevteam.lib.chat.commands.CommandUtils.coreExecutable
import buttondevteam.lib.chat.commands.MCCommandSettings
import buttondevteam.lib.chat.commands.SubcommandData
import buttondevteam.lib.player.ChromaGamerBase
@ -29,7 +30,6 @@ import org.bukkit.entity.Player
import org.bukkit.event.Listener
import org.bukkit.permissions.Permission
import org.bukkit.permissions.PermissionDefault
import java.lang.reflect.Method
import java.lang.reflect.Parameter
import java.util.*
import java.util.function.BiConsumer
@ -43,42 +43,24 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
* @param command The command to register
*/
override fun registerCommand(command: ICommand2MC) {
/*String mainpath;
var plugin = command.getPlugin();
{
String cpath = command.getCommandPath();
int i = cpath.indexOf(' ');
mainpath = cpath.substring(0, i == -1 ? cpath.length() : i);
}*/
val commandNode = super.registerCommandSuper(command)
val bcmd = registerOfficially(command, commandNode)
if (bcmd != null) // TODO: Support aliases
super.registerCommandSuper(command)
val perm = "chroma.command." + command.commandPath.replace(' ', '.')
if (Bukkit.getPluginManager().getPermission(perm) == null) //Check needed for plugin reset
Bukkit.getPluginManager().addPermission(Permission(perm,
PermissionDefault.TRUE)) //Allow commands by default, it will check mod-only
for (node in getSubcommands(commandNode)) {
if (path.length > 0) {
val subperm = perm + path
if (Bukkit.getPluginManager().getPermission(subperm) == null) //Check needed for plugin reset
Bukkit.getPluginManager().addPermission(
Permission(
subperm,
PermissionDefault.TRUE
)
) //Allow commands by default, it will check mod-only
}
val permPrefix = "chroma.command."
//Allow commands by default, it will check mod-only
val nodes = commandNode.coreExecutable<Command2MCSender, ICommand2MC>()
?.let { getSubcommands(commandNode) + it } ?: getSubcommands(commandNode)
for (node in nodes) {
val subperm = permPrefix + node.data.fullPath.replace(' ', '.')
if (Bukkit.getPluginManager().getPermission(subperm) == null) //Check needed for plugin reset
Bukkit.getPluginManager().addPermission(Permission(subperm, PermissionDefault.TRUE))
val pg = permGroup(node.data)
if (pg.isEmpty()) continue
val permGroup = "chroma.$pg"
//Do not allow any commands that belong to a group by default
if (Bukkit.getPluginManager().getPermission(permGroup) == null) //It may occur multiple times
Bukkit.getPluginManager().addPermission(
Permission(
permGroup,
PermissionDefault.OP
)
) //Do not allow any commands that belong to a group
Bukkit.getPluginManager().addPermission(Permission(permGroup, PermissionDefault.OP))
}
}
@ -111,7 +93,7 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
/**
* Returns the first group found in the hierarchy starting from the command method **or** the mod group if *any* of the superclasses are mod only.
*
* @param method The subcommand to check
* @param data The data of the subcommand to check
* @return The permission group for the subcommand or empty string
*/
private fun permGroup(data: SubcommandData<ICommand2MC, Command2MCSender>): String {
@ -170,7 +152,8 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
val i = commandline.indexOf(' ')
val mainpath = commandline.substring(1, if (i == -1) commandline.length else i) //Without the slash
//Our commands aren't PluginCommands, unless it's specified in the plugin.yml
return if ((!checkPlugin || (MainPlugin.instance.prioritizeCustomCommands.get() == true))
// So we need to handle the command if it's not a plugin command or if it's a plugin command, but for a ButtonPlugin
return if (!checkPlugin || MainPlugin.instance.prioritizeCustomCommands.get()
|| Bukkit.getPluginCommand(mainpath)?.let { it.plugin is ButtonPlugin } != false
)
super.handleCommand(sender, commandline) else false
@ -178,28 +161,30 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
private var shouldRegisterOfficially = true
private fun registerOfficially(command: ICommand2MC, node: LiteralCommandNode<Command2MCSender>): Command? {
return if (!shouldRegisterOfficially || command.plugin == null) null else try {
val cmdmap = Bukkit.getServer().javaClass.getMethod("getCommandMap").invoke(Bukkit.getServer()) as SimpleCommandMap
return if (!shouldRegisterOfficially) null else try {
val cmdmap =
Bukkit.getServer().javaClass.getMethod("getCommandMap").invoke(Bukkit.getServer()) as SimpleCommandMap
val path = command.commandPath
val x = path.indexOf(' ')
val mainPath = path.substring(0, if (x == -1) path.length else x)
var bukkitCommand: Command
run {
//Commands conflicting with Essentials have to be registered in plugin.yml
val oldcmd = cmdmap.getCommand(command.plugin.name + ":" + mainPath) //The label with the fallback prefix is always registered
if (oldcmd == null) {
bukkitCommand = BukkitCommand(mainPath)
cmdmap.register(command.plugin.name, bukkitCommand)
} else {
bukkitCommand = oldcmd
if (bukkitCommand is PluginCommand) (bukkitCommand as PluginCommand).executor = CommandExecutor { sender: CommandSender, command: Command, label: String, args: Array<String> -> this.executeCommand(sender, command, label, args) }
}
bukkitCommand = oldcmd ?: BukkitCommand(mainPath)
val bukkitCommand: Command
//Commands conflicting with Essentials have to be registered in plugin.yml
val oldcmd =
cmdmap.getCommand(command.plugin.name + ":" + mainPath) //The label with the fallback prefix is always registered
if (oldcmd == null) {
bukkitCommand = BukkitCommand(mainPath)
cmdmap.register(command.plugin.name, bukkitCommand)
} else {
bukkitCommand = oldcmd
if (bukkitCommand is PluginCommand) bukkitCommand.setExecutor(this::executeCommand)
}
if (CommodoreProvider.isSupported()) TabcompleteHelper.registerTabcomplete(command, node, bukkitCommand)
bukkitCommand
} catch (e: Exception) {
if (command.component == null) TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.plugin) else TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.component)
if (command.component == null)
TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.plugin)
else
TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.component)
shouldRegisterOfficially = false
null
}
@ -216,12 +201,14 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
sender.sendMessage("§cAn internal error occurred.")
return true
}
///trim(): remove space if there are no args
handleCommand(Command2MCSender(sender, user.channel.get(), sender),
("/" + command.name + " " + java.lang.String.join(" ", *args)).trim { it <= ' ' }, false) ///trim(): remove space if there are no args
("/${command.name} ${args.joinToString(" ")}").trim { it <= ' ' }, false
)
return true
}
private class BukkitCommand(name: String?) : Command(name) {
private class BukkitCommand(name: String) : Command(name) {
override fun execute(sender: CommandSender, commandLabel: String, args: Array<String>): Boolean {
return ButtonPlugin.command2MC.executeCommand(sender, this, commandLabel, args)
}
@ -232,20 +219,42 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
}
@Throws(IllegalArgumentException::class)
override fun tabComplete(sender: CommandSender, alias: String, args: Array<String>, location: Location): List<String> {
return emptyList()
override fun tabComplete(
sender: CommandSender,
alias: String,
args: Array<out String>?,
location: Location?
): MutableList<String> {
return mutableListOf()
}
}
private object TabcompleteHelper {
private var commodore: Commodore? = null
private fun appendSubcommand(path: String, parent: CommandNode<Any>,
subcommand: SubcommandData<ICommand2MC>?): LiteralCommandNode<Any> {
private val commodore: Commodore by lazy {
val commodore = CommodoreProvider.getCommodore(MainPlugin.instance) //Register all to the Core, it's easier
commodore.register(
LiteralArgumentBuilder.literal<Any?>("un") // TODO: This is a test
.redirect(
RequiredArgumentBuilder.argument<Any?, String>(
"unsomething",
StringArgumentType.word()
).suggests { _, builder ->
builder.suggest("untest").buildFuture()
}.build()
)
)
commodore
}
private fun appendSubcommand(
path: String, parent: CommandNode<Any>,
subcommand: SubcommandData<ICommand2MC>?
): LiteralCommandNode<Any> {
var scmd: LiteralCommandNode<Any>
if (parent.getChild(path) as LiteralCommandNode<kotlin.Any?>?. also { scmd = it } != null) return scmd
val scmdBuilder = LiteralArgumentBuilder.literal<Any>(path)
if (subcommand != null) scmdBuilder.requires { o: Any? ->
val sender = commodore!!.getBukkitSender(o)
val sender = commodore.getBukkitSender(o)
subcommand.hasPermission(sender)
}
scmd = scmdBuilder.build()
@ -253,40 +262,33 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
return scmd
}
private fun registerTabcomplete(command2MC: ICommand2MC, commandNode: LiteralCommandNode<Command2MCSender>, bukkitCommand: Command) {
if (commodore == null) {
commodore = CommodoreProvider.getCommodore(MainPlugin.instance) //Register all to the Core, it's easier
commodore.register(LiteralArgumentBuilder.literal<Any?>("un")
.redirect(RequiredArgumentBuilder.argument<Any?, String>(
"unsomething",
StringArgumentType.word()
).suggests { context: CommandContext<Any?>?, builder: SuggestionsBuilder ->
builder.suggest("untest").buildFuture()
}.build()
)
)
}
commodore!!.dispatcher.root.getChild(commandNode.name) // TODO: Probably unnecessary
val customTCmethods = Arrays.stream(command2MC.javaClass.declaredMethods) //val doesn't recognize the type arguments
.flatMap { method: Method ->
Optional.ofNullable(method.getAnnotation(CustomTabCompleteMethod::class.java)).stream()
.flatMap { ctcmAnn: CustomTabCompleteMethod ->
val paths = Optional.of<Array<String?>>(ctcmAnn.subcommand()).filter { s: Array<String?> -> s.size > 0 }
.orElseGet {
arrayOf(
CommandUtils.getCommandPath(method.name, ' ').trim { it <= ' ' }
)
}
Arrays.stream(paths).map { name: String? -> Triplet(name, ctcmAnn, method) }
}
}.toList()
fun registerTabcomplete(
command2MC: ICommand2MC,
commandNode: LiteralCommandNode<Command2MCSender>,
bukkitCommand: Command
) {
commodore.dispatcher.root.getChild(commandNode.name) // TODO: Probably unnecessary
val customTCmethods =
Arrays.stream(command2MC.javaClass.declaredMethods) //val doesn't recognize the type arguments
.flatMap { method ->
Optional.ofNullable(method.getAnnotation(CustomTabCompleteMethod::class.java)).stream()
.flatMap { ctcmAnn ->
val paths = Optional.of(ctcmAnn.subcommand).filter { s -> s.isNotEmpty() }
.orElseGet {
arrayOf(
CommandUtils.getCommandPath(method.name, ' ').trim { it <= ' ' })
}
Arrays.stream(paths).map { name: String? -> Triple(name, ctcmAnn, method) }
}
}.toList()
for (subcmd in subcmds) {
val subpathAsOne = CommandUtils.getCommandPath(subcmd.method.getName(), ' ').trim { it <= ' ' }
val subpath = subpathAsOne.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
var scmd: CommandNode<Any> = cmd
if (subpath[0].length > 0) { //If the method is def, it will contain one empty string
if (subpath[0].isNotEmpty()) { //If the method is def, it will contain one empty string
for (s in subpath) {
scmd = appendSubcommand(s, scmd, subcmd) //Add method name part of the path (could_be_multiple())
scmd =
appendSubcommand(s, scmd, subcmd) //Add method name part of the path (could_be_multiple())
}
}
val parameters: Array<Parameter> = subcmd.method.getParameters()
@ -294,12 +296,13 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
val parameter = parameters[i]
val customParamType: Boolean
// TODO: Arg type
val param: Any = subcmd.parameters.get(i - 1)
val param: String = subcmd.parameters.get(i - 1)
val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete::class.java))
.map(Function<CustomTabComplete, Array<String>> { obj: CustomTabComplete -> obj.value() })
val customTCmethod = customTCmethods.stream().filter { t: Triplet<String?, CustomTabCompleteMethod, Method> -> subpathAsOne.equals(t.value0, ignoreCase = true) }
.filter { t: Triplet<String?, CustomTabCompleteMethod, Method> -> param.replaceAll("[\\[\\]<>]", "").equalsIgnoreCase(t.value1.param()) }
.findAny()
.map { obj -> obj.value }
val customTCmethod =
customTCmethods.stream().filter { t -> subpathAsOne.equals(t.first, ignoreCase = true) }
.filter { t -> param.replace("[\\[\\]<>]", "").equals(t.second.param, ignoreCase = true) }
.findAny()
val argb: RequiredArgumentBuilder<S, T> = RequiredArgumentBuilder.argument(param, type)
.suggests(SuggestionProvider<S?> { context: CommandContext<S?>, builder: SuggestionsBuilder ->
if (parameter.isVarArgs) { //Do it before the builder is used
@ -310,8 +313,8 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
var ignoreCustomParamType = false
if (customTCmethod.isPresent) {
val tr = customTCmethod.get()
if (tr.value1.ignoreTypeCompletion()) ignoreCustomParamType = true
val method = tr.value2
if (tr.second.ignoreTypeCompletion) ignoreCustomParamType = true
val method = tr.third
val params = method.parameters
val args = arrayOfNulls<Any>(params.size)
var j = 0
@ -319,7 +322,7 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
while (j < args.size && k < subcmd.parameters.length) {
val paramObj = params[j]
if (CommandSender::class.java.isAssignableFrom(paramObj.type)) {
args[j] = commodore!!.getBukkitSender(context.getSource())
args[j] = commodore.getBukkitSender(context.getSource())
j++
continue
}
@ -336,12 +339,18 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
k++ //Only increment if not CommandSender
j++
}
if (args.size == 0 || args[args.size - 1] != null) { //Arguments filled entirely
if (args.isEmpty() || args[args.size - 1] != null) { //Arguments filled entirely
try {
val suggestions = method.invoke(command2MC, *args)
if (suggestions is Iterable<*>) {
for (suggestion in suggestions) if (suggestion is String) builder.suggest(suggestion as String?) else throw ClassCastException("Bad return type! It should return an Iterable<String> or a String[].")
} else if (suggestions is Array<String>) for (suggestion in suggestions as Array<String?>) builder.suggest(suggestion) else throw ClassCastException("Bad return type! It should return a String[] or an Iterable<String>.")
when (val suggestions = method.invoke(command2MC, *args)) {
is Iterable<*> -> {
for (suggestion in suggestions) if (suggestion is String) builder.suggest(
suggestion as String?
) else throw ClassCastException("Bad return type! It should return an Iterable<String> or a String[].")
}
is Array<*> -> for (suggestion in suggestions) builder.suggest(suggestion)
else -> throw ClassCastException("Bad return type! It should return a String[] or an Iterable<String>.")
}
} catch (e: Exception) {
val msg = "Failed to run tabcomplete method " + method.name + " for command " + command2MC.javaClass.simpleName
if (command2MC.component == null) TBMCCoreAPI.SendException(msg, e, command2MC.plugin) else TBMCCoreAPI.SendException(msg, e, command2MC.component)
@ -378,10 +387,12 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
val pluginName = command2MC.plugin.name.lowercase(Locale.getDefault())
val prefixedcmd = LiteralArgumentBuilder.literal<Any>(pluginName + ":" + path.get(0))
.redirect(maincmd).build()
commodore!!.register(prefixedcmd)
commodore.register(prefixedcmd)
for (alias in bukkitCommand.aliases) {
commodore!!.register(LiteralArgumentBuilder.literal<Any>(alias).redirect(maincmd).build())
commodore!!.register(LiteralArgumentBuilder.literal<Any>("$pluginName:$alias").redirect(maincmd).build())
commodore.register(LiteralArgumentBuilder.literal<Any>(alias).redirect(maincmd).build())
commodore.register(
LiteralArgumentBuilder.literal<Any>("$pluginName:$alias").redirect(maincmd).build()
)
}
}
}
@ -389,7 +400,7 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
companion object {
private fun getParamConverter(cl: Class<*>, command2MC: ICommand2MC): ParamConverter<*>? {
val converter = ButtonPlugin.getCommand2MC().paramConverters[cl]
val converter = ButtonPlugin.command2MC.paramConverters[cl]
if (converter == null) {
val msg = "Could not find a suitable converter for type " + cl.simpleName
val exception: Exception = NullPointerException("converter is null")

View file

@ -14,8 +14,8 @@ class CoreCommandBuilder<S : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcom
return this
}
override fun build(): CoreCommandNode<S, TC, TSD> {
val result = CoreCommandNode<_, TC, _>(
override fun build(): CoreCommandNode<S, TSD> {
val result = CoreCommandNode<_, _>(
literal,
command,
requirement,

View file

@ -1,13 +1,14 @@
package buttondevteam.lib.chat
import buttondevteam.lib.chat.commands.NoOpSubcommandData
import buttondevteam.lib.chat.commands.SubcommandData
import com.mojang.brigadier.Command
import com.mojang.brigadier.RedirectModifier
import com.mojang.brigadier.tree.CommandNode
import com.mojang.brigadier.tree.LiteralCommandNode
import java.util.function.Predicate
class CoreCommandNode<T : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcommandData>(
class CoreCommandNode<T : Command2Sender, TSD : NoOpSubcommandData>(
literal: String,
command: Command<T>,
requirement: Predicate<T>,
@ -15,4 +16,7 @@ class CoreCommandNode<T : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcomman
modifier: RedirectModifier<T>,
forks: Boolean,
val data: TSD
) : LiteralCommandNode<T>(literal, command, requirement, redirect, modifier, forks)
) : LiteralCommandNode<T>(literal, command, requirement, redirect, modifier, forks)
typealias CoreExecutableNode<TP, TC> = CoreCommandNode<TP, SubcommandData<TC, TP>>
typealias CoreNoOpNode<TP> = CoreCommandNode<TP, NoOpSubcommandData>

View file

@ -13,7 +13,7 @@ import java.util.function.Function
*
* @param TP The sender's type
</TP> */
abstract class ICommand2<TP : Command2Sender>(manager: Command2<*, TP>) {
abstract class ICommand2<TP : Command2Sender>(val manager: Command2<*, TP>) {
/**
* Default handler for commands, can be used to copy the args too.
*
@ -21,7 +21,7 @@ abstract class ICommand2<TP : Command2Sender>(manager: Command2<*, TP>) {
* @return The success of the command
*/
@Suppress("UNUSED_PARAMETER")
fun def(sender: TP): Boolean {
open fun def(sender: TP): Boolean {
return false
}
@ -32,6 +32,7 @@ abstract class ICommand2<TP : Command2Sender>(manager: Command2<*, TP>) {
* @param message The message to send to the sender
* @return Always true so that the usage isn't shown
*/
@Suppress("unused")
protected fun respond(sender: TP, message: String): Boolean {
sender.sendMessage(message)
return true
@ -49,23 +50,15 @@ abstract class ICommand2<TP : Command2Sender>(manager: Command2<*, TP>) {
return if (ann.helpText.isNotEmpty() || cc == null) ann.helpText else cc.helpText //If cc is null then it's empty array
}
private val path: String
val manager: Command2<*, TP>
open val commandPath: String
/**
* The command's path, or name if top-level command.<br></br>
* For example:<br></br>
* "u admin updateplugin" or "u" for the top level one<br></br>
* <u>The path must be lowercase!</u><br></br>
*
* @return The command path, *which is the command class name by default* (removing any "command" from it) - Change via the [CommandClass] annotation
*/
get() = path
init {
path = getcmdpath()
this.manager = manager
}
/**
* The command's path, or name if top-level command.<br></br>
* For example:<br></br>
* "u admin updateplugin" or "u" for the top level one<br></br>
* <u>The path must be lowercase!</u><br></br>
*
* @return The command path, *which is the command class name by default* (removing any "command" from it) - Change via the [CommandClass] annotation
*/
open val commandPath: String = getcmdpath()
open val commandPaths: Array<String>
/**
@ -74,8 +67,7 @@ abstract class ICommand2<TP : Command2Sender>(manager: Command2<*, TP>) {
*
* @return The full command paths that this command should be registered under in addition to the default one.
*/
get() =// TODO: Deal with this (used for channel IDs)
EMPTY_PATHS
get() = EMPTY_PATHS // TODO: Deal with this (used for channel IDs)
private fun getcmdpath(): String {
if (!javaClass.isAnnotationPresent(CommandClass::class.java)) throw RuntimeException(

View file

@ -7,14 +7,14 @@ import buttondevteam.lib.architecture.Component
abstract class ICommand2MC : ICommand2<Command2MCSender>(command2MC) {
private var _plugin: ButtonPlugin? = null
var plugin: ButtonPlugin
get() = _plugin ?: throw IllegalStateException("The command is not registered to a plugin!")
get() = _plugin ?: throw IllegalStateException("The command is not registered to a Button plugin!")
private set(value) {
if (_plugin != null) throw IllegalStateException("The command is already assigned to a plugin!")
if (_plugin != null) throw IllegalStateException("The command is already assigned to a Button plugin!")
_plugin = value
}
private var _component: Component<*>? = null
var component: Component<*>
get() = _component ?: throw IllegalStateException("The command is not registered to a component!")
var component: Component<*>?
get() = _component
private set(value) {
if (_component != null) throw IllegalStateException("The command is already assigned to a component!")
_component = value

View file

@ -2,8 +2,8 @@ package buttondevteam.lib.chat.commands
import buttondevteam.lib.chat.Command2Sender
import buttondevteam.lib.chat.CoreCommandNode
import buttondevteam.lib.chat.CoreExecutableNode
import buttondevteam.lib.chat.ICommand2
import com.mojang.brigadier.context.CommandContext
import com.mojang.brigadier.tree.CommandNode
import java.util.*
@ -24,20 +24,15 @@ object CommandUtils {
* Casts the node to whatever you say. Use responsibly.
*/
@Suppress("UNCHECKED_CAST")
fun <TP : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcommandData> CommandNode<TP>.core(): CoreCommandNode<TP, TC, TSD> {
return this as CoreCommandNode<TP, TC, TSD>
fun <TP : Command2Sender, TSD : NoOpSubcommandData> CommandNode<TP>.core(): CoreCommandNode<TP, TSD> {
return this as CoreCommandNode<TP, 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>()
fun <TP : Command2Sender, TC : ICommand2<*>> CommandNode<TP>.coreExecutable(): CoreExecutableNode<TP, TC>? {
val ret = core<TP, NoOpSubcommandData>()
return if (ret.data is SubcommandData<*, *>) ret.core() else null
}
val <TP : Command2Sender> CommandContext<TP>.subcommandPath
get(): String {
TODO("Return command path")
}
}

View file

@ -47,7 +47,7 @@ class SubcommandData<TC : ICommand2<*>, TP : Command2Sender>(
*/
val annotations: Array<Annotation>,
/**
* The full command path of this subcommand.
* The space-separated full command path of this subcommand.
*/
val fullPath: String
) : NoOpSubcommandData(helpTextGetter) {