Arn is an annotation-based command library inspired by Spring boot that helps Minecraft plugins with creating, handling and registering commands. This library uses Brigadier commands that Minecraft uses, so you can use most kinds of arguments you see in the commands of the original game. Plus, you can turn any enumerator to a custom argument, all it takes is two annotations.
Add this repository if you don't have it in your <repositories>
or repositories
.
<repository>
<id>efekosdev</id>
<url>https://efekos.dev/maven</url>
</repository>
maven { url 'https://efekos.dev/maven' }
Artifact | Platform | Latest Version |
---|---|---|
arn-paper |
PaperMC | 0.4 |
arn-spigot |
SpigotMC | 0.4 |
Add this dependency. Check the table above to make sure you use the latest version and the correct dependency.
<dependency>
<groupId>dev.efekos.arn</groupId>
<artifactId>arn-paper</artifactId>
<version>0.4</version>
</dependency>
implementation 'dev.efekos.arn:arn-paper:0.4'
Arn uses Java Reflection API to scan through your plugin, detect needed classes and use them. Because of this, you have
to add a Container
annotation to
every class that must be scanned by Arn.
Let's create a new class using the knowledge we know so far.
import dev.efekos.arn.common.annotation.Container;
@Container
public class CommandClass {
}
Normally, you need to either handle your command through events or create a Command
class for it. But in Arn,
all you have to do is add a method with such annotations.
import dev.efekos.arn.common.annotation.Command;
import org.bukkit.command.CommandSender;
@Command("ping") // command name
public int helloWorld(CommandSender sender /*get the sender*/) {
sender.sendMessage("Pong!");
return 0;
}
When scanned and registered, this method will be equivalent of command /helloworld
, that takes no arguments and says
"Pong!" back to the sender. Now you might be thinking about arguments. That is pretty easy.
import dev.efekos.arn.common.annotation.Command;
import dev.efekos.arn.common.annotation.CommandArgument;
import org.bukkit.command.CommandSender;
@Command("hello")
public int helloWorld(CommandSender sender, @CommandArgument String name /*string argument*/) {
sender.sendMessage("Hello "+name+"!");
return 0;
}
All we have to do is add a parameter with
CommandArgument
annotation. This
method is now the equivalent of /helloworld <name>
, <name>
being a String
that can have whitespaces using quoted
strings. You can use following combinations of annotations and types by default (all of them requires
CommandArgument
.):
Advancement
Attribute
@Block
Material
BlockData
Boolean
boolean
World
Double
double
PotionEffectType
Enchantment
Entity
Float
float
GameMode
Integer
int
@InventorySlot
Integer
@InventorySlot
int
@Item
Material
ItemStack
Location
Long
long
Entity
[]Player
[]Player
String
BaseComponent
@Vector
Location
These don't require a CommandArgument
annotation.
By default, name of an argument is same with name of the parameter. If you want, you can explicitly specify argument names like this.
public int method(@CommandArgument("name") String s);
If you don't specify argument names and let Arn use parameter names instead, you'll probably face a problem. Maven does
not compile applications with parameter names. In order to solve this problem, you can add small configuration to your
maven-compiler-plugin
. It is something like this:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<parameters>true</parameters>
</configuration>
<!-- ... -->
</plugin>
I use Maven, so I don't know a solution for Gradle. It is probably something easy like it is in Maven, so you can fix it with a bit of research.
I only showed you how to make base commands. There is more than base commands in Arn. If you want to use two or more
literals, you can simply separate them with .
in your CommandArgument
annotation. But in this way, arguments will be placed after all the literals. If you want to place literals between
arguments, you can follow this syntax: [ab]:[0-9]+:[a-z]+
. First group of a letter determines will this literal be placed before
or after the argument with the given index. Second group of a number is the index of an argument. Finally, last group of
a lowercase word is the actual literal. Let me explain how it works more with this graph:
As you can see, the last literal 'makefloor' is placed after 0th argument. This is because the first letter is 'a' and
the index is 0
. If the letter was 'b', the literal would be placed after second literal 'stuff'. If the index was 1
,
the literal would be placed after the 1st argument.
If you want to make a custom argument, you can, using Enum classes! All you have to do is annotate an enum class with both
Container
and CustomArgument
,
and you'll be able to use that enum class as a command argument.
// Rating.java
import dev.efekos.arn.common.annotation.Command;
import dev.efekos.arn.common.annotation.CommandArgument;
import dev.efekos.arn.common.annotation.Container;
import dev.efekos.arn.common.annotation.CustomArgument;
import org.bukkit.entity.Player;
@Container
@CustomArgument()
public enum Rating {
TRASH,
BAD,
MID,
GREAT,
GOAT
}
// Commands.java
@Container
public class Commands {
@Command("rate")
public int rate(@CommandArgument Rating rating, @CommandArgument String message, Player sender) {
player.sendMessage("Thanks for your rating!");
return 0;
}
}
You can block access by using a permission or annotations. All you have to do is add the permission node to the
Command
like this:
import dev.efekos.arn.common.annotation.Command;
import dev.efekos.arn.common.annotation.block.BlockCommandBlock;
import dev.efekos.arn.common.annotation.block.BlockConsole;
import dev.efekos.arn.common.annotation.block.BlockPlayer;
@Command(value = "this.is.command.name", permission = "methods.cmd.method")
@BlockPlayer // Blocks access to players
@BlockCommandBlock // Blocks access to command blocks
@BlockConsole // Blocks access to the console
public int method(/*...*/);
You can apply NumberLimitations
to any command argument with a number type.
import dev.efekos.arn.common.annotation.Command;
import dev.efekos.arn.common.annotation.CommandArgument;
import dev.efekos.arn.common.annotation.block.BlockCommandBlock;
import dev.efekos.arn.common.annotation.modifier.NumberLimitations;
@Command("settimeout")
@BlockCommandBlock
public int setTimeout(@CommandArgument @NumberLimitations(min = 0,max = 30) Integer argument);
If you want to go even more crazy, you can implement your own command parameter types. You have to make an implementation
of CommandHandlerMethodArgumentResolver
first. If you want to add an argument to the command structure to resolver a parameter, you might also need to make an
implementation of a CommandArgumentResolver
.
After that, simply make a Container
that
implements ArnConfigurer
, and add your
resolvers using that configurer.
After all of your command and configurations are done, call
Arn
#run
from your plugin.
import dev.efekos.arn.common.Arn;
import dev.efekos.arn.common.base.ArnInstance;
import org.bukkit.plugin.java.JavaPlugin;
public class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
if(Arn.isAvailable()){
ArnInstance arn = Arn.getInstance();
arn.run(MyPlugin.class,this);
}
}
}
There is an annotation called FromSender
that lets you access properties of the command sender (such as name) without having to get it yourself. As of 0.2, these
are the valid parameters you can use. Keep in mind that they all also need
@FromSender
.
Available Sender Types | Argument | Description | Notes |
---|---|---|---|
Location |
Returns location of the sender. | ||
@Name String |
Returns name of the sender. Console sender will return "CONSOLE" and command blocks will return a string containing its locations, "[@:24:62:-113]" for example. |
If the parameter name ends with "name" (ignoring case), @Name isn't required. |
|
ItemStack |
Returns the item player has in their main-hand slot. | ||
@OffHand ItemStack |
Returns the item player has in their off-hand slot. | ||
@Helmet ItemStack |
Returns the item player has in their helmet slot. | ||
@Chestplate ItemStack |
Returns the item player has in their chestplate slot. | ||
@Leggings ItemStack |
Returns the item player has in their leggings slot. | ||
@Boots ItemStack |
Returns the item player has in their boots slot. | ||
@ExpLevel Integer |
Returns player's experience level. | Can be any of Double /Integer /Float , it will be casted accordingly. |
|
@FoodLevel Integer |
Returns player's food level. | Can be any of Double /Integer /Float , it will be casted accordingly. |
|
@Health Double |
Returns player's health. | Can be any of Double /Integer /Float , it will be casted accordingly. |
|
@MaxHealth Double |
Returns player's maximum health. | Can be any of Double /Integer /Float , it will be casted accordingly. |
|
@Experience Float |
Returns player's experience points. | Can be any of Double /Integer /Float , it will be casted accordingly. |
|
UUID |
Returns UUID of the player. | ||
Inventory |
Returns inventory of the player. |
If a @FromSender
ed argument is not
available for the sender, it will return null
instead.
When you make custom argument resolvers, your resolvers might have conflicts with some other resolvers. In order to prevent this, you can use annotation exceptions. When you add an annotation exception to a resolver, that resolver can't work on parameters with that annotation, for example:
import dev.efekos.arn.common.exception.ExceptionMap;
import dev.efekos.arn.paper.face.PaperArnConfig;
import dev.efekos.arn.paper.face.PaperCmdResolver;
import dev.efekos.arn.paper.face.PaperHndResolver;
import java.util.List;
public class GreatConfig implements PaperArnConfig {
@Override
public void addHandlerMethodArgumentResolvers(List<PaperHndResolver> resolvers) {
}
@Override
public void addArgumentResolvers(List<PaperCmdResolver> resolvers) {
}
@Override
public void putArgumentResolverExceptions(ExceptionMap<CommandArgumentResolver> map) {
map.put(CmdStringArg.class, Greater.class); // now CmdStringArg won't be applicable for parameters with @Greater
}
@Override
public void putHandlerMethodArgumentResolverExceptions(ExceptionMap<CommandHandlerMethodArgumentResolver> map) {
map.put(HndStringArg.class, Greater.class); // This blocks HndStringArg the same way
}
}
This repository is licensed under the MIT License.