-
Notifications
You must be signed in to change notification settings - Fork 0
v5 KamiCommand
Available in spigot-utils and its inheritors (spigot-jar).
A command framework with typed parameters, requirements and automatic help. Each command in your tree is
a class extending KamiCommand, configured in its constructor.
We acknowledge MassiveCraft's MassiveCore as the original source and inspiration for this system.
public class CmdHome extends KamiCommand {
public CmdHome() {
addAliases("home");
addParameter(Parameter.of(TypeString.get()).name("home")); // required
addParameter(Parameter.of(TypeBooleanYes.get()).name("silent")
.defaultValue(false, "no")); // optional
addRequirements(RequirementHasPerm.get("myplugin.home"));
addRequirements(RequirementIsPlayer.get());
setDesc("Teleport to one of your homes");
addChild(new CmdHomeSet());
}
@Override
public void perform(@NotNull CommandContext context) throws KamiCommonException {
Player player = Objects.requireNonNull(context.getMe());
String home = readArg();
boolean silent = readArg();
// ...
}
}Register it in your plugin's enable:
new CmdHome().registerCommand(this);
KamiCommonCommandRegistration.updateRegistrations(); // required after server startand unregister in disable with command.unregisterCommand().
Only root commands can be registered. Registering a child throws
IllegalStateException.
Everything about the invocation arrives in the perform parameter.
context.getSender() // CommandSender
context.getMe() // Player, or null when console
context.isSenderIsConsole()
context.getArgs() // raw arguments
context.getLabel() // the alias actually typedMigrating from v4:
execute(...)gained aString labelparameter and is nowfinal. Overrideperform, notexecute. TheCommandContextparameter onperform()is not new; it arrived in v4.
getLabel() is new in v5. It is used internally for the help title, so a command reached through two
aliases renders the right title under each one. The page-flip arrows build their command from aliases
rather than from the label.
Parameters are declared in order and read in order. readArg() returns the next one, already parsed to
the right type.
addParameter(Parameter.of(TypeInteger.get()).name("page").defaultValue(1));Builder methods are fluent, with no set prefix:
| method | meaning |
|---|---|
.name(String) |
shown in the usage template. Defaults to the type's name |
.defaultValue(T) |
makes the parameter optional |
.defaultValue(T, String desc) |
as above, with a description shown in help |
.requiredFromConsole(boolean) |
required when the sender is console, optional otherwise |
.concatFromHere(boolean) |
swallow all remaining arguments into this parameter |
There is no required() flag: a parameter is required exactly when it has no default value. A default
of null is legal and different from having no default.
Ordering is enforced. A required parameter after an optional one throws IllegalArgumentException, and
anything after a concatenating parameter throws IllegalStateException.
Migrating from v3: the many
addParameter(...)overloads collapsed into the builder.this.addParameter(1, TypeInteger.get(), "page")becomesaddParameter(Parameter.of(TypeInteger.get()).name("page").defaultValue(1)). v4 already had the two builder overloads, so a v4 caller needs no change.
A Type<T> turns a string argument into a Java object and supplies tab completions. These ship:
Primitives. TypeString, TypeStringConfirmation, TypeByte, TypeShort, TypeInteger,
TypeLong, TypeFloat, TypeDouble, TypeBooleanTrue (true/false), TypeBooleanOn (on/off),
TypeBooleanYes (yes/no)
Senders. TypePlayer, TypeOfflinePlayer, TypeSender
Bukkit. TypeWorld, TypeMaterial, TypeXMaterial, TypeGameMode, TypeEnchantment,
TypePotionEffectType, TypePermission, TypeColor, TypeNamespacedKey
Other. TypeRange, TypeDate, TypeTimeZone, TypeUUID
All are singletons reached with get(). TypeRange.get(min, max), TypeMaterial.get(exclusions...)
and TypeXMaterial.get(exclusions...) take arguments. TypePlayer hides vanished players.
Extend TypeAbstract<T> and implement two methods:
public class TypeArena extends TypeAbstract<Arena> {
private static final TypeArena i = new TypeArena();
public TypeArena() { super(Arena.class); }
public static TypeArena get() { return i; }
@Override
public Arena read(String str, CommandSender sender) throws KamiCommonException {
Arena arena = ArenaManager.get(str);
if (arena == null) {
throw new KamiCommonException().addMsgFromMiniMessage(
"<red>No arena named <light_purple>" + str + "<red>.");
}
return arena;
}
@Override
public Collection<String> getTabList(CommandSender sender, String arg) {
return ArenaManager.names().stream()
.filter(n -> n.toLowerCase().startsWith(arg.toLowerCase()))
.limit(20)
.collect(Collectors.toList());
}
}Throwing KamiCommonException is the correct way to reject input. The framework catches it and sends
your message to the player. Never return null.
Tab completions are prefix-filtered for you. Override shouldShowAllTabCompletions() to return true
if you have already filtered them yourself.
Migrating from v4:
KamiCommonException.addMsg(String)is deprecated. UseaddMsgFromMiniMessage(String),addMsgFromLegacyColors(String)oraddMsg(VersionedComponent).getKMessage()is nowgetComponent(). On the abstract types,extractErrorMessageis nowextractErrorMessageMini. The old name still compiles as a new method and is never called.
Requirements gate a command. All of them must pass before perform runs, and a failing one sends its
own error message.
Built in: RequirementHasPerm.get(permission), RequirementIsPlayer.get(),
RequirementIsntPlayer.get(), RequirementHasItemInHand.get(materials...).
public class RequirementInWorld extends RequirementAbstract {
private final String world;
public RequirementInWorld(String world) { this.world = world; }
public static RequirementInWorld get(String world) { return new RequirementInWorld(world); }
@Override
public boolean apply(CommandSender sender, KamiCommand command) {
return sender instanceof Player && ((Player) sender).getWorld().getName().equals(world);
}
@Override
public @NotNull VersionedComponent createErrorMessage(CommandSender sender, KamiCommand command) {
return NmsAPI.getVersionedComponentSerializer()
.fromMiniMessage("<red>You must be in " + world + ".");
}
}Add children with addChild(...). The first child you add also causes a help command to be inserted
automatically at index 0, so /home ?, /home h and /home help work without you writing anything.
A command with children and no perform override shows that help by default. Help titles come from the
alias the player actually typed. Set extra lines above it with setHelpComments(...), and control
ordering with KamiCommandHelp.Config.setSortHelpCommands(true).
Hide a command from help with setVisibility(...): VISIBLE, SECRET or INVISIBLE.
New in v5. The permission handed to Bukkit controls whether the command appears in root tab completion at all, which the old requirement check could not do.
setBukkitCommandPermission("myplugin.home");If you do not call it, the permission is derived from your requirements: the first
RequirementHasPerm in the list wins.
⚠️ With more than oneRequirementHasPermon a command, derivation picks the first by declaration order, not the most specific, and logs a warning. Reordering twoaddRequirementslines would change the Bukkit permission. Set it explicitly whenever a command has several permission requirements.
Only root commands are affected. A command with no RequirementHasPerm and no explicit call gets no
Bukkit permission.
List<VersionedComponent> lines = /* ... */;
CommandPaging.getPage(this, lines, page, "Arenas").forEach(line -> line.sendTo(sender));Page height defaults to 9 for players and 50 for console; pass an explicit height with the five-argument
overload. Call it synchronously inside perform, because it reads the live CommandContext.
Clickable next and previous arrows appear automatically if your command has a parameter literally
named "page" and every parameter before it has a default value.
Migrating from v4:
getPagemoved offTxtand the argument order changed.Txt.titleizedPageTitlestill exists, but lost itsKamiCommandandList<String>parameters and no longer renders page arrows.Txt.titleizedPageTitle(title, pageCount, pageNum, ...)is nowCommandPaging.titleizedPageTitle(command, title, pageNum, pageCount).pageNumandpageCountare transposed. The arity dropped from five to four andcommandmoved to the front, so a blind port will not compile, but a port that preserves v4's ordering will, and silently reverses your page counter.
KamiCommand.Config holds every string and colour the framework emits: command and parameter colours,
the "you should use the command like this" line, ambiguous and missing sub-command messages, and the
permission-denied message. KamiCommandHelp.Config covers the help title, CommandPaging.Config the
pagination arrows and title, and TypeAbstractChoice.Config the "no match" and "ambiguous" errors.
All values are MiniMessage. Accessors that hold MiniMessage text carry a Mini suffix, so
KamiCommand.Config is uniformly suffixed while the others are mixed. KamiCommandHelp.Config has
plain setHelpTitleFormat, and CommandPaging.Config has both setBackIcon and
setActiveIconColorMini.
KamiCommand.Config.setErrorColorMini("<dark_red>");
KamiCommandHelp.Config.setHelpTitleFormat("Commands for {TITLE}");Migrating from v4: the
Langclass was removed; these config classes replace it. Placeholders that were literal substrings are now MiniMessage tags:{REPLACEMENT}is now<replacement>, and{prevPage}/{nextPage}are now<prev_page>/<next_page>. A custom format still using the old literals renders them as visible text and loses the click actions.
Setup
Spigot
Text
Data
Migration
Other versions