@@ -4,6 +4,7 @@ import org.bukkit.Bukkit
44import org.bukkit.command.Command
55import org.bukkit.command.CommandMap
66import org.bukkit.command.CommandSender
7+ import org.bukkit.command.TabCompleter
78import org.bukkit.plugin.java.JavaPlugin
89
910/* *
@@ -13,7 +14,7 @@ import org.bukkit.plugin.java.JavaPlugin
1314 * Commands are split into two categories based on [PluginCommand.isMainCommand]:
1415 *
1516 * * **Sub-commands** (`isMainCommand = false`, the default) – registered under
16- * the `/exampleplugin` parent command (alias `/ep`). A command with
17+ * the `/exampleplugin` parent command (alias `/ep`). A command with
1718 * `name = "reload"` becomes `/exampleplugin reload`.
1819 * * **Main commands** (`isMainCommand = true`) – registered directly on the
1920 * server [CommandMap] as standalone top-level commands.
@@ -24,6 +25,7 @@ import org.bukkit.plugin.java.JavaPlugin
2425object CommandRegistrar {
2526
2627 private const val COMMANDS_PACKAGE = " com.example.exampleplugin.commands"
28+ private const val ROOT_COMMAND = " exampleplugin"
2729
2830 /* * All registered sub-commands, keyed by their name (lower-case). */
2931 private val subCommands = mutableMapOf<String , PluginCommand >()
@@ -62,6 +64,9 @@ object CommandRegistrar {
6264 * standalone main command.
6365 */
6466 fun registerAll (plugin : JavaPlugin ) {
67+ subCommands.clear()
68+ allCommands.clear()
69+
6570 val commandClasses = PackageScanner .findClasses(
6671 plugin, COMMANDS_PACKAGE , PluginCommand ::class .java
6772 )
@@ -74,16 +79,18 @@ object CommandRegistrar {
7479 try {
7580 val command = instantiate(commandClass, plugin)
7681 val category = extractCategory(commandClass)
82+
7783 if (command.isMainCommand) {
7884 val bukkitCommand = createBukkitCommand(command)
7985 commandMap.register(plugin.name.lowercase(), bukkitCommand)
8086 plugin.logger.info(" Registered main command: /${command.name} " )
8187 mainCount++
8288 } else {
8389 subCommands[command.name.lowercase()] = command
84- plugin.logger.info(" Registered sub-command: /exampleplugin ${command.name} " )
90+ plugin.logger.info(" Registered sub-command: /$ROOT_COMMAND ${command.name} " )
8591 subCount++
8692 }
93+
8794 allCommands.add(RegisteredCommandInfo (command, category, ! command.isMainCommand))
8895 } catch (e: Exception ) {
8996 plugin.logger.severe(
@@ -92,9 +99,19 @@ object CommandRegistrar {
9299 }
93100 }
94101
95- // Register the /exampleplugin parent command (alias /ep)
96- val parentCommand = createParentCommand(plugin)
97- commandMap.register(plugin.name.lowercase(), parentCommand)
102+ val parentCommand = plugin.getCommand(ROOT_COMMAND )
103+ if (parentCommand == null ) {
104+ plugin.logger.severe(" Command '$ROOT_COMMAND ' is missing from plugin.yml" )
105+ return
106+ }
107+
108+ parentCommand.setExecutor { sender, _, _, args ->
109+ executeParentCommand(sender, args)
110+ }
111+
112+ parentCommand.tabCompleter = TabCompleter { sender, _, _, args ->
113+ tabCompleteParentCommand(sender, args)
114+ }
98115
99116 plugin.logger.info(
100117 " Registered $subCount sub-command(s) and $mainCount main command(s)"
@@ -121,89 +138,68 @@ object CommandRegistrar {
121138 } catch (_: NoSuchMethodException ) {
122139 throw IllegalArgumentException (
123140 " ${clazz.simpleName} must declare either a no-arg constructor " +
124- " or a constructor accepting a single JavaPlugin parameter"
141+ " or a constructor accepting a single JavaPlugin parameter"
125142 )
126143 }
127144 }
128145 }
129146
130- /* *
131- * Creates the `/exampleplugin` parent command that dispatches to sub-commands
132- * and provides tab-completion.
133- */
134- private fun createParentCommand (plugin : JavaPlugin ): Command {
135- return object : Command (
136- " exampleplugin" ,
137- " Main command for the ExamplePlugin plugin" ,
138- " /exampleplugin <subcommand> [args]" ,
139- listOf (" ep" )
140- ) {
141- override fun execute (
142- sender : CommandSender ,
143- commandLabel : String ,
144- args : Array <out String >
145- ): Boolean {
146- if (args.isEmpty()) {
147- sender.sendMessage(" Usage: /exampleplugin <subcommand>" )
148- sender.sendMessage(
149- " Available sub-commands: ${subCommands.keys.sorted().joinToString(" , " )} "
150- )
151- return true
152- }
153-
154- val subName = args[0 ].lowercase()
155- val subCommand = subCommands[subName]
156- if (subCommand == null ) {
157- sender.sendMessage(" Unknown sub-command: ${args[0 ]} " )
158- sender.sendMessage(
159- " Available sub-commands: ${subCommands.keys.sorted().joinToString(" , " )} "
160- )
161- return true
162- }
147+ private fun executeParentCommand (sender : CommandSender , args : Array <out String >): Boolean {
148+ if (args.isEmpty()) {
149+ sender.sendMessage(" Usage: /$ROOT_COMMAND <subcommand>" )
150+ sender.sendMessage(
151+ " Available sub-commands: ${subCommands.keys.sorted().joinToString(" , " )} "
152+ )
153+ return true
154+ }
163155
164- // Check permission
165- subCommand.permission?.let { perm ->
166- if (! sender.hasPermission(perm)) {
167- sender.sendMessage(" You do not have permission to use this command." )
168- return true
169- }
170- }
156+ val subName = args[0 ].lowercase()
157+ val subCommand = subCommands[subName]
158+ if (subCommand == null ) {
159+ sender.sendMessage(" Unknown sub-command: ${args[0 ]} " )
160+ sender.sendMessage(
161+ " Available sub-commands: ${subCommands.keys.sorted().joinToString(" , " )} "
162+ )
163+ return true
164+ }
171165
172- val subArgs = args.drop(1 ).toTypedArray()
173- return subCommand.execute(sender, subArgs)
166+ subCommand.permission?.let { perm ->
167+ if (! sender.hasPermission(perm)) {
168+ sender.sendMessage(" You do not have permission to use this command." )
169+ return true
174170 }
171+ }
175172
176- override fun tabComplete (
177- sender : CommandSender ,
178- alias : String ,
179- args : Array <out String >
180- ): List <String > {
181- if (args.size == 1 ) {
182- // Complete the sub-command name
183- return subCommands.keys
184- .filter { it.startsWith(args[0 ].lowercase()) }
185- .sorted()
186- }
187- if (args.size >= 2 ) {
188- // Delegate to the sub-command's tab completion
189- val subName = args[0 ].lowercase()
190- val subCommand = subCommands[subName] ? : return emptyList()
191- val subArgs = args.drop(1 ).toTypedArray()
192- return subCommand.tabComplete(sender, subArgs)
193- }
194- return emptyList()
195- }
173+ val subArgs = args.drop(1 ).toTypedArray()
174+ return subCommand.execute(sender, subArgs)
175+ }
176+
177+ private fun tabCompleteParentCommand (
178+ sender : CommandSender ,
179+ args : Array <out String >
180+ ): List <String > {
181+ if (args.size == 1 ) {
182+ return subCommands.keys
183+ .filter { it.startsWith(args[0 ].lowercase()) }
184+ .sorted()
196185 }
186+
187+ if (args.size >= 2 ) {
188+ val subName = args[0 ].lowercase()
189+ val subCommand = subCommands[subName] ? : return emptyList()
190+ val subArgs = args.drop(1 ).toTypedArray()
191+ return subCommand.tabComplete(sender, subArgs)
192+ }
193+
194+ return emptyList()
197195 }
198196
199197 /* *
200198 * Derives a display-friendly category name from the command class's package.
201199 *
202200 * Commands directly inside the `commands` package are categorised as
203201 * **"General"**; commands in a subpackage use the first subpackage
204- * segment, split on camelCase boundaries into title-cased words (e.g.
205- * `commands.game.StartCommand` → `"Game"`,
206- * `commands.roleManagement.AssignCommand` → `"Role Management"`).
202+ * segment, split on camelCase boundaries into title-cased words.
207203 */
208204 private fun extractCategory (clazz : Class <* >): String {
209205 val packageName = clazz.name.substringBeforeLast(' .' , " " )
@@ -218,8 +214,6 @@ object CommandRegistrar {
218214
219215 /* *
220216 * Converts a camelCase string into space-separated, title-cased words.
221- *
222- * Examples: `"game"` → `"Game"`, `"roleManagement"` → `"Role Management"`.
223217 */
224218 private fun formatCamelCase (input : String ): String {
225219 return input
0 commit comments