Command System
Myotus uses an annotation-driven command pipeline in 1.20.1. The system is intentionally small, but it is fully source-backed and already powers /myotus mods.
Registration flow
Section titled “Registration flow”Command registration starts in init/MyotusCommand on RegisterCommandsEvent.
At startup, Myotus:
- scans mod file annotation metadata for classes marked with
@MyoCommand - loads those classes by reflection
- selects root nodes where
parent() == void.class - recursively attaches subcommands whose
parent()points at the current command class
This means command discovery is not hardcoded to a manual list. If the annotation is present and the class is loadable, it can be picked up automatically.
Core annotations
Section titled “Core annotations”@MyoCommand
Section titled “@MyoCommand”Marks a class as a command node.
@MyoCommand("myotus")public final class RootCommand {}
@MyoCommand(value = "mods", parent = RootCommand.class)public final class ModsCommand {}valueis the literal Brigadier node nameparentcontrols where the node is attachedvoid.classmeans the node is a root command
@MyoExecute
Section titled “@MyoExecute”Marks the static method to run when the node executes.
@MyoExecutepublic static int execute(CommandSourceStack source) { return 1;}Important rules from CommandRegistrar:
- the method must be
static - the return type is expected to be
int - execution failures are logged and return
0
@MyoArgument
Section titled “@MyoArgument”Marks a method parameter that should become a Brigadier argument.
@MyoExecutepublic static int execute(CommandSourceStack source, @MyoArgument("enabled") boolean enabled) { return 1;}Parameters without @MyoArgument are only accepted when they are one of the context types:
CommandContextCommandSourceStack
Supported parameter types
Section titled “Supported parameter types”The current registrar maps only a small set of Java types to Brigadier argument types:
| Java type | Brigadier argument |
|---|---|
int / Integer | IntegerArgumentType.integer() |
boolean / Boolean | BoolArgumentType.bool() |
String | StringArgumentType.string() |
ServerPlayer | EntityArgument.player() |
Entity | EntityArgument.entity() |
If you use any other parameter type, registration logs an error and the node does not get a valid argument mapping.
Subcommand layout
Section titled “Subcommand layout”Myotus supports two subcommand patterns:
- nested command classes declared inside another command class
- separate top-level classes whose
parentpoints at the desired parent node
CommandRegistrar checks both patterns, so you can choose whichever layout is easier to maintain.
Minimal example
Section titled “Minimal example”@MyoCommand("myotus")public final class RootCommand {}
@MyoCommand(value = "set-flag", parent = RootCommand.class)public final class SetFlagCommand { @MyoExecute public static int execute(CommandSourceStack source, @MyoArgument("enabled") boolean enabled) { source.sendSuccess(() -> Component.literal("enabled=" + enabled), false); return 1; }}That pattern matches how the built-in /myotus mods command is authored.
Practical limitations
Section titled “Practical limitations”- There is no automatic support for optional arguments.
Stringparameters use plainstring()parsing, not a greedy string variant.- Validation errors are runtime-logged, not compile-time enforced.
- Discovery is annotation-based, so forgetting
@MyoCommandmeans the class is invisible to the registrar.