-
Notifications
You must be signed in to change notification settings - Fork 6
Ships Core Command System
The command system in Ships Core takes a page out of the Sponge API 1-7 command system in that you specify your arguments and then in the run function you can get the object you wish to use instead of needing to parse every string argument in the command.
A command argument is only part of a command. Simply only a single argument that is expected for the command.
The command argument is designed to parse the argument into a object for use on the actual command as well as provide the suggestions for the end user.
Parsing a argument is the main part of a command argument. Parsing is the idea of converting a word (or multiple words) into a data format that makes it easier for working with.
So for example, if the command expects a Integer and the user type 2
the command would see this as a String which isn't useful for mathematics. Therefore the Integer Argument converts the string into a integer automatically before the command code is executed. Therefore the command gets the integer data format when requesting this argument.
Integers may sound simple, so whats the point? Well when it comes to more advanced data points such as a array of Ships in the middle of the command then command arguments come in really handy. Plus its also a lot neater not to have all the code conversation directly in the command code.
####Example In this example we use the example of parsing a Integer.
When implementing the command argument you will get a function similar to the following.
public CommandArgumentResult<T> parse(CommandContext command, CommandArgumentContext<T> argumentContext) {
}
The return type is a CommandArgumentResult<T>
. CommandArgumentResult stores both the next position within the argument as well as the result of the argment. This can be created using the typical new CommandArgumentResult(position, result)
however to save you finding out the position every time, there are some helper methods to make your programming quicker. CommandArgumentResult.from(argumentContext, result)
which assumes that you only used one argument space within the user input. CommandArgumentResult.from(argumentContext, int plusBy, result)
is if you used more then one argument (or none) and need to specify. The ArgumentContext is that which is given to you as the 2nd parameter within the suggest as well as the parse.
For the mean time, we will keep the value at 1 as we are only handling a single word (or in this case number).
If you are unable to parse the argument then you should throw a IOException with the message provided in the Exception being a friendly way on what went wrong.
The CommandContext is all known information about the command including the results of previous command arguments and the original string variant of the command.
CommandArgumentContext is all known information about the argument you are attempting to handle.
public CommandArgumentResult<Integer> parse(CommandContext command, CommandArgumentContext<Integer> argumentContext) {
String word = context.getCommand()[argument.getFirstArgument()];
try{
return CommandArgumentResult.from(argumentContext, Integer.parseInt(word));
}catch (NumberFormatException e){
throw new IOException("'" + word + "' is not a number");
}
}
Suggestions are handled within the argument itself. This is similar to Bukkit's and Sponge's (API 7) way of handling suggestions.
You are given the same parameters as the run function, however instead of returning the value, you are required to return a list of strings.
The CommandContext even when in suggesting mode will attempt to parse the arguments within the command and therefore you can get the values of the arguments before.
Before writing your implementation of suggestions, please note that if two commands have the same arguments until your argument, the suggestions of your argument and the other argument will be combined and therefore if the other argument has lots of argument suggestions then your argument suggestions may not be shown as Minecraft client can only show a limited amount on screen at once.
public List<String> suggest(CommandContext context, CommandArgumentContext<T> argument) {
}
Note how you must return a list of strings, no exceptions can be thrown to get out of this one.
If you know that you can not suggest anything then return a Collections.emptyList()
.
In the following example, I will be using the example of a boolean state and therefore the only acceptable arguments is either true
or false
and therefore we should suggest these.
public List<String> suggest(CommandContext context, CommandArgumentContext<Boolean> argument) {
String word = argument.getFocusArgument().toLowerCase();
List<String> suggestions = new ArrayList<>();
if("true".startsWith(word)){
suggestions.add("true");
}
if("false".startsWith(word)){
suggestions.add("false");
}
return suggestions;
}
In this example if the user has not provided any text, the variable of word
would be a empty string and therefore both if statements would pass resulting in the end result being a array with both true
and false
in. However if the variable word is tr
then only the first if statement would pass, resulting in the only suggestion being true
.
Ships Core isn't mean and expects you to design multiple command arguments even for arguments such as boolean and integer. Instead its nice and provides you with them free of charge.
new IntegerArgument (String id);
The integer argument does not provide any suggestions, but simply converts a single word into a whole number.
new BooleanArgument (String id);
Simply converts a single argument into a Boolean and suggests true or false.
new EnumArgument (String id, class<T extends Enum> class);
Converts a single argument into a enum value with all entries within the enum being suggested.
new OptionalArgument<> (CommandArgument<T> argument, T defaultValue);
new OptionalArgument<> (CommandArgument<T> argument, BiFunction<CommandContext, CommandArgumentContext<T>, T> function)
By default, all arguments must be satisfied within a command for it to be executed. The Optional argument is a exception to that rule whereby if the argument is not satisfied, it instead uses the default value as its parsed value.
In this example, lets say we have a command whereby a player can have a trail for a specified time (in ticks), if no time is specified then it will default to 5 seconds (which is 100 ticks).
new OptionalArgument<> (new IntegerArgument("time"), (commandContext, commandArgumentContext) -> 100);
Here you can see that we specified a integer argument which would attempt to get a number from the command sender, however if they didn't specify a time then the lamda would be used instead. Due to the fact in this example, the "default" value (that being the 100 ticks) is fixed and doesn't require any of the provided parameters nor does it need to be created dynamicly, we can shorten this example to the following.
new OptionalArgument<> (new IntegerArgument("time"), 100);
which is much quicker to write.
new RemainingArguments (String id, CommandArgument<T>... arguments);
new RemainingArguments (String id, Collection<CommandArgument<T>> arguments);
Remaining arguments parses every single word left by using any of the provided command arguments to parse the value. The limitation is that the parsed value must be the same in all arguments.
Lets say that we have a command where the user specifies a list of words that will be banned form the server, the list of words can be specified in a single command such as /ban mose moses mosemister
We could get the list of words using a RemainingArgument like so
new RemainingArguments<> ("words", new StringArgument(""))
You may have seen that the StringArgument's ID is blank, this is because it is not used and therefore can be any valid String. Null is not allowed as the constructor of the argument is checked for null and will throw a exception if found.
Flat remaining arguments works in the same way as the regular remaining arguments only it takes arguments that result in a collection, it then combines the result of the arguments into a single list. This is so when getting the result you don't end up with a return class type of List<List<String>>>
where you then would need to loop though a loop of the results, flat remaining arguments would instead just return List<String>
.
Collected remaining arguments works in the same way as the regular remaining arguments only the results are collected using a Collector specified.
Lets say you want the user to enter in the name of a town which can have spaces within it. A String argument won't work as it can only accept one argument, however we can use a CollectedRemainingArguments combined with the StringArgument to get all the remaining arguments as strings and then have them collected into a single String.
new CollectedRemainingArgument("town name", Collectors.joining(" "), new StringArgument(""));
If you don't know, the Collectors.joining(" ")
collects the results and returns a String of the results with the provided String being the seperator. This argument is the magic that divides the normal RemainingArgument to the CollectedRemainingArgument
new MappedArgument<> (CommandArgument<J> argument, Function<J, T> mapper);
If you require an argument to return a value similar to what it actually returns then you can use the Mapped argument which simply parses and suggests using the provided argument however then maps the parsed argument to the datatype that you require. This solves the issue of all values being the same with the Remaining Argument.
When it comes to mapped arguments, this is something you would see in the code, but not on the user facing side. So this example is code based.
Lets say you want to provide a remaining argument to the user, however the arguments you want to use, one returns a long while the other returns a integer. Due to the way Java handles generics, both cannot be used within RemainingArguments at the same time, not without mapping one of the values to the other type.
Due to the fact a int can be a long but a long may not be a int (depending on size of the number), we will map the int argument to a long.
CommandArgument<Integer> integerArgument;
CommandArgument<Long> longArgument;
new RemainingArguments("id", integerArgument, longArgument); //this wont work as the generics are not the same
new RemainingArguments("id", new MappedArgument<>(integerArgument, (intValue) -> (long)intValue), longArgument); //now it will work as we have mapped the int to a long argument
new ExactArgument (String id);
new ExactArgument (String id, boolean caseSensitive, String... toMatch);
The exact argument is used to seperate out your command from another command. If any of the toMatch
values is typed as the argument then it passes (if created using the id only, this means that its not case sensitive and the to match is the id itself).
The parsed value is the string that was used to pass the argument with the suggestions being the toMatch
.
The Block Type argument has 3 different types of arguments depending on your use case.
If you just want a single block type then its very simple.
new BlockTypeArgument (String id);
However if your wanting multiple block types, then we would recommend BlockTypesArgument as this combines both the single BlockType argument (only for multiple entries) and block groups (which is below). On the inner workings of this its actually a flat remaining arguments, with both the block group and block type arguments. With block type being mapped to a singleton collection and the blockgroups being mapped from a block group to a collection of blocktypes (using the examples above)
new BlockTypesArgument (String id);
Due to the fact that minecraft removed the need for sub ids in 1.13 by providing all blocks that had sub Ids as primary IDs, such as in 1.12 the difference between Blue Wool and Red Wool was the subId, however in 1.13+ we now write them using the primary Id of BLUE_WOOL and RED_WOOL. This caused a problem whereby if you wanted to make a plugin that reacted to all wools the same, you would need to specify each wool block seperately. So they introduced Tags which grouped blocks, items, etc together. In Ships Core the grouped blocks are called BlockGroups
new BlockGroupArgument (String id)