Creating an Event

Creating an Event

Lets jump into events, events are the main control of scripting in my opinion, they start everything that the user can base their code block off of.

Events are the first element furthest to the left in any code block, when something happens in Minecraft there is probably an event for it. All events and subclasses extend the Event class which can be found here https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/Event.html

So before we start there are two types of event registration in Skript, one is for handling "simple events" and that is exactly what it's called SimpleEvent.class https://github.com/SkriptLang/Skript/blob/master/src/main/java/ch/njol/skript/lang/util/SimpleEvent.java where they can't have any parameters/expressions in the syntax, and the other is a SkriptEvent https://github.com/SkriptLang/Skript/blob/master/src/main/java/ch/njol/skript/lang/SkriptEvent.java which is the main usage of events, events are the easiest thing to implement in Skript so don't worry too much about this tutorial, I will be going into details about SkriptEvent classes first then SimpleEvents because it's super simple, it's long about the explanation but honestly it's easy :emoji_stuck_out_tongue:

So first in our example addon, I'm going to make a new package in the elements registering package, and create EvtExample that extends a SkriptEvent


In Skript there is a standard naming to name all your event classes with the prefix "EvtSomething" of course it's your addon, but you should really follow these standards, it makes it easier for other developers to understand what the class is about.

So now we have all the implemented methods, the initialization method is the exact same as Expressions, with some modification, they don't have expressions. Since events are the beginning of a code block and users have no room or a direct event in the script to create expressions, the events use Literals.

A literal is basically an element that is a direct object of something, it can have parameters but most of the time it doesn't, for example a Literal is "console" you can use this anytime to reference the console, the console is always a standard present object. So in our event we could use things like ItemType "light green stained glass" or "diamond sword" because these are values that can always be grabbed, we can't however grab something like an Entity in the event (you can grab entity data which is similar to an ItemType compared to an ItemStack. "zombie" rather than it's object).

In this example I will be using the event https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityAirChangeEvent.html since it's not already implemented in Skript yet (1.13), with the usage of EntityData, keep in mind if you want to use ItemTypes for a different event, it's similar to how i'm about to implement EntityData.

The syntax I'm making for this event is "[entity] [remaining] air chang(e|ing) [(of|for) %-entitydatas%]"
The "-" in expressions basically means that the Expression will default to null if the entitydata is not used in the event, but this is not the case. This is a literal rather than an Expression so Skript will already handle that during runtime and just ignore the nullable prefix we added. Having this prefix in optional expressions is always good practice to remember and use, otherwise you will get errors if it's an actual Expression.

In order to register events we use the "registerEvent" method from the Skript class
static {
    Skript.registerEvent("Remaining Air", EvtEntityAir.class, EntityAirChangeEvent.class, "[entity] [remaining] air chang(e|ing) [(of|for) %-entitydatas%]");
}


The first parameter is a String which is the name of the event that will be displayed in the documentation.
The second parameter is the actual class we made, this parameter needs to have a class that extends SkriptEvent which we do have.
The third parameter is the events we are making the class for. Now there are two "registerEvent" methods in the Skript class, you can have multiple events for one SkriptEvent, this is because if you want to register or handle multiple events in one class you can do so, an example of this is the click event in Skript https://github.com/SkriptLang/Skript/blob/master/src/main/java/ch/njol/skript/events/EvtClick.java
If you want to merge a bunch of events into one class you can, but it's not really clean nor a good idea to do unless all events have a relevancy to eachother or something like the EvtClick above. Use at your own discretion. In this tutorial I will only be calling one event for it, which is EntityAirChangeEvent.class.
Lastly the final fourth parameter is the syntax patterns.

Now lets grab the EntityData from the init method, and our class should look like this now;

package me.limeglass.addon.elements.events;

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

import ch.njol.skript.Skript;
import ch.njol.skript.entity.EntityData;
import ch.njol.skript.lang.Literal;
import ch.njol.skript.lang.SkriptEvent;
import ch.njol.skript.lang.SkriptParser.ParseResult;

public class EvtEntityAir extends SkriptEvent {

    static {
        Skript.registerEvent("Remaining Air", EvtEntityAir.class, EntityAirChangeEvent.class, "[entity] [remaining] air chang(e|ing) [(of|for) %-entitydatas%]");
    }

    Literal<EntityData<?>> entities;

    @SuppressWarnings("unchecked")
    @Override
    public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) {
        entities = (Literal<EntityData<?>>) args[0];
        return true;
    }

    @Override
    public boolean check(Event e) {
        //Explaining still
        return false;
    }

    @Override
    public String toString(@Nullable Event e, boolean debug) {
        return "Remaining air event " + entities.toString(e, debug);
    }

}

If the init method is returned false this event will not get registered, useful if something in the literal did not pass your expectations.
The entities variable is a literal and we set it from the init method just like Expressions, we don't have to worry about it being null until the check method.

So now is the main part of the event, the check method. This method is what allows you, the developer, to control when the event is called in Skript for the script user. If the check method returns false the event will not be called in the script code.

Njol thought ahead of us and he had added a Checker to Literals for the exact reason of using them in events. Checkers are just like Getters but they check for values. Basically Checkers will go into the literal and check all the values of the literal against your check. So lets make this to explain it;
@Override
public boolean check(Event e) {
    if (entities != null) {
        Entity entity = ((EntityAirChangeEvent)e).getEntity();
        return entities.check(e, new Checker<EntityData<?>>() {
            @Override
            public boolean check(EntityData<?> data) {
                return data.isInstance(entity);
            }
        });
    }
    return true;
}

This is a checker in Skript. Most EntityData events in Skript are really old and don't actually use this, this is a better and cleaner system to use in general, a checker will again as said go through all the values of the Literal and what we need to do is check to see if the user wants this entity, we use the "isInstance(ENTITY)" method of the EntityData to check that. If the checker matches all the entitydatas the user wants, it will return true and the event will be called. Since this is an entity event the "entity/event-entity" EventValues will be registered, and I will get into that later in this thread.

And that's it! A complete event that works now :emoji_grinning:


package me.limeglass.addon.elements.events;

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

import ch.njol.skript.Skript;
import ch.njol.skript.entity.EntityData;
import ch.njol.skript.lang.Literal;
import ch.njol.skript.lang.SkriptEvent;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.util.Checker;

public class EvtEntityAir extends SkriptEvent {

    static {
        Skript.registerEvent("Remaining Air", EvtEntityAir.class, EntityAirChangeEvent.class, "[entity] [remaining] air chang(e|ing) [(of|for) %-entitydatas%]");
    }

    Literal<EntityData<?>> entities;

    @SuppressWarnings("unchecked")
    @Override
    public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult) {
        entities = (Literal<EntityData<?>>) args[0];
        return true;
    }

    @Override
    public boolean check(Event e) {
        if (entities != null) {
            Entity entity = ((EntityAirChangeEvent)e).getEntity();
            return entities.check(e, new Checker<EntityData<?>>() {
                @Override
                public boolean check(EntityData<?> data) {
                    return data.isInstance(entity);
                }
            });
        }
        return true;
    }

    @Override
    public String toString(@Nullable Event e, boolean debug) {
        return "Remaining air event " + entities.toString(e, debug);
    }

}

We can now use the following;

on entity air changing for zombies and humans:
    set {_air} to remaining air of entity
    broadcast "%entity%'s air has changed to %{_air}%"

Since there is already an expression for getting air we can use this. I will still go into EventValues and how to get it, but in this case creating an expression for getting the remaining air is better than event-number.

Now for simple events. If an event doesn't have a good way of implementing Literals, it's best to use SImpleEvent, and perks of using this registration is that you get to skip the whole creation of making a class. Examples of SimpleEvent from Skript may be found here https://github.com/SkriptLang/Skript/blob/master/src/main/java/ch/njol/skript/events/SimpleEvents.java

For this example I will be doing https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/player/PlayerRiptideEvent.html because it's not an event that is already added to Skript and we can use it in SimpleEvent because we can't really grab a player in an event Literal.
This event is called when a player Riptides with a Trident (It's an enchantment, 1.13).

Skript.registerEvent("Player Riptide", SimpleEvent.class, PlayerRiptideEvent.class, "[player] [trident] riptid(e|ing)");

That's it, the event is now registered. Easy. You can place this in a new class called SimpleEvents with a static call just like Skript's if you wish, this method can be placed anywhere, it just has to register. In the second parameter we use SimpleEvent in replacement of our class because we don't need to check for literals or anything, we just want to call this event when it happens, Simple.

Since this is a player event in Spigot, Skript will register the %player% for this event. The following should now work;

on player riptide:
    broadcast "%player% just riptided"


Lets talk EventValues,

EventValues are that value you can add to events that begin with event-something, keep in mind that all event-values don't require the "event-" part, so something like event-entity can just be entity.

EventValues can be useful for custom types you may have implemented, getting an Entity in your custom event, or anything you may need more values for.

Examples of EventValues from Skript may be found here https://github.com/SkriptLang/Skript/blob/master/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java

Lets make an EventValue for getting the event-number (remaining air) of the entity in our first Skript event (as said above this isn't a proper way to add the ability to get this, it should be an expression, this is for learning purposes);

EventValues.registerEventValue(EntityAirChangeEvent.class, Number.class, new Getter<Number, EntityAirChangeEvent>() {
    @Override
    @Nullable
    public Number get(EntityAirChangeEvent e) {
        return e.getAmount();
    }
}, 0);

You access the EventValue register static methods to do the above.
The first parameter is the event you want to add the event-value to.
The second parameter is the Type that you want to have return in the event-value, keep in mind if this is a custom type, that it should have a ClassInfo registered, there is another tutorial in here about ClassInfos
The third parameter is a Getter, and similar to a Checker it grabs values rather than checking on them. This Getter will help you get all the values in an event, and allow you to decide the return of the event value.
In a Getter the return type is the first Generic Type and the Event is the second Generic Type "new Getter<Number, EntityAirChangeEvent>() {", in my opinion, it should be reversed but it's not feasible to change now.

This will make the rest of the code generate. In the get method, what ever the second Generic is will be asked as the main object to get from. So in this case it's an event, we need to get the amount of air left on the entity involved in the event, so there is a method in the event called "getAmount()" and as simple as it is, we just return that. So now "event-number" should be registered and you can start using it in your script coding. Again this isn't proper implementation, and should be done through expressions like Skript has already with the remaining air.

Lastly the fourth parameter you may have missed but it's a integer, this integer can be -1, 0 or 1.
This number represents the time state of the event value. There is a feature in Skript that allows expressions to have a time state, meaning the skript user can't or has to use the delay effect.
It's harder to understand, I will give an example from SkQuery, there is a block fall event, and it has multiple event-values for this event. If the event-value is called right away without any delay, it will be the block that this falling block is landing on (for example dirt or sand if it was in the world) and if you wait a tick the event-value will now be the actual block that landed.

Hopefully you understand that, if not you can ask developers about this feature or myself LimeGlass.

-1 means past state
0 means present
1 means future.

You can use this like a so

future event-entity
past entity

future event-block
block
past block

In my opinion I have played around with EventValue time states the past few years and they appear to not work as expected, they have some internal issues that cause them to not work properly, and there also isn't that many uses for these time values, so if in doubt just use 0, this makes the event-value work in past and future states. The issue of these time values not working properly can be viewed at this issue report https://github.com/SkriptLang/Skript/issues/671




Addon tutorial
Back | Next