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 @CommandClass
public class ChromaCommand extends ICommand2MC { public class ChromaCommand extends ICommand2MC {
public ChromaCommand() { public ChromaCommand() {
manager.addParamConverter(ButtonPlugin.class, name -> getManager().addParamConverter(ButtonPlugin.class, name ->
(ButtonPlugin) Optional.ofNullable(Bukkit.getPluginManager().getPlugin(name)) (ButtonPlugin) Optional.ofNullable(Bukkit.getPluginManager().getPlugin(name))
.filter(plugin -> plugin instanceof ButtonPlugin).orElse(null), .filter(plugin -> plugin instanceof ButtonPlugin).orElse(null),
"No Chroma plugin found by that name.", () -> Arrays.stream(Bukkit.getPluginManager().getPlugins()) "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() { public override fun pluginEnable() {
instance = this instance = this
val pdf = description 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 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.") logger.warning("No economy plugin found! Components using economy will not be registered.")
saveConfig() saveConfig()
@ -134,9 +134,8 @@ class MainPlugin : ButtonPlugin() {
logger.info("Player data saved.") logger.info("Player data saved.")
} }
private fun setupPermissions(): Boolean { private fun setupPermissions() {
permission = setupProvider(Permission::class.java) permission = setupProvider(Permission::class.java) ?: throw NullPointerException("No permission plugin found!")
return permission != null
} }
private fun setupEconomy(): Boolean { private fun setupEconomy(): Boolean {
@ -159,8 +158,7 @@ class MainPlugin : ButtonPlugin() {
companion object { companion object {
lateinit var instance: MainPlugin lateinit var instance: MainPlugin
@JvmField lateinit var permission: Permission
var permission: Permission? = null
@JvmField @JvmField
var ess: Essentials? = null var ess: Essentials? = null

View file

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

View file

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

View file

@ -133,13 +133,22 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
* @param command The command to register * @param command The command to register
* @return The Brigadier command node if you need it for something (like tab completion) * @return The Brigadier command node if you need it for something (like tab completion)
*/ */
protected fun registerCommandSuper(command: TC): LiteralCommandNode<TP> { protected fun registerCommandSuper(command: TC): CoreCommandNode<TP, *> {
var mainCommandNode: LiteralCommandNode<TP>? = null var mainCommandNode: CoreCommandNode<TP, *>? = null
for (meth in command.javaClass.methods) { for (meth in command.javaClass.methods) {
val ann = meth.getAnnotation(Subcommand::class.java) ?: continue val ann = meth.getAnnotation(Subcommand::class.java) ?: continue
val fullPath = command.commandPath + CommandUtils.getCommandPath(meth.name, ' ') val fullPath = command.commandPath + CommandUtils.getCommandPath(meth.name, ' ')
val (lastNode, mainNode, remainingPath) = registerNodeFromPath(fullPath) 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 if (mainCommandNode == null) mainCommandNode = mainNode
else if (mainNode!!.name != mainCommandNode.name) { 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) 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, * @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) * 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(" ") val split = path.split(" ")
var parent: CommandNode<TP> = dispatcher.root var parent: CommandNode<TP> = dispatcher.root
var mainCommand: LiteralCommandNode<TP>? = null var mainCommand: CoreCommandNode<TP, *>? = null
split.forEachIndexed { i, part -> split.forEachIndexed { i, part ->
val child = parent.getChild(part) val child = parent.getChild(part)
if (child == null) parent.addChild(CoreCommandBuilder.literalNoOp<TP, TC>(part, getSubcommandList()) if (child == null) parent.addChild(CoreCommandBuilder.literalNoOp<TP, TC>(part, getSubcommandList())
.executes(::executeHelpText).build().also { parent = it }) .executes(::executeHelpText).build().also { parent = it })
else parent = child 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()) 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 * @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() 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()) .collect(Collectors.toUnmodifiableSet())
/** /**
@ -373,7 +383,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, 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() 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 * @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 } 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( private fun unregisterCommandIf(
condition: Predicate<CoreCommandNode<TP, TC, SubcommandData<TC, TP>>>, condition: Predicate<CoreCommandNode<TP, SubcommandData<TC, TP>>>,
root: CoreCommandNode<TP, TC, NoOpSubcommandData> root: CoreCommandNode<TP, NoOpSubcommandData>
) { ) {
// TODO: Remvoe no-op nodes without children // 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
@ -408,8 +418,24 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
/** /**
* Get all subcommands of the specified command. Only returns executable nodes. * 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>>> { fun getSubcommands(
return dispatcher.root.children.mapNotNull { it.coreExecutable<TP, TC>() } // TODO: Needs more depth 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.ButtonPlugin
import buttondevteam.lib.architecture.Component import buttondevteam.lib.architecture.Component
import buttondevteam.lib.chat.commands.CommandUtils import buttondevteam.lib.chat.commands.CommandUtils
import buttondevteam.lib.chat.commands.CommandUtils.coreExecutable
import buttondevteam.lib.chat.commands.MCCommandSettings import buttondevteam.lib.chat.commands.MCCommandSettings
import buttondevteam.lib.chat.commands.SubcommandData import buttondevteam.lib.chat.commands.SubcommandData
import buttondevteam.lib.player.ChromaGamerBase import buttondevteam.lib.player.ChromaGamerBase
@ -29,7 +30,6 @@ import org.bukkit.entity.Player
import org.bukkit.event.Listener import org.bukkit.event.Listener
import org.bukkit.permissions.Permission import org.bukkit.permissions.Permission
import org.bukkit.permissions.PermissionDefault import org.bukkit.permissions.PermissionDefault
import java.lang.reflect.Method
import java.lang.reflect.Parameter import java.lang.reflect.Parameter
import java.util.* import java.util.*
import java.util.function.BiConsumer import java.util.function.BiConsumer
@ -43,42 +43,24 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
* @param command The command to register * @param command The command to register
*/ */
override fun registerCommand(command: ICommand2MC) { 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 commandNode = super.registerCommandSuper(command)
val bcmd = registerOfficially(command, commandNode) val bcmd = registerOfficially(command, commandNode)
if (bcmd != null) // TODO: Support aliases if (bcmd != null) // TODO: Support aliases
super.registerCommandSuper(command) super.registerCommandSuper(command)
val perm = "chroma.command." + command.commandPath.replace(' ', '.') val permPrefix = "chroma.command."
if (Bukkit.getPluginManager().getPermission(perm) == null) //Check needed for plugin reset //Allow commands by default, it will check mod-only
Bukkit.getPluginManager().addPermission(Permission(perm, val nodes = commandNode.coreExecutable<Command2MCSender, ICommand2MC>()
PermissionDefault.TRUE)) //Allow commands by default, it will check mod-only ?.let { getSubcommands(commandNode) + it } ?: getSubcommands(commandNode)
for (node in getSubcommands(commandNode)) { for (node in nodes) {
if (path.length > 0) { val subperm = permPrefix + node.data.fullPath.replace(' ', '.')
val subperm = perm + path if (Bukkit.getPluginManager().getPermission(subperm) == null) //Check needed for plugin reset
if (Bukkit.getPluginManager().getPermission(subperm) == null) //Check needed for plugin reset Bukkit.getPluginManager().addPermission(Permission(subperm, PermissionDefault.TRUE))
Bukkit.getPluginManager().addPermission(
Permission(
subperm,
PermissionDefault.TRUE
)
) //Allow commands by default, it will check mod-only
}
val pg = permGroup(node.data) val pg = permGroup(node.data)
if (pg.isEmpty()) continue if (pg.isEmpty()) continue
val permGroup = "chroma.$pg" 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 if (Bukkit.getPluginManager().getPermission(permGroup) == null) //It may occur multiple times
Bukkit.getPluginManager().addPermission( Bukkit.getPluginManager().addPermission(Permission(permGroup, PermissionDefault.OP))
Permission(
permGroup,
PermissionDefault.OP
)
) //Do not allow any commands that belong to a group
} }
} }
@ -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. * 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 * @return The permission group for the subcommand or empty string
*/ */
private fun permGroup(data: SubcommandData<ICommand2MC, Command2MCSender>): 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 i = commandline.indexOf(' ')
val mainpath = commandline.substring(1, if (i == -1) commandline.length else i) //Without the slash 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 //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 || Bukkit.getPluginCommand(mainpath)?.let { it.plugin is ButtonPlugin } != false
) )
super.handleCommand(sender, commandline) else false super.handleCommand(sender, commandline) else false
@ -178,28 +161,30 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
private var shouldRegisterOfficially = true private var shouldRegisterOfficially = true
private fun registerOfficially(command: ICommand2MC, node: LiteralCommandNode<Command2MCSender>): Command? { private fun registerOfficially(command: ICommand2MC, node: LiteralCommandNode<Command2MCSender>): Command? {
return if (!shouldRegisterOfficially || command.plugin == null) null else try { return if (!shouldRegisterOfficially) null else try {
val cmdmap = Bukkit.getServer().javaClass.getMethod("getCommandMap").invoke(Bukkit.getServer()) as SimpleCommandMap val cmdmap =
Bukkit.getServer().javaClass.getMethod("getCommandMap").invoke(Bukkit.getServer()) as SimpleCommandMap
val path = command.commandPath val path = command.commandPath
val x = path.indexOf(' ') val x = path.indexOf(' ')
val mainPath = path.substring(0, if (x == -1) path.length else x) val mainPath = path.substring(0, if (x == -1) path.length else x)
var bukkitCommand: Command val bukkitCommand: Command
run { //Commands conflicting with Essentials have to be registered in plugin.yml
//Commands conflicting with Essentials have to be registered in plugin.yml val oldcmd =
val oldcmd = cmdmap.getCommand(command.plugin.name + ":" + mainPath) //The label with the fallback prefix is always registered cmdmap.getCommand(command.plugin.name + ":" + mainPath) //The label with the fallback prefix is always registered
if (oldcmd == null) { if (oldcmd == null) {
bukkitCommand = BukkitCommand(mainPath) bukkitCommand = BukkitCommand(mainPath)
cmdmap.register(command.plugin.name, bukkitCommand) cmdmap.register(command.plugin.name, bukkitCommand)
} else { } else {
bukkitCommand = oldcmd 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) } if (bukkitCommand is PluginCommand) bukkitCommand.setExecutor(this::executeCommand)
}
bukkitCommand = oldcmd ?: BukkitCommand(mainPath)
} }
if (CommodoreProvider.isSupported()) TabcompleteHelper.registerTabcomplete(command, node, bukkitCommand) if (CommodoreProvider.isSupported()) TabcompleteHelper.registerTabcomplete(command, node, bukkitCommand)
bukkitCommand bukkitCommand
} catch (e: Exception) { } 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 shouldRegisterOfficially = false
null null
} }
@ -216,12 +201,14 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
sender.sendMessage("§cAn internal error occurred.") sender.sendMessage("§cAn internal error occurred.")
return true return true
} }
///trim(): remove space if there are no args
handleCommand(Command2MCSender(sender, user.channel.get(), sender), 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 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 { override fun execute(sender: CommandSender, commandLabel: String, args: Array<String>): Boolean {
return ButtonPlugin.command2MC.executeCommand(sender, this, commandLabel, args) return ButtonPlugin.command2MC.executeCommand(sender, this, commandLabel, args)
} }
@ -232,20 +219,42 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
} }
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
override fun tabComplete(sender: CommandSender, alias: String, args: Array<String>, location: Location): List<String> { override fun tabComplete(
return emptyList() sender: CommandSender,
alias: String,
args: Array<out String>?,
location: Location?
): MutableList<String> {
return mutableListOf()
} }
} }
private object TabcompleteHelper { private object TabcompleteHelper {
private var commodore: Commodore? = null private val commodore: Commodore by lazy {
private fun appendSubcommand(path: String, parent: CommandNode<Any>, val commodore = CommodoreProvider.getCommodore(MainPlugin.instance) //Register all to the Core, it's easier
subcommand: SubcommandData<ICommand2MC>?): LiteralCommandNode<Any> { 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> var scmd: LiteralCommandNode<Any>
if (parent.getChild(path) as LiteralCommandNode<kotlin.Any?>?. also { scmd = it } != null) return scmd if (parent.getChild(path) as LiteralCommandNode<kotlin.Any?>?. also { scmd = it } != null) return scmd
val scmdBuilder = LiteralArgumentBuilder.literal<Any>(path) val scmdBuilder = LiteralArgumentBuilder.literal<Any>(path)
if (subcommand != null) scmdBuilder.requires { o: Any? -> if (subcommand != null) scmdBuilder.requires { o: Any? ->
val sender = commodore!!.getBukkitSender(o) val sender = commodore.getBukkitSender(o)
subcommand.hasPermission(sender) subcommand.hasPermission(sender)
} }
scmd = scmdBuilder.build() scmd = scmdBuilder.build()
@ -253,40 +262,33 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
return scmd return scmd
} }
private fun registerTabcomplete(command2MC: ICommand2MC, commandNode: LiteralCommandNode<Command2MCSender>, bukkitCommand: Command) { fun registerTabcomplete(
if (commodore == null) { command2MC: ICommand2MC,
commodore = CommodoreProvider.getCommodore(MainPlugin.instance) //Register all to the Core, it's easier commandNode: LiteralCommandNode<Command2MCSender>,
commodore.register(LiteralArgumentBuilder.literal<Any?>("un") bukkitCommand: Command
.redirect(RequiredArgumentBuilder.argument<Any?, String>( ) {
"unsomething", commodore.dispatcher.root.getChild(commandNode.name) // TODO: Probably unnecessary
StringArgumentType.word() val customTCmethods =
).suggests { context: CommandContext<Any?>?, builder: SuggestionsBuilder -> Arrays.stream(command2MC.javaClass.declaredMethods) //val doesn't recognize the type arguments
builder.suggest("untest").buildFuture() .flatMap { method ->
}.build() Optional.ofNullable(method.getAnnotation(CustomTabCompleteMethod::class.java)).stream()
) .flatMap { ctcmAnn ->
) val paths = Optional.of(ctcmAnn.subcommand).filter { s -> s.isNotEmpty() }
} .orElseGet {
commodore!!.dispatcher.root.getChild(commandNode.name) // TODO: Probably unnecessary arrayOf(
val customTCmethods = Arrays.stream(command2MC.javaClass.declaredMethods) //val doesn't recognize the type arguments CommandUtils.getCommandPath(method.name, ' ').trim { it <= ' ' })
.flatMap { method: Method -> }
Optional.ofNullable(method.getAnnotation(CustomTabCompleteMethod::class.java)).stream() Arrays.stream(paths).map { name: String? -> Triple(name, ctcmAnn, method) }
.flatMap { ctcmAnn: CustomTabCompleteMethod -> }
val paths = Optional.of<Array<String?>>(ctcmAnn.subcommand()).filter { s: Array<String?> -> s.size > 0 } }.toList()
.orElseGet {
arrayOf(
CommandUtils.getCommandPath(method.name, ' ').trim { it <= ' ' }
)
}
Arrays.stream(paths).map { name: String? -> Triplet(name, ctcmAnn, method) }
}
}.toList()
for (subcmd in subcmds) { for (subcmd in subcmds) {
val subpathAsOne = CommandUtils.getCommandPath(subcmd.method.getName(), ' ').trim { it <= ' ' } val subpathAsOne = CommandUtils.getCommandPath(subcmd.method.getName(), ' ').trim { it <= ' ' }
val subpath = subpathAsOne.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() val subpath = subpathAsOne.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
var scmd: CommandNode<Any> = cmd 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) { 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() val parameters: Array<Parameter> = subcmd.method.getParameters()
@ -294,12 +296,13 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
val parameter = parameters[i] val parameter = parameters[i]
val customParamType: Boolean val customParamType: Boolean
// TODO: Arg type // 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)) val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete::class.java))
.map(Function<CustomTabComplete, Array<String>> { obj: CustomTabComplete -> obj.value() }) .map { obj -> obj.value }
val customTCmethod = customTCmethods.stream().filter { t: Triplet<String?, CustomTabCompleteMethod, Method> -> subpathAsOne.equals(t.value0, ignoreCase = true) } val customTCmethod =
.filter { t: Triplet<String?, CustomTabCompleteMethod, Method> -> param.replaceAll("[\\[\\]<>]", "").equalsIgnoreCase(t.value1.param()) } customTCmethods.stream().filter { t -> subpathAsOne.equals(t.first, ignoreCase = true) }
.findAny() .filter { t -> param.replace("[\\[\\]<>]", "").equals(t.second.param, ignoreCase = true) }
.findAny()
val argb: RequiredArgumentBuilder<S, T> = RequiredArgumentBuilder.argument(param, type) val argb: RequiredArgumentBuilder<S, T> = RequiredArgumentBuilder.argument(param, type)
.suggests(SuggestionProvider<S?> { context: CommandContext<S?>, builder: SuggestionsBuilder -> .suggests(SuggestionProvider<S?> { context: CommandContext<S?>, builder: SuggestionsBuilder ->
if (parameter.isVarArgs) { //Do it before the builder is used if (parameter.isVarArgs) { //Do it before the builder is used
@ -310,8 +313,8 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
var ignoreCustomParamType = false var ignoreCustomParamType = false
if (customTCmethod.isPresent) { if (customTCmethod.isPresent) {
val tr = customTCmethod.get() val tr = customTCmethod.get()
if (tr.value1.ignoreTypeCompletion()) ignoreCustomParamType = true if (tr.second.ignoreTypeCompletion) ignoreCustomParamType = true
val method = tr.value2 val method = tr.third
val params = method.parameters val params = method.parameters
val args = arrayOfNulls<Any>(params.size) val args = arrayOfNulls<Any>(params.size)
var j = 0 var j = 0
@ -319,7 +322,7 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
while (j < args.size && k < subcmd.parameters.length) { while (j < args.size && k < subcmd.parameters.length) {
val paramObj = params[j] val paramObj = params[j]
if (CommandSender::class.java.isAssignableFrom(paramObj.type)) { if (CommandSender::class.java.isAssignableFrom(paramObj.type)) {
args[j] = commodore!!.getBukkitSender(context.getSource()) args[j] = commodore.getBukkitSender(context.getSource())
j++ j++
continue continue
} }
@ -336,12 +339,18 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
k++ //Only increment if not CommandSender k++ //Only increment if not CommandSender
j++ 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 { try {
val suggestions = method.invoke(command2MC, *args) when (val suggestions = method.invoke(command2MC, *args)) {
if (suggestions is Iterable<*>) { 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[].") for (suggestion in suggestions) if (suggestion is String) builder.suggest(
} 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>.") 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) { } catch (e: Exception) {
val msg = "Failed to run tabcomplete method " + method.name + " for command " + command2MC.javaClass.simpleName 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) 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 pluginName = command2MC.plugin.name.lowercase(Locale.getDefault())
val prefixedcmd = LiteralArgumentBuilder.literal<Any>(pluginName + ":" + path.get(0)) val prefixedcmd = LiteralArgumentBuilder.literal<Any>(pluginName + ":" + path.get(0))
.redirect(maincmd).build() .redirect(maincmd).build()
commodore!!.register(prefixedcmd) commodore.register(prefixedcmd)
for (alias in bukkitCommand.aliases) { for (alias in bukkitCommand.aliases) {
commodore!!.register(LiteralArgumentBuilder.literal<Any>(alias).redirect(maincmd).build()) commodore.register(LiteralArgumentBuilder.literal<Any>(alias).redirect(maincmd).build())
commodore!!.register(LiteralArgumentBuilder.literal<Any>("$pluginName:$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 { companion object {
private fun getParamConverter(cl: Class<*>, command2MC: ICommand2MC): ParamConverter<*>? { private fun getParamConverter(cl: Class<*>, command2MC: ICommand2MC): ParamConverter<*>? {
val converter = ButtonPlugin.getCommand2MC().paramConverters[cl] val converter = ButtonPlugin.command2MC.paramConverters[cl]
if (converter == null) { if (converter == null) {
val msg = "Could not find a suitable converter for type " + cl.simpleName val msg = "Could not find a suitable converter for type " + cl.simpleName
val exception: Exception = NullPointerException("converter is null") val exception: Exception = NullPointerException("converter is null")

View file

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

View file

@ -1,13 +1,14 @@
package buttondevteam.lib.chat package buttondevteam.lib.chat
import buttondevteam.lib.chat.commands.NoOpSubcommandData import buttondevteam.lib.chat.commands.NoOpSubcommandData
import buttondevteam.lib.chat.commands.SubcommandData
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 java.util.function.Predicate import java.util.function.Predicate
class CoreCommandNode<T : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcommandData>( class CoreCommandNode<T : Command2Sender, TSD : NoOpSubcommandData>(
literal: String, literal: String,
command: Command<T>, command: Command<T>,
requirement: Predicate<T>, requirement: Predicate<T>,
@ -16,3 +17,6 @@ class CoreCommandNode<T : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcomman
forks: Boolean, forks: Boolean,
val data: TSD 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 * @param TP The sender's type
</TP> */ </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. * 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 * @return The success of the command
*/ */
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
fun def(sender: TP): Boolean { open fun def(sender: TP): Boolean {
return false return false
} }
@ -32,6 +32,7 @@ abstract class ICommand2<TP : Command2Sender>(manager: Command2<*, TP>) {
* @param message The message to send to the sender * @param message The message to send to the sender
* @return Always true so that the usage isn't shown * @return Always true so that the usage isn't shown
*/ */
@Suppress("unused")
protected fun respond(sender: TP, message: String): Boolean { protected fun respond(sender: TP, message: String): Boolean {
sender.sendMessage(message) sender.sendMessage(message)
return true 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 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> * The command's path, or name if top-level command.<br></br>
open val commandPath: String * For example:<br></br>
/** * "u admin updateplugin" or "u" for the top level one<br></br>
* The command's path, or name if top-level command.<br></br> * <u>The path must be lowercase!</u><br></br>
* For example:<br></br> *
* "u admin updateplugin" or "u" for the top level one<br></br> * @return The command path, *which is the command class name by default* (removing any "command" from it) - Change via the [CommandClass] annotation
* <u>The path must be lowercase!</u><br></br> */
* open val commandPath: String = getcmdpath()
* @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
}
open val commandPaths: Array<String> 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. * @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) get() = EMPTY_PATHS // TODO: Deal with this (used for channel IDs)
EMPTY_PATHS
private fun getcmdpath(): String { private fun getcmdpath(): String {
if (!javaClass.isAnnotationPresent(CommandClass::class.java)) throw RuntimeException( 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) { abstract class ICommand2MC : ICommand2<Command2MCSender>(command2MC) {
private var _plugin: ButtonPlugin? = null private var _plugin: ButtonPlugin? = null
var plugin: ButtonPlugin 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) { 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 _plugin = value
} }
private var _component: Component<*>? = null private var _component: Component<*>? = null
var component: Component<*> var component: Component<*>?
get() = _component ?: throw IllegalStateException("The command is not registered to a component!") get() = _component
private set(value) { private set(value) {
if (_component != null) throw IllegalStateException("The command is already assigned to a component!") if (_component != null) throw IllegalStateException("The command is already assigned to a component!")
_component = value _component = value

View file

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