Creating Custom ArgumentTypes - Pante/chimera GitHub Wiki
Before creating a custom ArgumentType
, check if the type is available in either Chimera or Brigadier.
WARNING: It is recommended to use
WordType.word()
instead ofStringArgumentType.word()
, andReaders.unquoted(StringReader)
instead ofStringReader.readUnquoted()
. See the FAQ for more information.
A custom ArgumentType
can suggest completions and parse a string into a type. Due to how the client processes ArgumentType
s, injecting a custom ArgumentType
that is not native to NMS and Brigadier will cause the client to disconnect. Hence, a custom type must be mapped to a native type that is recognizable by the client. However, this limits the potential of custom types.
Type<T>
is a Chimera exclusive subinterface of ArgumentType
. Custom types that implement Type<T>
will be automatically mapped to the native type returned by Type.mapped()
.
For convenience, Type<T>
s that map to commonly used native types are provided:
Type | Native Type | Description |
---|---|---|
PointType |
ArgumentVec2 /ArgumnetVec3 (NMS) |
An unquoted 2D/3D Cartesian string |
VectorType |
ArgumentVec2 /ArgumnetVec3 (NMS) |
An unquoted 2/D3D Cartesian string |
StringType |
StringArgumentType |
An optionally quotable string |
WordType |
StringArgumentType |
A single word with no spaces |
Full list of custom types Full list of native Brigadier types
In this example we will create a custom type that parses a player's name to a associated rank. For brevity, we pretend that the player's name is already mapped to a rank.
We implement WordType
since a player's name cannot contain spaces. A trie is used to improve look-up performance when listing suggestions later. Chimera provides the trie implementation that we use.
import com.karuslabs.commons.command.types.WordType;
import com.karuslabs.commons.util.collections.Trie;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.concurrent.CompletableFuture;
public class RankType implements WordType<Integer> {
private Trie<Integer> ranks = new Trie<>();
public RankType() {
// Stub population
ranks.put("Bob", 1); ranks.put("Bobby", 2); ranks.put("Ruth", 3);
}
@Override
public Integer parse(StringReader reader) throws CommandSyntaxException {
// Covered later
}
@Override
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
// Covered later
}
}
First we declare a CommandSyntaxException
factory. It is used to create exceptions when the given input cannot be parsed.
import com.mojang.brigadier.LiteralMessage;
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
private static final DynamicCommandExceptionType EXCEPTION = new DynamicCommandExceptionType(name -> new LiteralMessage("Unknown name: " + name));
The RankType.parse(StringReader)
implementation is trivial. The rank of the player is returned if the player's name is in ranks
, otherwise the exception factory is used to throw an exception.
@Override
public Integer parse(StringReader reader) throws CommandSyntaxException {
String name = reader.getRemaining();
Integer rank = ranks.get(name);
if (rank == null) {
throw EXCEPTION.createWithContext(reader, name);
}
return rank;
}
The RankType.parse(CommandContext<S>, SuggestionsBuilder)
implementation is also trivial. We search ranks
for names that start with the given remaining input and suggest the names that start with it.
@Override
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
String incomplete = builder.getRemaining();
Set<String> matches = ranks.prefixedKeys(incomplete);
for (var match : matches) {
builder.suggest(match);
}
return builder.buildFuture();
}
After finishing this guide, RankType
should look something like this:
import com.karuslabs.commons.command.types.WordType;
import com.karuslabs.commons.util.collections.Trie;
import com.mojang.brigadier.LiteralMessage;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
public class RankType implements WordType<Integer> {
private static final DynamicCommandExceptionType EXCEPTION = new DynamicCommandExceptionType(name -> new LiteralMessage("Unknown name: " + name));
private Trie<Integer> ranks = new Trie<>();
public RankType() {
// Stub population
ranks.put("Bob", 1); ranks.put("Bobby", 2); ranks.put("Ruth", 3);
}
@Override
public Integer parse(StringReader reader) throws CommandSyntaxException {
String name = reader.getRemaining();
Integer rank = ranks.get(name);
if (rank == null) {
throw EXCEPTION.createWithContext(reader, name);
}
return rank;
}
@Override
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
String incomplete = builder.getRemaining();
Set<String> matches = ranks.prefixedKeys(incomplete);
for (var match : matches) {
builder.suggest(match);
}
return builder.buildFuture();
}
}
To use the custom type, we must apply it on an Argument
import com.karuslabs.commons.command.tree.nodes.Argument;
Argument<CommandSender, Integer> rank = Argument.of("rank", new RankType()).build();