Developer API: Making a Keyword - BoBoBalloon/InnovativeItemsDOCS GitHub Wiki

Intro

For this example, I will demonstrate below a keyword that kills the provided target called suicide.

Make A New Keyword

To start make a new class that extends the Keyword class:

public class SuicideKeyword extends Keyword {

}

Implement all methods and build a blank constructor:

public class SuicideKeyword extends Keyword {
    public SuicideKeyword() {
        super();
    }

    @Override
    protected void calling(ImmutableList<Object> arguments, RuntimeContext context) {
        
    }

    @Override
    public boolean isAsync() {
        
    }
}

Make A New Keyword: Constructor

Let's first talk about the constructor. The keyword constructor has two arguments. The first is a string. The second is a "ExpectedArguments" vararg.

The string is the identifier of the keyword. This is how it is identified in the configuration files and how it is retrieved from the cache. Because of that, it must be unique and no other keyword that is already registered can share that name. The second argument represents all expected arguments to be provided in the configuration file. All arguments provided must implement the functional interface of "ExpectedArguments". There are currently six implemantations that can be used but it is possible to add your own implementations.

ExpectedArguments: a functional interface used to allow any developer to parse the provided string in the configuration file to any object they like
ExpectedTargeters: a class used to represent all the targeters that can be used in a given argument
ExpectedPrimitive: a class used to represent all primitive data types including strings that will be parsed from the provided string in the configuration file
ExpectedManual: a class used to provide an implementation to a functional interface with the same shape as the aforementioned ExpectedArguments interface that allows support for error messages if parsing failed
ExpectedEnum<T extends Enum<T>>: a class used to parse any member of a given enum's class from a provided string in the configuration file (the class makes the assumption that the enum member will follow proper conventions and will be uppercase due to all enum members being constants)
ExpectedVarArg: a class used to create a var-arg, as a result this argument can ONLY BE USED AS THE TAIL OF ARGUMENTS OF A FUNCTION

Please use these classes to instruct the interpreter how to parse the given strings to objects that will be provided to you in the call method that will be explained in detail later in this tutorial.

public SuicideKeyword() {
    super("suicide", 
         new ExpectedTargeters(KeywordTargeter.PLAYER, KeywordTargeter.ENTITY));
}

This constructor gives the keyword the identifier of "suicide" and gives it one arguments that must be a targeter and can only be a player or entity targeter, if another targeter like block is provided, it will throw an error before your code is ever executed, so don't worry about that.

Make A New Keyword: calling() Method

The calling method is where the magic happens. This is the code that is executed when the keyword is fired. The calling method has two arguments that are passed into it that you can use. The first is the list of arguments that were parsed based on the elements you provided in the constructor. Because you set all the values in the constructor, you know what data type each element is, meaning it is safe to cast without using the instanceof keyword. The second is the runtime context. This is all of the information about how the event was fired and all of the info you need is there. For the suicide keyword, it would look like this:

@Override
protected void calling(ImmutableList<Object> arguments, RuntimeContext context) {
    KeywordTargeter rawTarget = (KeywordTargeter) arguments.get(0);
    LivingEntity target = null;

    if (rawTarget == KeywordTargeter.PLAYER) {
       target = context.getPlayer();
    }

    if (rawTarget == KeywordTargeter.ENTITY && context instanceof EntityContext) {
       EntityContext entityContext = (EntityContext) context;
       target = entityContext.getEntity();
    }

    target.setHealth(0);
}

Make A New Keyword: isAsync() Method

Because abilities have the potential to impact server performance, all keywords have the option to run async. But some bukkit methods cannot run async, they will throw an error. In these cases they have to run on the main thread. If the keyword can run perfectly fine async, it is highly recommended to have this method return true. If there is no way around it and the calling method must run on the main thread, return false. Because the suicide keyword is only setting health and not touching the damage method, it is possible to run this async. Because of this, I will opt to return true for this method, the example is here:

@Override
public boolean isAsync() {
    return true;
}

Make A New Keyword: All Together

Now we have talked about all the parts of the keyword object you must implement, let's take a look at the whole thing together now:

public class SuicideKeyword extends Keyword {
    public SuicideKeyword() {
        super("suicide", 
             new ExpectedTargeters(KeywordTargeter.PLAYER, KeywordTargeter.ENTITY));
    }

    @Override
    protected void calling(ImmutableList<Object> arguments, RuntimeContext context) {
        KeywordTargeter rawTarget = (KeywordTargeter) arguments.get(0);
        LivingEntity target = null;

        if (rawTarget == KeywordTargeter.PLAYER) {
           target = context.getPlayer();
        }

        if (rawTarget == KeywordTargeter.ENTITY && context instanceof EntityContext) {
           EntityContext entityContext = (EntityContext) context;
           target = entityContext.getEntity();
        }

        target.setHealth(0);
    }

    @Override
    public boolean isAsync() {
        return true;
    }
}

Registering The Keyword

Once we have made our keyword class, now we need to register it so the Innovative Items plugin can use it. In our main class we need to override the onLoad method. Do not put this inside the onEnable method, as this will not load the keywords in time for parsing. Our method will look like this:

@Override
public void onLoad() {

}

Once we have overridden the method, we need to get the active instance of the function manager object, to do this, we can use the InnovativeItemsAPI class like so:

@Override
public void onLoad() {
    FunctionManager functionManager = InnovativeItemsAPI.getFunctionManager();
}

Once we have the function manager, we need to call the registerKeywords method and initialize our keyword inside of it, in the case of the suicide keyword, it will look like this:

@Override
public void onLoad() {
    FunctionManager functionManager = InnovativeItemsAPI.getFunctionManager();

    functionManager.registerKeywords(new SuicideKeyword());
}

And we are done, now we can use the suicide keyword in innovative items config files just like any other keyword!

⚠️ **GitHub.com Fallback** ⚠️