This page will explain how you can create and resolve custom parameter types
One of the core features of Lamp is the ability to use custom parameter types for commands. This allows us to use types with specific meanings, restrict values to certain options, or provide customized tab completions.
We will illustrate this with a simple Quests plugin.
Creating a Quest type
Let's create a Quest class that contains all relevant data for a single Quest. Because this is slightly irrelevant to our end goal, we will use a relatively simple implementation.
We will create a class that handles, stores, and retrieves all Quest objects. Let's call it QuestManager. It will contain basic functionality for creating, updating, querying and deleting quests
We will create a single instance of this QuestManager in our main class.
public final class QuestsPlugin extends JavaPlugin {+ private final QuestManager questManager = new QuestManager();}
class QuestsPlugin : JavaPlugin() {+ private val questManager = QuestManager()}
Now, let's tell Lamp how to resolve a Quest parameter.
To create custom parameter types, we must implement the ParameterType interface. This interface describes how parameters are resolved and what suggestions they receive by default.
Creating a QuestParameterType
publicfinalclassQuestParameterTypeimplementsParameterType<BukkitCommandActor,Quest> { @OverridepublicQuestparse(@NotNullMutableStringStream input, @NotNullExecutionContext<BukkitCommandActor> context) {/* Resolve a Quest here */ }}
classQuestParameterType : ParameterType<BukkitCommandActor, Quest> {overridefunparse(input: MutableStringStream, context: ExecutionContext<BukkitCommandActor>): Quest {/* Resolve a Quest here */ }}
You may have noticed that ParameterType contains generics. In fact, it requires that you define two generics when you use it:
A: A subclass of CommandActor that the ParameterType can work with. If we are creating a general ParameterType that works with any platform, we can have this as the CommandActor interface. If we, however, need a ParameterType that only works with Bukkit, for example, this would be BukkitCommandActor or any of its subclasses.
In other words, what is the most general CommandActor implementation we can work with?
T: The type of object we need to resolve. In this case, it is a Quest type. This is used by #parse(...) to dictate what types we are expected to return.
We would like to resolve our objects from our QuestManager. For this, let's create a constructor that receives a QuestManager:
Let's create a simple implementation of our parse function:
@OverridepublicQuestparse(@NotNullMutableStringStream input, @NotNullExecutionContext<BukkitCommandActor> context) {String name =input.readString();Quest quest =questManager.quest(name);if (quest ==null)thrownewCommandErrorException("No such quest: "+ name);return quest;}
overridefunparse(input: MutableStringStream, context: ExecutionContext<BukkitCommandActor>): Quest {val name = input.readString()val quest = questManager.quest(name) ?: throwCommandErrorException("No such quest: $name")return quest}
Let's break down what we are doing here:
input.readString(): This consumes a string from a MutableStringStream. You can think of MutableStringStream as a String that is tracked by a cursor that moves along that string. When we read something from it, we move the cursor forward and receive the value that the cursor passed over. readString() will consume an entire string token. This means that if the user gives a value enclosed by double-quotations, for example, "Hello world", this will return Hello world. Otherwise, this will consume the next string until it finds a space.
A ParameterType is free to consume as much of a MutableStringStream as it needs. The only requirement is that if it consumes a token, it must consume it entirely. It cannot consume part of a string, for example.
throw new CommandErrorException("No such quest: " + name): This will signal that the command execution failed, and tell the user that they supplied an invalid quest.
Adding tab completion to our Quest type
A nicety in ParameterType is that it allows us to define custom suggestions for our quest type. These can be supplied using the defaultSuggestions method:
That's it! We can now use Quest in our commands to our heart's delight.
💡 You may have noticed that the builder provides addXXX and addXXXLast. Why the two variants?
When you use the addXXXLast variant, you are essentially giving your ParameterType less priority over others. When two ParameterTypes, one registered with addXXX while the other with addXXXLast conflict, the addXXX one will be used.
Using addXXXLast is very useful if you want to leave area for later overriding. Lamp uses it under the hood to provide all default ParameterTypes, which means you can override any of the default ones easily.
Creating our QuestCommands class
Let's create a simple QuestCommands class:
publicclassQuestCommands {}
And register it:
lamp.register(newQuestCommands());
classQuestCommands
And register it:
lamp.register(QuestCommands());
We need to access this QuestManager from our command class. How are we going to do this?
We have multiple solutions:
Pass it to the constructor: It is the traditional Java way of doing things. It involves no magic and no overhead. It, however, creates a tightly-coupled class that
Create a Lamp dependency: This is the way Lamp encourages. It is a simple form of dependency injection that allows us to create loosely coupled code. In simpler terms, it says "I want a QuestManager. I don't care where this QuestManager comes from. I just want it"
We will go with the second way. It will keep our code clean and easy for future refactoring. Dependency injection also comes with many benefits, and this is not the place to discuss them. You can read up on the topic for more details.
To create a dependency, we must register it in our Lamp instance as follows:
var lamp =BukkitLamp.builder(this)// ....dependency(QuestManager.class, questManager)// ....build();
val lamp = BukkitLamp.builder(this)// ... .dependency(QuestManager::class.java, questManager)// ... .build()
ParameterType.Factory allows you to dynamically create ParameterType instances based on the type of parameter and annotations. This is particularly useful for complex parameter parsing scenarios. Below is an example demonstrating how to create a custom factory for handling enum types.
Example: Enum Parameter Type Factory
This example shows how to implement a ParameterType.Factory that handles enum types. The factory converts a string input into an enum constant and provides suggestions based on enum names.