Continued implementing support for annotations, finished permission check
This commit is contained in:
parent
af7d097f9b
commit
4e617fdbc9
5 changed files with 50 additions and 59 deletions
|
@ -129,9 +129,9 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
||||||
var mainCommandNode: LiteralCommandNode<TP>? = null
|
var mainCommandNode: LiteralCommandNode<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 methodPath = CommandUtils.getCommandPath(meth.name, ' ')
|
val fullPath = command.commandPath + CommandUtils.getCommandPath(meth.name, ' ')
|
||||||
val (lastNode, mainNode, remainingPath) = registerNodeFromPath(command.commandPath + methodPath)
|
val (lastNode, mainNode, remainingPath) = registerNodeFromPath(fullPath)
|
||||||
lastNode.addChild(getExecutableNode(meth, command, ann, remainingPath, CommandArgumentHelpManager(command)))
|
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)
|
||||||
|
@ -148,19 +148,27 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
||||||
*
|
*
|
||||||
* @param method The subcommand method
|
* @param method The subcommand method
|
||||||
* @param command The command object
|
* @param command The command object
|
||||||
* @param path The command path
|
* @param ann The subcommand annotation
|
||||||
|
* @param remainingPath The command path
|
||||||
|
* @param argHelpManager The object that gets the usage text from code
|
||||||
|
* @param fullPath The full command path as registered
|
||||||
* @return The executable node
|
* @return The executable node
|
||||||
*/
|
*/
|
||||||
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, remainingPath: String,
|
||||||
|
argHelpManager: CommandArgumentHelpManager<TC, TP>, fullPath: String): 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 helpText = command.getHelpText(method, ann)
|
val helpText = command.getHelpText(method, ann)
|
||||||
val node = CoreCommandBuilder.literal(path, params[0].type, paramMap, params, command,
|
val node = CoreCommandBuilder.literal(
|
||||||
|
remainingPath, params[0].type, paramMap, params, command,
|
||||||
{ helpText }, // TODO: Help text getter support
|
{ helpText }, // TODO: Help text getter support
|
||||||
{ sender: TP -> hasPermission(sender, command, method) })
|
{ sender: TP, data: SubcommandData<TC, TP> -> hasPermission(sender, data) },
|
||||||
|
method.annotations.filterNot { it is Subcommand }.toTypedArray(),
|
||||||
|
fullPath
|
||||||
|
)
|
||||||
.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
|
||||||
|
@ -225,7 +233,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
||||||
numAnn.upperLimit
|
numAnn.upperLimit
|
||||||
),
|
),
|
||||||
param.isAnnotationPresent(OptionalArg::class.java),
|
param.isAnnotationPresent(OptionalArg::class.java),
|
||||||
name)
|
name, param.annotations.filterNot { it is OptionalArg || it is NumberArg || it is TextArg }.toTypedArray())
|
||||||
}, parameters[0].type)
|
}, parameters[0].type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,7 +346,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
||||||
invokeCommand.run();*/return 0
|
invokeCommand.run();*/return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun hasPermission(context: CommandContext<TP>): Boolean
|
abstract fun hasPermission(sender: TP, data: SubcommandData<TC, TP>): Boolean
|
||||||
val commandsText: Array<String> get() = commandHelp.toTypedArray()
|
val commandsText: Array<String> get() = commandHelp.toTypedArray()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,7 +5,6 @@ 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.subcommandPath
|
|
||||||
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
|
||||||
|
@ -83,29 +82,26 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hasPermission(context: CommandContext<Command2MCSender>): Boolean {
|
override fun hasPermission(sender: Command2MCSender, data: SubcommandData<ICommand2MC, Command2MCSender>): Boolean {
|
||||||
return hasPermission(context.source.sender, context.subcommandPath)
|
val mcsender = sender.sender
|
||||||
}
|
if (mcsender is ConsoleCommandSender) return true //Always allow the console
|
||||||
|
|
||||||
fun hasPermission(sender: CommandSender, path: String): Boolean {
|
|
||||||
if (sender is ConsoleCommandSender) return true //Always allow the console
|
|
||||||
var pg: String
|
|
||||||
var p = true
|
var p = true
|
||||||
val cmdperm = "chroma.command.$path"
|
val cmdperm = "chroma.command.${data.fullPath.replace(' ', '.')}"
|
||||||
// TODO: Register a permission for the main command as well - the previous implementation relied on the way the commands were defined
|
// TODO: Register a permission for the main command as well - the previous implementation relied on the way the commands were defined
|
||||||
val perms = arrayOf(
|
val perms = arrayOf(
|
||||||
cmdperm + path,
|
cmdperm,
|
||||||
if (permGroup(command, method).also { pg = it }.length > 0) "chroma.$pg" else null
|
permGroup(data).let { if (it.isEmpty()) null else "chroma.$it" }
|
||||||
)
|
)
|
||||||
for (perm in perms) {
|
for (perm in perms) {
|
||||||
if (perm != null) {
|
if (perm != null) {
|
||||||
if (p) { //Use OfflinePlayer to avoid fetching player data
|
if (p) { //Use OfflinePlayer to avoid fetching player data
|
||||||
p = if (sender is OfflinePlayer) MainPlugin.permission.playerHas(
|
p = if (mcsender is OfflinePlayer) MainPlugin.permission.playerHas(
|
||||||
if (sender is Player) sender.location.world.name else null,
|
if (mcsender is Player) mcsender.location.world?.name else null,
|
||||||
sender as OfflinePlayer,
|
mcsender as OfflinePlayer,
|
||||||
perm
|
perm
|
||||||
) else false //Use sender's method
|
) else false //Use sender's method
|
||||||
if (!p) p = sender.hasPermission(perm)
|
if (!p) p = mcsender.hasPermission(perm)
|
||||||
} else break //If any of the permissions aren't granted then don't allow
|
} else break //If any of the permissions aren't granted then don't allow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,28 +121,6 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
|
||||||
return group ?: ""
|
return group ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loops until it finds a value that is **not** the same as def
|
|
||||||
*
|
|
||||||
* @param sourceCl The class which has the annotation
|
|
||||||
* @param annCl The annotation to get
|
|
||||||
* @param annMethod The annotation method to check
|
|
||||||
* @param def The value to ignore when looking for the result
|
|
||||||
* @param <T> The annotation type
|
|
||||||
* @param <V> The type of the value
|
|
||||||
* @return The value returned by the first superclass or def
|
|
||||||
</V></T> */
|
|
||||||
private fun <T : Annotation?, V> getAnnForValue(sourceCl: Class<*>, annCl: Class<T>, annMethod: Function<T, V>, def: V): V {
|
|
||||||
var cl: Class<*>? = sourceCl
|
|
||||||
while (cl != null) {
|
|
||||||
val cc = cl.getAnnotation(annCl)
|
|
||||||
var r: V
|
|
||||||
if (cc != null && annMethod.apply(cc).also { r = it } !== def) return r
|
|
||||||
cl = cl.superclass
|
|
||||||
}
|
|
||||||
return def
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatically colors the message red.
|
* Automatically colors the message red.
|
||||||
* {@see super#addParamConverter}
|
* {@see super#addParamConverter}
|
||||||
|
@ -177,7 +151,7 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unregisterCommands(component: Component<*>) {
|
fun unregisterCommands(component: Component<*>) {
|
||||||
unregisterCommandIf({ node: CoreCommandNode<Command2MCSender?, ICommand2MC?> ->
|
unregisterCommandIf({ node ->
|
||||||
Optional.ofNullable(node.data.command).map { obj: ICommand2MC -> obj.plugin }
|
Optional.ofNullable(node.data.command).map { obj: ICommand2MC -> obj.plugin }
|
||||||
.map { comp: ButtonPlugin -> component.javaClass.simpleName == comp.javaClass.simpleName }.orElse(false)
|
.map { comp: ButtonPlugin -> component.javaClass.simpleName == comp.javaClass.simpleName }.orElse(false)
|
||||||
}, true)
|
}, true)
|
||||||
|
@ -190,10 +164,9 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
|
||||||
private fun handleCommand(sender: Command2MCSender, commandline: String, checkPlugin: Boolean): Boolean {
|
private fun handleCommand(sender: Command2MCSender, commandline: String, checkPlugin: Boolean): Boolean {
|
||||||
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
|
||||||
var pcmd: PluginCommand
|
//Our commands aren't PluginCommands, unless it's specified in the plugin.yml
|
||||||
return if ((!checkPlugin
|
return if ((!checkPlugin || (MainPlugin.Instance.prioritizeCustomCommands.get() == true))
|
||||||
|| MainPlugin.Instance.prioritizeCustomCommands.get()) || Bukkit.getPluginCommand(mainpath).also { pcmd = it } == null //Our commands aren't PluginCommands
|
|| Bukkit.getPluginCommand(mainpath)?.let { it.plugin is ButtonPlugin } != false)
|
||||||
|| pcmd.plugin is ButtonPlugin) //Unless it's specified in the plugin.yml
|
|
||||||
super.handleCommand(sender, commandline) else false
|
super.handleCommand(sender, commandline) else false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,9 @@ class CoreCommandBuilder<S : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcom
|
||||||
* @param argumentsInOrder A list of the command arguments in the order they are expected
|
* @param argumentsInOrder A list of the command arguments in the order they are expected
|
||||||
* @param command The command object that has this subcommand
|
* @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.
|
* @param helpTextGetter Custom help text that can depend on the context. The function receives the sender as the command itself receives it.
|
||||||
|
* @param hasPermission A function that determines whether the user has permission to run this subcommand
|
||||||
|
* @param annotations All annotations implemented by the method that executes the command
|
||||||
|
* @param fullPath The full command path of this subcommand.
|
||||||
*/
|
*/
|
||||||
fun <S : Command2Sender, TC : ICommand2<*>> literal(
|
fun <S : Command2Sender, TC : ICommand2<*>> literal(
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -48,11 +51,13 @@ class CoreCommandBuilder<S : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcom
|
||||||
argumentsInOrder: List<CommandArgument>,
|
argumentsInOrder: List<CommandArgument>,
|
||||||
command: TC,
|
command: TC,
|
||||||
helpTextGetter: (Any) -> Array<String>,
|
helpTextGetter: (Any) -> Array<String>,
|
||||||
hasPermission: (S) -> Boolean
|
hasPermission: (S, SubcommandData<TC, S>) -> Boolean,
|
||||||
|
annotations: Array<Annotation>,
|
||||||
|
fullPath: String
|
||||||
): CoreCommandBuilder<S, TC, SubcommandData<TC, S>> {
|
): CoreCommandBuilder<S, TC, SubcommandData<TC, S>> {
|
||||||
return CoreCommandBuilder(
|
return CoreCommandBuilder(
|
||||||
name,
|
name,
|
||||||
SubcommandData(senderType, arguments, argumentsInOrder, command, helpTextGetter, hasPermission)
|
SubcommandData(senderType, arguments, argumentsInOrder, command, helpTextGetter, hasPermission, annotations, fullPath)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,5 +9,6 @@ class CommandArgument(
|
||||||
val greedy: Boolean,
|
val greedy: Boolean,
|
||||||
val limits: Pair<Double, Double>,
|
val limits: Pair<Double, Double>,
|
||||||
val optional: Boolean,
|
val optional: Boolean,
|
||||||
val description: String
|
val description: String,
|
||||||
|
val annotations: Array<Annotation> // TODO: Annotations for parameters as well
|
||||||
)
|
)
|
||||||
|
|
|
@ -41,11 +41,15 @@ class SubcommandData<TC : ICommand2<*>, TP : Command2Sender>(
|
||||||
/**
|
/**
|
||||||
* A function that determines whether the user has permission to run this subcommand.
|
* A function that determines whether the user has permission to run this subcommand.
|
||||||
*/
|
*/
|
||||||
private val permissionCheck: (TP) -> Boolean,
|
private val permissionCheck: (TP, SubcommandData<TC, TP>) -> Boolean,
|
||||||
/**
|
/**
|
||||||
* All annotations implemented by the method that executes the command. Can be used to add custom metadata when implementing a platform.
|
* All annotations implemented by the method that executes the command. Can be used to add custom metadata when implementing a platform.
|
||||||
*/
|
*/
|
||||||
val annotations: Array<Annotation>
|
val annotations: Array<Annotation>,
|
||||||
|
/**
|
||||||
|
* The full command path of this subcommand.
|
||||||
|
*/
|
||||||
|
val fullPath: String
|
||||||
) : NoOpSubcommandData(helpTextGetter) {
|
) : NoOpSubcommandData(helpTextGetter) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,6 +59,6 @@ class SubcommandData<TC : ICommand2<*>, TP : Command2Sender>(
|
||||||
* @return Whether the user has permission
|
* @return Whether the user has permission
|
||||||
*/
|
*/
|
||||||
fun hasPermission(sender: TP): Boolean {
|
fun hasPermission(sender: TP): Boolean {
|
||||||
return permissionCheck(sender)
|
return permissionCheck(sender, this)
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue