diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.kt index 35c58c9..40cb941 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.kt @@ -5,7 +5,6 @@ import buttondevteam.core.ComponentManager import buttondevteam.lib.TBMCCoreAPI import buttondevteam.lib.architecture.exceptions.UnregisteredComponentException import buttondevteam.lib.chat.ICommand2MC -import lombok.Getter import org.bukkit.configuration.ConfigurationSection import org.bukkit.event.Listener import org.bukkit.plugin.java.JavaPlugin @@ -24,7 +23,6 @@ abstract class Component { val config = IHaveConfig(null) - @Getter private val data //TODO : IHaveConfig? = null diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt index 0bfa5d8..b5ee6a4 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt @@ -2,7 +2,6 @@ package buttondevteam.lib.architecture import buttondevteam.core.MainPlugin import buttondevteam.lib.ChromaUtils -import lombok.* import org.bukkit.Bukkit import org.bukkit.configuration.Configuration import org.bukkit.scheduler.BukkitTask diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt index 9a7a514..4d0e381 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt @@ -12,7 +12,6 @@ import com.mojang.brigadier.context.CommandContext import com.mojang.brigadier.exceptions.CommandSyntaxException import com.mojang.brigadier.tree.CommandNode import com.mojang.brigadier.tree.LiteralCommandNode -import lombok.RequiredArgsConstructor import org.bukkit.Bukkit import java.lang.reflect.Method import java.util.function.Function @@ -24,8 +23,17 @@ import java.util.stream.Collectors * The method name is the subcommand, use underlines (_) to add further subcommands. * The args may be null if the conversion failed and it's optional. */ -@RequiredArgsConstructor -abstract class Command2, TP : Command2Sender> { +abstract class Command2, TP : Command2Sender>( + /** + * The first character in the command line that shows that it's a command. + */ + private val commandChar: Char, + + /** + * Whether the command's actual code has to be run on the primary thread. + */ + private val runOnPrimaryThread: Boolean +) { /** * Parameters annotated with this receive all the remaining arguments */ @@ -44,16 +52,10 @@ abstract class Command2, TP : Command2Sender> { */ val helpText: Array = [], /** - * The main permission which allows using this command (individual access can be still revoked with "chroma.command.X"). - * Used to be "tbmc.admin". The [.MOD_GROUP] is provided to use with this. + * Aliases for the subcommand that can be used to invoke it in addition to the method name. */ - val permGroup: String = "", val aliases: Array = []) { - companion object { - /** - * Allowed for OPs only by default - */ - const val MOD_GROUP = "mod" - } + val aliases: Array = [] // TODO + ) { } @Target(AnnotationTarget.VALUE_PARAMETER) @@ -66,16 +68,6 @@ abstract class Command2, TP : Command2Sender> { private val commandHelp = ArrayList() //Mainly needed by Discord private val dispatcher = CommandDispatcher() - /** - * The first character in the command line that shows that it's a command. - */ - private val commandChar = 0.toChar() - - /** - * Whether the command's actual code has to be run on the primary thread. - */ - private val runOnPrimaryThread = false - /** * Adds a param converter that obtains a specific object from a string parameter. * The converter may return null. @@ -96,13 +88,17 @@ abstract class Command2, TP : Command2Sender> { return false // Unknown command } //Needed because permission checking may load the (perhaps offline) sender's file which is disallowed on the main thread - Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance) { + Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance) { _ -> try { dispatcher.execute(results) } catch (e: CommandSyntaxException) { sender.sendMessage(e.message) } catch (e: Exception) { - TBMCCoreAPI.SendException("Command execution failed for sender " + sender.name + "(" + sender.javaClass.canonicalName + ") and message " + commandline, e, MainPlugin.Instance) + TBMCCoreAPI.SendException( + "Command execution failed for sender " + sender.name + "(" + sender.javaClass.canonicalName + ") and message " + commandline, + e, + MainPlugin.Instance + ) } } return true //We found a method @@ -342,7 +338,7 @@ abstract class Command2, TP : Command2Sender> { invokeCommand.run();*/return 0 } - abstract fun hasPermission(sender: TP, command: TC, subcommand: Method?): Boolean + abstract fun hasPermission(context: CommandContext): Boolean val commandsText: Array get() = commandHelp.toTypedArray() /** @@ -393,4 +389,11 @@ abstract class Command2, TP : Command2Sender> { root.children.removeIf { node -> node.coreExecutable()?.let { condition.test(it) } ?: false } for (child in root.children) unregisterCommandIf(condition, child.core()) } + + /** + * Get all subcommands of the specified command. Only returns executable nodes. + */ + fun getSubcommands(mainCommand: LiteralCommandNode): List>> { + return dispatcher.root.children.mapNotNull { it.coreExecutable() } // TODO: Needs more depth + } } \ No newline at end of file diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt index b65fa4a..8452195 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt @@ -5,6 +5,8 @@ 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 import com.mojang.brigadier.arguments.StringArgumentType @@ -57,44 +59,52 @@ class Command2MC : Command2('/', true), Listener 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 (method in command.javaClass.methods) { - if (!method.isAnnotationPresent(Subcommand::class.java)) continue - val path = CommandUtils.getCommandPath(method.name, '.') + 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 + Bukkit.getPluginManager().addPermission( + Permission( + subperm, + PermissionDefault.TRUE + ) + ) //Allow commands by default, it will check mod-only } - val pg = permGroup(command, method) - if (pg.length == 0) continue + val pg = permGroup(node.data) + if (pg.isEmpty()) continue val permGroup = "chroma.$pg" 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 + ) + ) //Do not allow any commands that belong to a group } } - override fun hasPermission(sender: Command2MCSender, command: ICommand2MC, method: Method): Boolean { - return hasPermission(sender.sender, command, method) + override fun hasPermission(context: CommandContext): Boolean { + return hasPermission(context.source.sender, context.subcommandPath) } - fun hasPermission(sender: CommandSender, command: ICommand2MC?, method: Method): Boolean { + fun hasPermission(sender: CommandSender, path: String): Boolean { if (sender is ConsoleCommandSender) return true //Always allow the console - if (command == null) return true //Allow viewing the command - it doesn't do anything anyway var pg: String var p = true - val cmdperm = "chroma.command." + command.commandPath.replace(' ', '.') - val path = CommandUtils.getCommandPath(method.name, '.') + val cmdperm = "chroma.command.$path" + // TODO: Register a permission for the main command as well - the previous implementation relied on the way the commands were defined val perms = arrayOf( - if (path.length > 0) cmdperm + path else null, - cmdperm, + cmdperm + path, if (permGroup(command, method).also { pg = it }.length > 0) "chroma.$pg" else null ) 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, perm) else false //Use sender's method + p = if (sender is OfflinePlayer) MainPlugin.permission.playerHas( + if (sender is Player) sender.location.world.name else null, + sender as OfflinePlayer, + perm + ) else false //Use sender's method if (!p) p = sender.hasPermission(perm) } else break //If any of the permissions aren't granted then don't allow } @@ -108,14 +118,11 @@ class Command2MC : Command2('/', true), Listener * @param method The subcommand to check * @return The permission group for the subcommand or empty string */ - private fun permGroup(command: ICommand2MC, method: Method?): String { - if (method != null) { - val sc = method.getAnnotation(Subcommand::class.java) - if (sc != null && sc.permGroup().length > 0) { - return sc.permGroup() - } - } - return if (getAnnForValue(command.javaClass, CommandClass::class.java, Function { obj: CommandClass -> obj.modOnly() }, false)) Subcommand.MOD_GROUP else getAnnForValue(command.javaClass, CommandClass::class.java, Function { obj: CommandClass -> obj.permGroup() }, "") + private fun permGroup(data: SubcommandData): String { + val group = data.annotations.filterIsInstance().map { + if (it.permGroup.isEmpty() && it.modOnly) MCCommandSettings.MOD_GROUP else "" + }.firstOrNull() + return group ?: "" } /** diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandUtils.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandUtils.kt index 6b22671..1db8c68 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandUtils.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandUtils.kt @@ -3,6 +3,7 @@ package buttondevteam.lib.chat.commands import buttondevteam.lib.chat.Command2Sender import buttondevteam.lib.chat.CoreCommandNode import buttondevteam.lib.chat.ICommand2 +import com.mojang.brigadier.context.CommandContext import com.mojang.brigadier.tree.CommandNode import java.util.* @@ -34,4 +35,9 @@ object CommandUtils { val ret = core() return if (ret.data is SubcommandData<*, *>) ret.core() else null } + + val CommandContext.subcommandPath + get(): String { + TODO("Return command path") + } } \ No newline at end of file diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/MCCommandSettings.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/MCCommandSettings.kt new file mode 100644 index 0000000..609c001 --- /dev/null +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/MCCommandSettings.kt @@ -0,0 +1,25 @@ +package buttondevteam.lib.chat.commands + +import java.lang.annotation.Inherited + +@Inherited +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +annotation class MCCommandSettings( + /** + * The main permission which allows using this command (individual access can be still revoked with "chroma.command.X"). + * Used to be "tbmc.admin". The [.MOD_GROUP] is provided to use with this. + */ + val permGroup: String = "", + /** + * Whether the (sub)command is mod only. This means it requires the chroma.mod permission. + * This is just a shorthand for providing MOD_GROUP for permGroup. + */ + val modOnly: Boolean = false +) { + companion object { + /** + * Allowed for OPs only by default + */ + const val MOD_GROUP = "mod" + } +} diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/SubcommandData.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/SubcommandData.kt index 00755f8..f918706 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/SubcommandData.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/SubcommandData.kt @@ -41,7 +41,11 @@ class SubcommandData, TP : Command2Sender>( /** * A function that determines whether the user has permission to run this subcommand. */ - private val permissionCheck: (TP) -> Boolean + private val permissionCheck: (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 ) : NoOpSubcommandData(helpTextGetter) { /**