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
|
||||
for (meth in command.javaClass.methods) {
|
||||
val ann = meth.getAnnotation(Subcommand::class.java) ?: continue
|
||||
val methodPath = CommandUtils.getCommandPath(meth.name, ' ')
|
||||
val (lastNode, mainNode, remainingPath) = registerNodeFromPath(command.commandPath + methodPath)
|
||||
lastNode.addChild(getExecutableNode(meth, command, ann, remainingPath, CommandArgumentHelpManager(command)))
|
||||
val fullPath = command.commandPath + CommandUtils.getCommandPath(meth.name, ' ')
|
||||
val (lastNode, mainNode, remainingPath) = registerNodeFromPath(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)
|
||||
|
@ -146,21 +146,29 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
|||
/**
|
||||
* Returns the node that can actually execute the given subcommand.
|
||||
*
|
||||
* @param method The subcommand method
|
||||
* @param method The subcommand method
|
||||
* @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
|
||||
*/
|
||||
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 paramMap = HashMap<String, CommandArgument>()
|
||||
for (param in params) {
|
||||
paramMap[param.name] = param
|
||||
}
|
||||
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
|
||||
{ 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) }
|
||||
var parent: ArgumentBuilder<TP, *> = node
|
||||
for (param in params) { // Register parameters in the right order
|
||||
|
@ -224,9 +232,9 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
|||
numAnn.lowerLimit,
|
||||
numAnn.upperLimit
|
||||
),
|
||||
param.isAnnotationPresent(OptionalArg::class.java),
|
||||
name)
|
||||
}, parameters[0].type)
|
||||
param.isAnnotationPresent(OptionalArg::class.java),
|
||||
name, param.annotations.filterNot { it is OptionalArg || it is NumberArg || it is TextArg }.toTypedArray())
|
||||
}, parameters[0].type)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -338,7 +346,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
|||
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()
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,7 +5,6 @@ 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.subcommandPath
|
||||
import buttondevteam.lib.chat.commands.MCCommandSettings
|
||||
import buttondevteam.lib.chat.commands.SubcommandData
|
||||
import buttondevteam.lib.player.ChromaGamerBase
|
||||
|
@ -83,29 +82,26 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
|
|||
}
|
||||
}
|
||||
|
||||
override fun hasPermission(context: CommandContext<Command2MCSender>): Boolean {
|
||||
return hasPermission(context.source.sender, context.subcommandPath)
|
||||
}
|
||||
override fun hasPermission(sender: Command2MCSender, data: SubcommandData<ICommand2MC, Command2MCSender>): Boolean {
|
||||
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
|
||||
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
|
||||
val perms = arrayOf(
|
||||
cmdperm + path,
|
||||
if (permGroup(command, method).also { pg = it }.length > 0) "chroma.$pg" else null
|
||||
cmdperm,
|
||||
permGroup(data).let { if (it.isEmpty()) null else "chroma.$it" }
|
||||
)
|
||||
for (perm in perms) {
|
||||
if (perm != null) {
|
||||
if (p) { //Use OfflinePlayer to avoid fetching player data
|
||||
p = if (sender is OfflinePlayer) MainPlugin.permission.playerHas(
|
||||
if (sender is Player) sender.location.world.name else null,
|
||||
sender as OfflinePlayer,
|
||||
p = if (mcsender is OfflinePlayer) MainPlugin.permission.playerHas(
|
||||
if (mcsender is Player) mcsender.location.world?.name else null,
|
||||
mcsender as OfflinePlayer,
|
||||
perm
|
||||
) 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
|
||||
}
|
||||
}
|
||||
|
@ -125,28 +121,6 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
|
|||
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.
|
||||
* {@see super#addParamConverter}
|
||||
|
@ -177,7 +151,7 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
|
|||
}
|
||||
|
||||
fun unregisterCommands(component: Component<*>) {
|
||||
unregisterCommandIf({ node: CoreCommandNode<Command2MCSender?, ICommand2MC?> ->
|
||||
unregisterCommandIf({ node ->
|
||||
Optional.ofNullable(node.data.command).map { obj: ICommand2MC -> obj.plugin }
|
||||
.map { comp: ButtonPlugin -> component.javaClass.simpleName == comp.javaClass.simpleName }.orElse(false)
|
||||
}, true)
|
||||
|
@ -190,10 +164,9 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
|
|||
private fun handleCommand(sender: Command2MCSender, commandline: String, checkPlugin: Boolean): Boolean {
|
||||
val i = commandline.indexOf(' ')
|
||||
val mainpath = commandline.substring(1, if (i == -1) commandline.length else i) //Without the slash
|
||||
var pcmd: PluginCommand
|
||||
return if ((!checkPlugin
|
||||
|| MainPlugin.Instance.prioritizeCustomCommands.get()) || Bukkit.getPluginCommand(mainpath).also { pcmd = it } == null //Our commands aren't PluginCommands
|
||||
|| pcmd.plugin is ButtonPlugin) //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))
|
||||
|| Bukkit.getPluginCommand(mainpath)?.let { it.plugin is ButtonPlugin } != 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 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 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(
|
||||
name: String,
|
||||
|
@ -48,11 +51,13 @@ class CoreCommandBuilder<S : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcom
|
|||
argumentsInOrder: List<CommandArgument>,
|
||||
command: TC,
|
||||
helpTextGetter: (Any) -> Array<String>,
|
||||
hasPermission: (S) -> Boolean
|
||||
hasPermission: (S, SubcommandData<TC, S>) -> Boolean,
|
||||
annotations: Array<Annotation>,
|
||||
fullPath: String
|
||||
): CoreCommandBuilder<S, TC, SubcommandData<TC, S>> {
|
||||
return CoreCommandBuilder(
|
||||
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 limits: Pair<Double, Double>,
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
val annotations: Array<Annotation>
|
||||
val annotations: Array<Annotation>,
|
||||
/**
|
||||
* The full command path of this subcommand.
|
||||
*/
|
||||
val fullPath: String
|
||||
) : NoOpSubcommandData(helpTextGetter) {
|
||||
|
||||
/**
|
||||
|
@ -55,6 +59,6 @@ class SubcommandData<TC : ICommand2<*>, TP : Command2Sender>(
|
|||
* @return Whether the user has permission
|
||||
*/
|
||||
fun hasPermission(sender: TP): Boolean {
|
||||
return permissionCheck(sender)
|
||||
return permissionCheck(sender, this)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue