Skip to content

Commit e83d02e

Browse files
committed
Fix: Improve subcommand registration
1 parent 129ef5c commit e83d02e

1 file changed

Lines changed: 67 additions & 73 deletions

File tree

src/main/kotlin/com/example/exampleplugin/registration/CommandRegistrar.kt

Lines changed: 67 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import org.bukkit.Bukkit
44
import org.bukkit.command.Command
55
import org.bukkit.command.CommandMap
66
import org.bukkit.command.CommandSender
7+
import org.bukkit.command.TabCompleter
78
import 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
2425
object 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

Comments
 (0)