1. Welcome to skUnity!

    Welcome to skUnity! This is a forum where members of the Skript community can communicate and interact. Skript Resource Creators can post their Resources for all to see and use.

    If you haven't done so already, feel free to join our official Discord server to expand your level of interaction with the comminuty!

    Now, what are you waiting for? Join the community now!

Dismiss Notice
This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

Creating an Expression

Oct 16, 2018
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:
    Code (Skript):
    1. [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:

    Code (Java):
    1. package me.limeglass.addon.elements.expressions;
    2.  
    3. import org.bukkit.event.Event;
    4. import org.eclipse.jdt.annotation.Nullable;
    5.  
    6. import ch.njol.Skript.lang.Expression;
    7. import ch.njol.Skript.lang.SkriptParser.ParseResult;
    8. import ch.njol.Skript.lang.util.SimpleExpression;
    9. import ch.njol.util.Kleenean;
    10.  
    11. public class ExprExample extends SimpleExpression<String> {
    12.  
    13.     @Override
    14.     public Class<? extends String> getReturnType() {
    15.         //1
    16.         return null;
    17.     }
    18.  
    19.     @Override
    20.     public boolean isSingle() {
    21.         //2
    22.         return true;
    23.     }
    24.  
    25.     @Override
    26.     public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) {
    27.         //3
    28.         return false;
    29.     }
    30.  
    31.     @Override
    32.     public String toString(@Nullable Event event, boolean debug) {
    33.         //4
    34.         return null;
    35.     }
    36.  
    37.     @Override
    38.     @Nullable
    39.     protected String[] get(Event event) {
    40.         //5
    41.         return null;
    42.     }
    43. }
    Our project should look like this at the moment:

    [​IMG]
    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

    Code (Java):
    1. @Override
    2. public Class<? extends String> getReturnType() {
    3.     return String.class;
    4. }
    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.

    Code (Java):
    1. @Override
    2. public boolean isSingle() {
    3.     return true;
    4. }
    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.

    Code (Java):
    1. package me.limeglass.addon.elements.expressions;
    2.  
    3. import org.bukkit.entity.Player;
    4. import org.bukkit.event.Event;
    5. import org.eclipse.jdt.annotation.Nullable;
    6.  
    7. import ch.njol.Skript.lang.Expression;
    8. import ch.njol.Skript.lang.SkriptParser.ParseResult;
    9. import ch.njol.Skript.lang.util.SimpleExpression;
    10. import ch.njol.util.Kleenean;
    11.  
    12. public class ExprExample extends SimpleExpression<String> {
    13.  
    14.     private Expression<Player> player;
    15.  
    16.     @Override
    17.     public Class<? extends String> getReturnType() {
    18.         return String.class;
    19.     }
    20.  
    21.     @Override
    22.     public boolean isSingle() {
    23.         return true;
    24.     }
    25.  
    26.     @SuppressWarnings("unchecked")
    27.     @Override
    28.     public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) {
    29.         player = (Expression<Player>) exprs[0];
    30.         return true;
    31.     }
    32.  
    33.     @Override
    34.     public String toString(@Nullable Event event, boolean debug) {
    35.         //Still needing to explain
    36.         return null;
    37.     }
    38.  
    39.     @Override
    40.     @Nullable
    41.     protected String[] get(Event event) {
    42.         //Still needing to explain
    43.         return null;
    44.     }
    45. }
    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.

    Code (Java):
    1. @Override
    2. public String toString(@Nullable Event event, boolean debug) {
    3.     return "Example expression with expression player: " + player.toString(event, debug);
    4. }
    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:

    Code (Java):
    1. @Override
    2. @Nullable
    3. protected String[] get(Event event) {
    4.     Player p = player.getSingle(event);
    5.     if (p != null) {
    6.         return new String[] {p.getDisplayName()};
    7.     }
    8.     return null;
    9. }
    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"
    [​IMG]

    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:

    Code (Java):
    1. static {
    2.     Skript.registerExpression(ExprExample.class, String.class, ExpressionType.COMBINED, "[the] name of %player%");
    3. }
    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:
    Code (Skript):
    1. [the] data value of %items%
    2.  
    3. #or
    4.  
    5. %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:

    Code (Java):
    1. package me.limeglass.addon.elements.expressions;
    2.  
    3. import org.bukkit.entity.Player;
    4. import org.bukkit.event.Event;
    5.  
    6. import org.eclipse.jdt.annotation.Nullable;
    7.  
    8. import ch.njol.Skript.Skript;
    9. import ch.njol.Skript.lang.Expression;
    10. import ch.njol.Skript.lang.ExpressionType;
    11. import ch.njol.Skript.lang.SkriptParser.ParseResult;
    12. import ch.njol.Skript.lang.util.SimpleExpression;
    13. import ch.njol.util.Kleenean;
    14. public class ExprExample extends SimpleExpression<String> {
    15.  
    16.    static {
    17.        Skript.registerExpression(ExprExample.class, String.class, ExpressionType.COMBINED, "[the] name of %player%");
    18.    }
    19.  
    20.    private Expression<Player> player;
    21.  
    22.    @Override
    23.    public Class<? extends String> getReturnType() {
    24.        return String.class;
    25.    }
    26.  
    27.    @Override
    28.    public boolean isSingle() {
    29.        return true;
    30.    }
    31.  
    32.    @SuppressWarnings("unchecked")
    33.    @Override
    34.    public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) {
    35.        player = (Expression<Player>) exprs[0];
    36.        return true;
    37.    }
    38.  
    39.    @Override
    40.    public String toString(@Nullable Event event, boolean debug) {
    41.        return "Example expression with expression player: " + player.toString(event, debug);
    42.    }
    43.  
    44.    @Override
    45.    @Nullable
    46.    protected String[] get(Event event) {
    47.        Player p = player.getSingle(event);
    48.        if (p != null) {
    49.            return new String[] {p.getDisplayName()};
    50.        }
    51.        return null;
    52.    }
    53. }
    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

    [​IMG]

    So it works now. You can do

    Code (Skript):
    1. command /test:
    2.    trigger:
    3.        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:

    Code (Java):
    1. package me.limeglass.addon.elements.expressions;
    2.  
    3. import org.bukkit.entity.Player;
    4. import org.bukkit.event.Event;
    5.  
    6. import org.eclipse.jdt.annotation.Nullable;
    7.  
    8. import ch.njol.Skript.Skript;
    9. import ch.njol.Skript.classes.Changer.ChangeMode;
    10. import ch.njol.Skript.lang.Expression;
    11. import ch.njol.Skript.lang.ExpressionType;
    12. import ch.njol.Skript.lang.SkriptParser.ParseResult;
    13. import ch.njol.Skript.lang.util.SimpleExpression;
    14. import ch.njol.util.Kleenean;
    15. public class ExprExample extends SimpleExpression<String> {
    16.  
    17.    static {
    18.        Skript.registerExpression(ExprExample.class, String.class, ExpressionType.COMBINED, "[the] name of %player%");
    19.    }
    20.  
    21.    private Expression<Player> player;
    22.  
    23.    @Override
    24.    public Class<? extends String> getReturnType() {
    25.        return String.class;
    26.    }
    27.  
    28.    @Override
    29.    public boolean isSingle() {
    30.        return true;
    31.    }
    32.  
    33.    @SuppressWarnings("unchecked")
    34.    @Override
    35.    public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parser) {
    36.        player = (Expression<Player>) exprs[0];
    37.        return true;
    38.    }
    39.  
    40.    @Override
    41.    public String toString(@Nullable Event event, boolean debug) {
    42.        return "Example expression with expression player: " + player.toString(event, debug);
    43.    }
    44.  
    45.    @Override
    46.    @Nullable
    47.    protected String[] get(Event event) {
    48.        Player p = player.getSingle(event);
    49.        if (p != null) {
    50.            return new String[] {p.getDisplayName()};
    51.        }
    52.        return null;
    53.    }
    54.  
    55.    @Override
    56.    public void change(Event event, Object[] delta, ChangeMode mode){
    57.        //Still explaining this
    58.    }
    59.  
    60.    @Override
    61.    public Class<?>[] acceptChange(final ChangeMode mode) {
    62.        //Still explaining this
    63.        return null;
    64.    }
    65. }
    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:

    Code (Skript):
    1. set name of player to "&6Example name"
    2. reset name of player
    3. 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:

    Code (Java):
    1. @Override
    2. public Class<?>[] acceptChange(final ChangeMode mode) {
    3.    if (mode == ChangeMode.DELETE || mode == ChangeMode.SET || mode == ChangeMode.RESET) {
    4.        return CollectionUtils.array(String.class);
    5.    }
    6.    return null;
    7. }
    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:

    Code (Java):
    1. @Override
    2. public void change(Event event, Object[] delta, ChangeMode mode){
    3.    Player p = player.getSingle(event);
    4.    if (p != null) {
    5.        if (mode == ChangeMode.SET) {
    6.            p.setDisplayName((String) delta[0]);
    7.        } else if (mode == ChangeMode.DELETE || mode == ChangeMode.RESET) {
    8.            p.setDisplayName(p.getName()); //This will reset their display name back to their original name
    9.        }
    10.    }
    11. }
    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:

    Code (Skript):
    1. time of %world%
    2.  
    3. #and
    4.  
    5. %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:

    Code (Java):
    1. package me.limeglass.addon.elements.expressions;
    2.  
    3. import org.bukkit.World;
    4. import org.eclipse.jdt.annotation.Nullable;
    5.  
    6. import ch.njol.Skript.expressions.base.SimplePropertyExpression;
    7.  
    8. public class ExprWorldTime extends SimplePropertyExpression<World, Number> {
    9.  
    10.     @Override
    11.     public Class<? extends Number> getReturnType() {
    12.         //still explaining
    13.         return null;
    14.     }
    15.  
    16.     @Override
    17.     @Nullable
    18.     public Number convert(World world) {
    19.         //still explaining
    20.         return null;
    21.     }
    22.  
    23.     @Override
    24.     protected String getPropertyName() {
    25.         //still explaining
    26.         return null;
    27.     }
    28. }
    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:

    Code (Java):
    1. package me.limeglass.addon.elements.expressions;
    2.  
    3. import org.bukkit.World;
    4. import org.eclipse.jdt.annotation.Nullable;
    5.  
    6. import ch.njol.Skript.expressions.base.SimplePropertyExpression;
    7.  
    8. public class ExprWorldTime extends SimplePropertyExpression<World, Number> {
    9.  
    10.     @Override
    11.     public Class<? extends Number> getReturnType() {
    12.         return Number.class;
    13.     }
    14.  
    15.     @Override
    16.     @Nullable
    17.     public Number convert(World world) {
    18.         return world.getTime();
    19.     }
    20.  
    21.     @Override
    22.     protected String getPropertyName() {
    23.         return "time";
    24.     }
    25. }
    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

    Code (Skript):
    1. [the] time of %world%
    2.  
    3. #or
    4.  
    5. %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. Here is the finished syntax:

    Code (Java):
    1. package me.limeglass.addon.elements.expressions;
    2.  
    3. import org.bukkit.World;
    4. import org.bukkit.event.Event;
    5. import org.eclipse.jdt.annotation.Nullable;
    6.  
    7. import ch.njol.Skript.classes.Changer.ChangeMode;
    8. import ch.njol.Skript.expressions.base.SimplePropertyExpression;
    9. import ch.njol.util.coll.CollectionUtils;
    10.  
    11. public class ExprWorldTime extends SimplePropertyExpression<World, Number> {
    12.  
    13.    static {
    14.        register(ExprWorldTime.class, Number.class, "time", "world");
    15.    }
    16.  
    17.    @Override
    18.    public Class<? extends Number> getReturnType() {
    19.        return Number.class;
    20.    }
    21.  
    22.    @Override
    23.    @Nullable
    24.    public Number convert(World world) {
    25.        return world.getTime();
    26.    }
    27.  
    28.    @Override
    29.    protected String getPropertyName() {
    30.        return "time";
    31.    }
    32.  
    33.    @Override
    34.    public void change(Event event, Object[] delta, ChangeMode mode){
    35.        if (delta != null) {
    36.            World world = getExpr().getSingle(event);
    37.            if (mode != ChangeMode.DELETE && mode != ChangeMode.RESET && world == null) return;
    38.            long time = ((Number) delta[0]).longValue();
    39.            switch (mode) {
    40.                case ADD:
    41.                    world.setTime(world.getTime() + time);
    42.                    break;
    43.                case DELETE:
    44.                    world.setTime(24000); //just before sunset
    45.                    break;
    46.                case REMOVE:
    47.                    world.setTime(world.getTime() - time);
    48.                    break;
    49.                case REMOVE_ALL:
    50.                case RESET:
    51.                    world.setTime(24000);
    52.                    break;
    53.                case SET:
    54.                    world.setTime(time);
    55.                    break;
    56.                default:
    57.                    assert false;
    58.            }
    59.        }
    60.    }
    61.  
    62.    @Override
    63.    public Class<?>[] acceptChange(final ChangeMode mode) {
    64.        return (mode != ChangeMode.REMOVE_ALL) ? CollectionUtils.array(Number.class) :  null;
    65.    }
    66. }
    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://forums.skunity.com/wiki/creating-an-effect/

    Addon tutorial
    Back | Next
jaylawl likes this.