Creating an Expression

Creating an Expression

Let's start with a SimpleExpression this is a class of Skript. A simple expression is something that returns a value, this can be a list or a single value. It can also be set, add, remove, delete and etc. So lets go through all the methods of a SimpleExpression one by one and create one ourselves.

First we need to create a new class, and a new Package to support our syntax. Let's create another package inside our elements package, so the name should be "me.limeglass.addon.elements.expressions" you right click the elements package to create a sub package of it.

So then after you created a sub package of elements. Create a new Class in the package "me.limeglass.addon.elements.expressions"

When naming expressions it is a Skript recommendation to call it ExprNAME. This is just how Skript likes to name it's expressions. Again it's your addon, but using these naming methods are highly recommended as it helps other developers understand your coding if someone reports an error or something.

So for my example i'm going to create a new class called ExprExample and it's going to extend SimpleExpression.

Now SimpleExpressions need a generic type. Which is the Object inside the SimpleExpression<stuff>

The "stuff" that goes inside this, should be your returning type. So if I wanted to return the name of a player.
With the syntax:
[the] name of %player%

Everything in [these] are optional

This would be returning a String. Strings are text surrounded by "quotes". We need to extend this expression with a string. So our setup will look like this:

package me.limeglass.addon.elements.expressions;

import org.bukkit.event.Event;
import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.util.SimpleExpression;
import ch.njol.util.Kleenean;

public class ExprExample extends SimpleExpression<String> {

@Override
public Class<? extends String> getReturnType() {
//1
return null;
}

@Override
public boolean isSingle() {
//2
return true;
}

@Override
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) {
//3
return false;
}

@Override
public String toString(@Nullable Event event, boolean debug) {
//4
return null;
}

@Override
@Nullable
protected String[] get(Event event) {
//5
return null;
}
}


Our project should look like this at the moment:


So now let's go over each method of this SimpleExpression.

getReturnType() method should return the class of the type you want to return. This copies the Generic type we specified in the extends portion. And we wanted a String. So it should return String.class

@Override
public Class<? extends String> getReturnType() {
return String.class;
}


isSingle() this method tells Skript if this expression returns as a list of String's or just one String. In our case i'm just going to make this a one string return. So I would set this as true. Because it is single.

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


init() Ok, this is where things get a little more more complex. This method is where you can grab your expressions that were used in the syntax as well as test what event this was used in and lots of stuff. For this i'm going to make it as simple as I can. You can look at Tips and Tricks section to understand matchedPattern and what the parser option does. For this example I will only be using the expressions. So we need to create a private field for the expression, and set it as this method gets called. So let's setup a Player expression, because that's what's inside our syntax.

The matchedPattern is a system that returns between 0-X X being the amount of strings your syntax supports.
isDelayed is a boolean to tell if it's as the event happens or a tick after the event has happened. This is for telling if the user has waited a second or not. Some times methods need to have no delay in them.
parser is a system to help with advanced syntax. I will go over this in the Tips and Tricks section.

package me.limeglass.addon.elements.expressions;

import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.util.SimpleExpression;
import ch.njol.util.Kleenean;

public class ExprExample extends SimpleExpression<String> {

private Expression<Player> player;

@Override
public Class<? extends String> getReturnType() {
return String.class;
}

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

@SuppressWarnings("unchecked")
@Override
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) {
player = (Expression<Player>) exprs[0];
return true;
}

@Override
public String toString(@Nullable Event event, boolean debug) {
//Still needing to explain
return null;
}

@Override
@Nullable
protected String[] get(Event event) {
//Still needing to explain
return null;
}
}

Now the exprs is an Array. This array contains a list of all the expressions in order from left to right. If %player% was our first type that appeared, that would be index 0 in our array. So now we set that to the private field we created at the top.

toString() this is a system to help debug errors that may arise from users reporting issues or anything in between. So we need something that will be helpful for us to understand what is going on.

@Override
public String toString(@Nullable Event event, boolean debug) {
return "Example expression with expression player: " + player.toString(event, debug);
}

This will return the name of this expression and the player expression that was in it aswell. The optional parameters of the toString() of the expression are for Skript to help understand. The debug will return true or false depending on if the user has set the debug option in the Skript config.sk. This method is completely up to you, old poorly written Skript Addons just return the name of the expression, and sometimes forget to update it when they copy the expression over. So it's up to you on how much information you're willing to help out someone when they get an error report.

get() this is the main method we need. This is where all the juice is. Here we return what we want. In our case we want to get the name of the player. So let's first grab our player from our Expression<Player> then we return the name of the player. We need to return as an Array. This is just how Skript works. So here is how we can do that:

@Override
@Nullable
protected String[] get(Event event) {
Player p = player.getSingle(event);
if (p != null) {
return new String[] {p.getDisplayName()};
}
return null;
}


If the expression returned null we return null.

Ok now you might be wondering: Why do I have this @Nullable thing and why is it red. This is an annotation that allows something like a method, field or parameter to be null. In order for Eclipse users to fix this you can hover over the @Nullable and click "Copy library with default null annotations to build path"


So now we need to register the Syntax of this expression. Well now I can tell you about that loadClasses() method we used in our onEnable(). This method will loop through all classes in the package we specified. So in our case, it will loop all the classes in the packages "elements" and when it loops through all these classes. Skript is going to call a static method if it exists. So we can register our expression in that. Here is an example of it:

static {
Skript.registerExpression(ExprExample.class, String.class, ExpressionType.COMBINED, "[the] name of %player%");
}


The first parameter is the name of our Expression we just made. The second is the return type, which is String because the name of the player is a String. The third parameter is an ExpressionType. There are 5 used expression types at the time of writing this, here is more information about them from Njol:

SIMPLE
Expressions that only match simple text, e.g. "[the] player" (Contain no types)

COMBINED

Expressions that contain other expressions, e.g. "[the] distance between %location% and %location%" (Contains two or more types)

PROPERTY
Property expressions, e.g:
[the] data value of %items%

#or

%items%['s] data value

(Contains one type)

PATTERN_MATCHES_EVERYTHING
Expressions whose pattern matches (almost) everything, e.g. "[the] [event-]<.+>" (Used for calculating advanced syntax)

So you may be wondering now why I used COMBINED instead of PROPERTY, because clearly it states that COMBINED is only used for more than two types when we only have one lol. So you're right, I'm only using this for the example. You could also use SIMPLE. The syntax still registers regardless of the type you use, but you should be using the correct one based on your syntax. I will show you a better way of doing Property syntax with single types below. Using the PropertyExpression class.

But first lets take a look at what we have right now, so we're both on the same page:

package me.limeglass.addon.elements.expressions;

import org.bukkit.entity.Player;
import org.bukkit.event.Event;

import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.Skript;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.ExpressionType;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.util.SimpleExpression;
import ch.njol.util.Kleenean;
public class ExprExample extends SimpleExpression<String> {

static {
Skript.registerExpression(ExprExample.class, String.class, ExpressionType.COMBINED, "[the] name of %player%");
}

private Expression<Player> player;

@Override
public Class<? extends String> getReturnType() {
return String.class;
}

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

@SuppressWarnings("unchecked")
@Override
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) {
player = (Expression<Player>) exprs[0];
return true;
}

@Override
public String toString(@Nullable Event event, boolean debug) {
return "Example expression with expression player: " + player.toString(event, debug);
}

@Override
@Nullable
protected String[] get(Event event) {
Player p = player.getSingle(event);
if (p != null) {
return new String[] {p.getDisplayName()};
}
return null;
}
}


Look at that! We made our first expression gg. So you can run that by compiling the plugin into a jar if you want now. Click File -> Export -> Java -> JAR file -> Then click Next

Then check mark your addon if it's not already, find a location to output the jar and click finish



So it works now. You can do

command /test:
trigger:
broadcast "%name of player%"


This might be redundant, because Skript has the same syntax. Keep this in mind, if Skript has any syntax that is also in your addon. Skript takes priority and will choose it's own syntax over your addon.

If you want to create another expression, you can create a new class in the elements.expressions package we just created.

Quick TIP: For some reason Skript doesn't like to handle Integers/int in the Skript API. It does weird mechanics, like expressions not working, can't register and other weirdness. So it's always best to work with Numbers. If something asks for an Integer, transfer and return it as a Number. If the user needs to set it as an Integer. Use Numbers as the input and parse it as an integer.

number.intValue();

So now lets get into how to set, add, remove and all that stuff. How can I add that to this expression? Well here is how we can start; there are two new methods we have to add to our expression we just made. These methods are acceptChange() and change() these two methods are what allow Skript to add functionality to our expressions.

So let's add these two methods. This is what our ExprExample class should look like now:

package me.limeglass.addon.elements.expressions;

import org.bukkit.entity.Player;
import org.bukkit.event.Event;

import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.Skript;
import ch.njol.skript.classes.Changer.ChangeMode;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.ExpressionType;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.util.SimpleExpression;
import ch.njol.util.Kleenean;
public class ExprExample extends SimpleExpression<String> {

static {
Skript.registerExpression(ExprExample.class, String.class, ExpressionType.COMBINED, "[the] name of %player%");
}

private Expression<Player> player;

@Override
public Class<? extends String> getReturnType() {
return String.class;
}

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

@SuppressWarnings("unchecked")
@Override
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) {
player = (Expression<Player>) exprs[0];
return true;
}

@Override
public String toString(@Nullable Event event, boolean debug) {
return "Example expression with expression player: " + player.toString(event, debug);
}

@Override
@Nullable
protected String[] get(Event event) {
Player p = player.getSingle(event);
if (p != null) {
return new String[] {p.getDisplayName()};
}
return null;
}

@Override
public void change(Event event, Object[] delta, ChangeMode mode){
//Still explaining this
}

@Override
public Class<?>[] acceptChange(final ChangeMode mode) {
//Still explaining this
return null;
}
}


So I feel like now I should explain what ChangeMode is. ChangeMode is an Enum of "changers" which allows you to define what this syntax can do. The changers that are available as of writing this are SET, ADD, REMOVE, RESET, DELETE and REMOVE_ALL

DELETE = clear %expression%; delete %expression%

These allow you to do magical things to your expressions such as these Skript code snippets:

set name of player to "&6Example name"
reset name of player
remove "example" from name of player #Makes sense if your expression was a list


So now here is what we need to do; In the acceptChange() we need to test if the calling mode is allowed or not. The input will be from the user. So lets test for SET, RESET and DELETE:

@Override
public Class<?>[] acceptChange(final ChangeMode mode) {
if (mode == ChangeMode.DELETE || mode == ChangeMode.SET || mode == ChangeMode.RESET) {
return CollectionUtils.array(String.class);
}
return null;
}


The CollectionUtils is a class from Skript and we're going to be using the array converter. We want to convert String.class to an array, because in our case, we're only allowing String for the user to use. If you want to add multiple types to be set, added, removed etc, you have to add them here in this array. So this will create an array of just String.class

Now we need to execute what we promised to Skript that this Expression could allow. We do this in the change() method:

@Override
public void change(Event event, Object[] delta, ChangeMode mode){
Player p = player.getSingle(event);
if (p != null) {
if (mode == ChangeMode.SET) {
p.setDisplayName((String) delta[0]);
} else if (mode == ChangeMode.DELETE || mode == ChangeMode.RESET) {
p.setDisplayName(p.getName()); //This will reset their display name back to their original name
}
}
}


delta is an object array containing objects from what the user has set, added, removed etc. So if the user was to set the name as "Hello" the delta array would contain String "Hello". If you allow multiple types/objects, this will come through here and you will need to do some casting.

And that's how to create an expression yay!

Property Expressions:

Now let's get into a better way of doing single type expressions. Such as the PROPERTY ExpressionType example that was provided above.

These are called well, PropertyExpression's. There are two classes within Skript to implant this. SimplePropertyExpression and PropertyExpression. These make creating single type expressions much easier and quicker. In the following property expression, I will be using the class SimplePropertyExpression to make this tutorial simpler and I will be getting the time of a world, with changes.

So before I begin some may be wondering what the difference is between the two classes. The PropertyExpression was designed to make Expressions even simpler. This class turns a syntax into a single string, meaning no fancy optional symbols or anything. So for example if I set the getPropertyName() method of it to "time" and the input type is a World, the syntax would be:

time of %world%

#and

%world%'s time

The other class SimplePropertyExpression is an even easier class to create where there are only two mandatory methods needed to create a syntax.

So let's get started shall we. First you need to create a new class, and in our case it will be within the "expressions.elements" package for easy registration. Here is how it should look like:

package me.limeglass.addon.elements.expressions;

import org.bukkit.World;
import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.expressions.base.SimplePropertyExpression;

public class ExprWorldTime extends SimplePropertyExpression<World, Number> {

@Override
public Class<? extends Number> getReturnType() {
//still explaining
return null;
}

@Override
@Nullable
public Number convert(World world) {
//still explaining
return null;
}

@Override
protected String getPropertyName() {
//still explaining
return null;
}
}

To break this down. You may notice that there are now two generic types at the extends portion of the top class. This is how we tell Skript what the input should be and what the output should be. So we're going to take a World and convert it into a Number. (The number being the time of the world)

So in order to do this we can use the convert() method that is provided in this class, it will call the parameter which is the type you specified and now you have to return the other generic type that you told you would return, and lastly there is a toString() which as I explained above is helpful for debugging.

In a PropertyExpression there can only be 1 expression. Of course that expression may be single or a list. So in order to set it you use setExpr(insert expression) since this is using the SimplePropertyExpression, Skript automatically sets this up for us and returns it in the convert method. If you were using a more complex syntax and using PropertyExpression, you would use getExpr() to grab that expression in the get() method. So here how we use the convert() method in the SimplePropertyExpression:

package me.limeglass.addon.elements.expressions;

import org.bukkit.World;
import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.expressions.base.SimplePropertyExpression;

public class ExprWorldTime extends SimplePropertyExpression<World, Number> {

@Override
public Class<? extends Number> getReturnType() {
return Number.class;
}

@Override
@Nullable
public Number convert(World world) {
return world.getTime();
}

@Override
protected String getPropertyName() {
return "time";
}
}

Wow, look how simple and easy that is? It's great I know. It's best to always use a PropertyExpression if you have only one type in a syntax, unless you're doing more complex things with it, such as matched patterns, conditional testing etc. The getPropertyName() method is what defines and sets up the syntax. This will transform the syntax into the Property. So if you add "[the] time" as the property name return. The syntax would be

[the] time of %world%

#or

%world%'s [the] time

Now you see that second syntax doesn't sound right? It's not english right? So that's why Skript handles the Noun's of a syntax. You don't have to worry about adding [the] or [a] to the starting of your syntax. Skript automatically tests and adds that stuff. You might need to add some nouns into your syntax though if you need to add them in the middle of the syntax or something, Skript will always add Noun's before an expression, type or settable object.

And if you wanted to allow for multiple setting/adding/removing etc you would do the same as the above expression I explained earlier. You can also just call a method called register() in the static which is a shortcut for registering PropertyExpression's another reason to love them. The 4th parameter is the classinfo/type to be used such as %string%, %number%, %world% etc

Here is the finished syntax:

package me.limeglass.addon.elements.expressions;

import org.bukkit.World;
import org.bukkit.event.Event;
import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.classes.Changer.ChangeMode;
import ch.njol.skript.expressions.base.SimplePropertyExpression;
import ch.njol.util.coll.CollectionUtils;

public class ExprWorldTime extends SimplePropertyExpression<World, Number> {

static {
register(ExprWorldTime.class, Number.class, "time", "world");
}

@Override
public Class<? extends Number> getReturnType() {
return Number.class;
}

@Override
@Nullable
public Number convert(World world) {
return world.getTime();
}

@Override
protected String getPropertyName() {
return "time";
}

@Override
public void change(Event event, Object[] delta, ChangeMode mode){
if (delta != null) {
World world = getExpr().getSingle(event);
if (mode != ChangeMode.DELETE && mode != ChangeMode.RESET && world == null) return;
long time = ((Number) delta[0]).longValue();
switch (mode) {
case ADD:
world.setTime(world.getTime() + time);
break;
case DELETE:
world.setTime(24000); //just before sunset
break;
case REMOVE:
world.setTime(world.getTime() - time);
break;
case REMOVE_ALL:
case RESET:
world.setTime(24000);
break;
case SET:
world.setTime(time);
break;
default:
assert false;
}
}
}

@Override
public Class<?>[] acceptChange(final ChangeMode mode) {
return (mode != ChangeMode.REMOVE_ALL) ? CollectionUtils.array(Number.class) : null;
}
}


To look at more examples you can check out Skript's expressions from Bensku's fork here https://github.com/bensku/Skript/tree/master/src/main/java/ch/njol/skript/expressions

Next section is creating an effect https://docs.skunity.com/guides/guide/creating-an-effect/

Addon tutorial
Back | Next