Tutorial: Using LEAF - auriium/AUtils GitHub Wiki
In the normal bukkit/spigot api commands are a fucking pain in the ass. Adding commands requires you to add to the plugin.yml and work with annoying arrays of Strings. Adding tabComplete is a nightmare and everything sucks.
That's why Leaf exists.
Leaf handles arguments, passes actual objects down it's chain, works performant, and handles tabcomplete and registration off the bat without even touching the plugin.yml. Leaf also has easy cross version support, so building commands with tabcomplete in bungeecord is about as simple as switching from a SpigotPlugin to BungeePlugin - the ACommandManager will handle the rest!
This tutorial will explain how to use LEAF assuming you already read the main tutorial and know how to use GUICE.
First, wherever you want to register your commands (usually your main class) you'll want to tell GUICE to inject an ACommandManager interface into your class.
@Inject private ACommandManager commandManager
To register commands into this commandManager is astoundingly simple. Call the method ACommandManager#registerCommand(ACommand cum)
commandManager.registerCommand(command)
A typical usage of this is in the onEnable method.
@Inject ACommandManager commandManager;
@Inject CommandTest lah;
public void onEnable() {
commandManager.registerCommand(lah);
}
You will need an instance of your command class to use this, so let's discuss that next.
In spigot, you typically create a command by extending CommandExecutor.
In leaf, you extend AMapExecutor.
This interface is just another interface extending more interfaces: Translation - If you don't want to use LEAF's command mapping system, you can always register a command using a lower level interface like ACommandExecutor if you want to write specific implementation while still maintaining. cross-version suport.
Let's take a look at an AMapExecutor once you write one, and what to do with it.
-
The first method, onCommandMap will need to return a NewLeafMap. This will be explained in the next section as it is the most important aspect of Leaf.
-
The next method, onIncorrectPerms will handle what happens if someone without the correct permissions to run this command attempts to run it. This permission is derived from the method getPermission.
-
onIncorrectExecutor is ran when the executor of the command is the console. I cannot think of any reason this method is necessary but I don't read the code I write so what do I know.
-
getName is the name of the command as it's typed out. If you want your command to start /helpMeDearGod, let this return "helpMeDearGod"
-
getAliases returns all potential aliases to use. If you want an alias to /helpMeDearGod to be /hmdg, return List.of("hmdg"). Otherwise, return an empty List implementation.
-
getPermission is the permission you need to A. be able to execute this command and B. be able to run this command. By default LEAF framework hides commands that you do not have permission to execute from you. This will be togglable in a later version or if enough people ping me on discord.
-
getUsage must return the default spigot usage.
-
getDescription should return the default spigot description.
Here is an example of a command class that extends AMapExecutor.
public class APointTest extends AMapExecutor {
private NewLeafMap map = new NewLeafMap.Builder(getName()).basePoint(builder -> {
return builder.setHandler((s,a) -> {
s.sendMessage("CUM GOD");
});
}).hasNoArgs(true).build();
@Override
public NewLeafMap onCommandMap() {
return map;
}
@Override
public void onIncorrectPermission(ASenderWrapper sender) {
sender.sendMessage("perms");
}
@Override
public void onIncorrectExecutor(ASenderWrapper sender) {
sender.sendMessage("executor");
}
@Override
public String getName() {
return "cum";
}
@Override
public List<String> getAliases() {
return new ArrayList<>();
}
@Override
public String getPermission() {
return "elytraforce.cool";
}
@Override
public String getUsage() {
return "balh";
}
@Override
public String getDescription() {
return "balh";
}
}
While all this is all well and good, the real shiny bit of LEAF framework is the modular LEAF system.
To register your branches of commands, LEAF uses 3 different types of Leafs: PointLeaf, SplitLeaf, ValueLeaf.
These are built into a MewLeafMap.
This is what your map executor must return, and is the base of your branching commands.
Register a leaf into it with NewLeafMap#basePoint, NewLeafMap#baseSplit, or NewLeafMap#baseValue.
A newLeaf with a basePoint does one thing with no regard for arguments. (Think /fly, it has no args and toggles flight.) A newLeaf with a baseSplit splits into multiple tabCompleted arguments. Using the branching power of SplitLeaf, you can either add point/value leafs to end the branch, or expand it with more splitLeaves. (Think /kitpvp, with subcommands /kitpvp options and /kitpvp kits). A newLeaf with a baseValue handles logic with ARGUMENTS. This will be explained more lately. (Think /givekit %name% where name is an argument.)
"this is still so confusing for me what the fuck does any of that mean"
Think of Trees. the newleafmap is the base of the tree. a splitleaf is like a branch - it splits that base into two or more. a pointleaf or valueleaf is the endpoint of a branch.
- PointLeaf
/
- PointLeaf - SplitLeaf -
/ / \
NewLeafMap - - - - - - - -SplitLeaf - - PointLeaf
\
PointLeaf
A pointLeaf is the most simple of leaf. When arguments are passed to the command that cause this leaf to trigger, it simply runs the provided handler.
If you want a command that does one thing no matter the arguments, build a NewLeafMap with a single pointLeaf.
private NewLeafMap map = new NewLeafMap.Builder(getName()).basePoint(builder -> {
return builder.setHandler((s,a) -> {
s.sendMessage("CUM GOD");
});
}).hasNoArgs(true).build();
The command that runs this map will do one thing, and that's send the message "CUM GOD" to the sender of the command.
Alternatively you can append this to a SplitLeaf.
This leaf is able to branch into multiple more leafs. You can also attach more SplitLeaves to other SplitLeaves making giant fucking messes branching commands.
private NewLeafMap newMap = new NewLeafMap.Builder(getName())
.baseSplit(builder -> {
return builder
.point("args0hello",builder1 -> {
return builder1.setHandler((player,args) -> {
player.sendMessage("args0 command!");
});
})
.split("args0split",builder1 -> {
return builder1.pointDefaultArgs("first",builder2 -> {
return builder2.setHandler((sender,args) -> {
sender.sendMessage("first command!");
});
}).point("second",builder2 -> {
return builder2.setHandler((sender,args) -> {
sender.sendMessage("second command!");
});
}).value("value",builder2 -> {
return builder2.argument(new StringArgument("cum_type").withDefault("blue"))
.setHandler((sender,args) -> {
sender.sendMessage("You have the nice color of " + args.getString("cum_type"));
});
});
})
}).build();
This leafMap has a base of a SplitMap, and branches into either
- a pointLeaf of args0hello or
- another split of args0split, which branches into 2 point args of "first" and "second" as well as a value arg of "value"
This command's full tree looks like
/commandname args0hello /commandname args0split first /commandname args0split second /commandname args0split value %CUM_TYPE%
If you want a default leaf for your SplitLeaf (the leaf used if no arguments are provided) return a builder with defaultArgs in the build method name instead of a normal builder.
This leaf allows you to run a command with multiple or less arguments from the position that it is at, while still working with objects and not strings. Arguments must be provided to the ValueLeaf.
private NewLeafMap map = new NewLeafMap.Builder(getName())
.hasNoArgs(true)
.baseValue(builder -> {
return builder
.argument(new EnumArgument("bool",set).withDefault(Toggle.DEFAULT))
.setHandler((sender,args) -> {
switch ((Toggle) args.getEnum("bool")) {
case ON:
sender.sendMessage("ON");
break;
case OFF:
sender.sendMessage("OFF");
break;
case DEFAULT:
sender.sendMessage("DEFAULT");
break;
}
})
.pointWrongArgs(builder1 -> builder1.setHandler((sender,args) -> {
sender.sendMessage(String.join(" ", args));
}));
}).build();
This command's first argument is an EnumArgument (which provides from a list of enums) that has a default of enumerator Toggle's Toggle.Default. Arguments are input sensitive. If an EnumArgument is added and then a IntegerArgument, the positioning of the command would look like
/commandname EnumInput IntegerInput
All "Arguments" must be provided with a stringly-typed identifier. This EnumArgument's identifier is the retardedly named "bool".
This command will check if the command looks like
/commandname OFF /commandname ON /commandname DEFAULT
by retrieving the previous stringly typed identifier of "bool" from the Arguments object (notated as "args") and then checking if it matches.
Next, this NewLeafMap has a pointWrongArgs, which means that if the argument provided is not an enum from the provided list of enums in TOGGLE, instead of failing and throwing an exception, the logic in the PointLeaf under method pointWrongArgs is called, sending a message.
Let's say this leaf map shit is too complicated for you understandedble and you just want to have a command that can
- be non-version specific (spigot or bungee!)
- handle permissions and command hiding
- handle incorrect executor
- handle tabcomplete without more fucking interfaces
- register itself in the CommandManager without touching the spigot.yml
Easy! Just extend the abstract class of ACommandExecutor instead of AMapExecutor. AMapExecutor is just a high level extension of ACommandExecutor, while extending ACommandExecutor lets you interact with the command logic without having everything handled for you.
If even ACommandExecutor is too hands-on for you, you can go to the base level of ACommand. This is an interface, and you will be expected to implement all code yourself.