Creating a Type/ClassInfo
Lets create a class info. Class infos are basically that input the Skripter has to add. You know %player%, %string% %damagecause% all these are considered classinfos. Skript registers some default classinfos which can be found here for reference after reading this tutorial https://github.com/SkriptLang/Skript/blob/master/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java
In this example we'll make a class info for a pillager raid from 1.14. https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Raid.html
To start you will need to create a classinfo and then add it to the "ch.njol.skript.registrations.Classes" class. Classes.registerClass(ClassInfo);
So here is our simple class register system
This class is contained within our elements package, that way when Skript goes to register, it will also go into this class using our static method. This is because we defined Skript to register everything in the elements package, back on the first or second tutorial page.
The first thing of the ClassInfo class you have to define is the class of which will be used for the classinfo, in our case the actual Raid object of the raid events. The string is the classinfo name. This is what is used in the %object% input if you don't define the user. The user is a regex string that will check for the name used from the syntax input, in other words if anything between the percent signs %% matches that regex, it will be targeted to your classinfo. In our case I made a s optional as the user may define multiple raids. The question mark in regex means the element to it's left will be optional. You can use entit(y|ies) for types with usage of that for proper grammar.
The name is just a general name of the classinfo.
Description and examples are optional but I do suggest adding them.
There is a since method but it's only used internally and was planned for addons to use it aswell, but that was never implemented. So you don't need to use the since method, but for syntax the since annotation does respect addons. There is also a usage() method which tells the user how to use the classinfo.
The defaultExpression method will register an event value expression for you based on the class you're registering. This is what will create event-%classinfo% for you so event-raid or just raid. If you want to have something custom like adding changers to the event value only, you can extend the event value expression as it's own expression, a good example of this is ExprItem https://github.com/SkriptLang/Skript/blob/master/src/main/java/ch/njol/skript/expressions/ExprItem.java
SpawnReason and TeleportReason expressions from Skript don't need to be registered as their own EventValueExpression because they're simple and only doing that so they can get a custom syntax, so they're not a great example.
You can also add changers to the classinfo itself instead of when it's just the event value, which I will get into later in this tutorial.
Now to get into the parser. The parser is what allows Skript and scripters to parse things like "LimeGlass" parsed as offlineplayer. You can use this to add parsing to your class info. In our case it makes no sense to allow parsing on a raid, they should be using an effect rather than parsing. By default Skript enables parsing and then throws when the classinfo doesn't override the parse method which is a poor system, it should be false by default. So in our case we need to tell skript NO. So we override the canParse method, we also return null on the parse method even though it will never be called if it's false, the Parser just requires it to be overriden.
The toVariableNameString is an over complex method name I know lol, When a user uses our classinfo within a variable like {_raids::%event-raid%} this method will be called to turn the object into a string, because everything in Skript's databases aka variables need to be a string, this is database managment. So in our case it's not really something that should be used in a variable name, so I will just return the location of the raid. The getVariableNamePattern is a regex pattern that needs to match whatever you just input in the toVariableNameString method, it's used to determine if the user messed up and to test for variable conflicts, it's required because it helps Skript debug issues.
The toString is mainly used for debugging and error reporting, so you can return whatever might be helpful to debug issues.
Now on to more complex methods within the ClassInfo class that aren't used in the basic ClassInfo above.
There is after() and before() which tell Skript to register our ClassInfo after another ClassInfo, for example call direction after location since direction uses locations which is what Skript does for these two classinfos.
There is changers() which I mentioned above in the event value section. You can define set, add, remove and such on the classinfo, so you can use something like add player to event-raid even though that's not something that would be added to a raid. It's similar to expressions which I went over in the expressions section. It's easy to understand, the only thing difference is that the Changer object is directly created rather than be included in the Abstract expression class. Again you can view the Skript BukkitClasses.class for examples on this like entities. There is also DefaultChangers in Skript which register general changers like blocks, location and entities. That can be used for reference too.
You can use the math() method to add an Arithmetic object to the classinfo. This basically allows scripters to do math on your classinfo if you want to allow that. Like add 1 to event-raid etc event-raid / 2. If there was a reasonable sense to using it, in our case raids don't need this feature. It's more for numbers like double, floats and integers. (Which are broken anyways lol)
Next is the serializer this allows Skript to write the Raid classinfo to the database, like when a player saves a global variable instead of using a local variable. In our case Raids do persist after server restarts so we will have to add a serializer. Things like entities cannot be saved. Entities will be despawned on server restart and spawned as a clone when the server starts, it won't be the exact same entity, it will have a different entity id which is why Skript doesn't serialize them. Bossbars are another example, but we can use the serializer to recreate the bossbar, Basically just make a new bossbar when it's called with the same properties, it can be done the same way as I will express below.
To start you need to add the serialize() method and create a new Serializer. Njol created his own serializer called Yggdrasil named after the mythical tree in norse cosmology, Njol made Yggdrasil because Gson didn't exist back then and Java's serializer was too heavy for such a simple task of Skript. It's included with Skript so you don't need to worry about dependencies if you have Skript already.
We need to put the object into the fields, so with the serialize method we add everything.
Then when deserializing we need to turn those into a Raid object. We can't create a Raid so we shouldn't really be serializing a Raid and since it's kinda weird storing a raid. But for example we can grab from the fields in the deserialize(fields) method String name = fields.getAndRemovePrimitive("name", String.class);
We then return the Raid object. The deserialize with the object already as a parameter is used when it gets instantiated which is a method that can be overrided to set that. If it's true Yggdrasil will create the object and send it though the method in some cases. It's not really needed when you can just build one from the method that requires the return of the Raid, but if you can't this can be used.
mustSyncDeserialization is not used currently but in my opinion it should, since Skript has started to evolve into not just Bukkit things anymore. This was suppose to be a method to define if it can be determined async or not from the Minecraft thread.
And that's a serializer. If you're using a Enum as the ClassInfo, you can use the Enum Serializer that bensku and Pika added awhile back. You can view BukkitClasses at the bottom for examples, You can also use serializeAs.
In this example we'll make a class info for a pillager raid from 1.14. https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Raid.html
To start you will need to create a classinfo and then add it to the "ch.njol.skript.registrations.Classes" class. Classes.registerClass(ClassInfo);
So here is our simple class register system
public class ClassInfos {
static {
Classes.registerClass(new ClassInfo<>(Raid.class, "raid")
.user("raids?")
.name("Raid")
.description("Represents a raid from a pillager raid on a village.")
.examples("on raid start:", "\tbroadcast \"A raid has started at level %omen level of event-raid%\"")
.defaultExpression(new EventValueExpression<>(Raid.class))
.parser(new Parser<Raid>() {
@Override
@Nullable
public Raid parse(String input, ParseContext context) {
return null;
}
@Override
public boolean canParse(ParseContext context) {
return false;
}
@Override
public String toVariableNameString(Raid raid) {
Location location = raid.getLocation();
return "raid:" + location.getX() + "," + location.getY() + "," + location.getZ();
}
@Override
public String getVariableNamePattern() {
return "raid:[0-9],[0-9],[0-9]";
}
@Override
public String toString(Raid raid, int flags) {
return toVariableNameString(raid);
}
}));
}
}
static {
Classes.registerClass(new ClassInfo<>(Raid.class, "raid")
.user("raids?")
.name("Raid")
.description("Represents a raid from a pillager raid on a village.")
.examples("on raid start:", "\tbroadcast \"A raid has started at level %omen level of event-raid%\"")
.defaultExpression(new EventValueExpression<>(Raid.class))
.parser(new Parser<Raid>() {
@Override
@Nullable
public Raid parse(String input, ParseContext context) {
return null;
}
@Override
public boolean canParse(ParseContext context) {
return false;
}
@Override
public String toVariableNameString(Raid raid) {
Location location = raid.getLocation();
return "raid:" + location.getX() + "," + location.getY() + "," + location.getZ();
}
@Override
public String getVariableNamePattern() {
return "raid:[0-9],[0-9],[0-9]";
}
@Override
public String toString(Raid raid, int flags) {
return toVariableNameString(raid);
}
}));
}
}
This class is contained within our elements package, that way when Skript goes to register, it will also go into this class using our static method. This is because we defined Skript to register everything in the elements package, back on the first or second tutorial page.
The first thing of the ClassInfo class you have to define is the class of which will be used for the classinfo, in our case the actual Raid object of the raid events. The string is the classinfo name. This is what is used in the %object% input if you don't define the user. The user is a regex string that will check for the name used from the syntax input, in other words if anything between the percent signs %% matches that regex, it will be targeted to your classinfo. In our case I made a s optional as the user may define multiple raids. The question mark in regex means the element to it's left will be optional. You can use entit(y|ies) for types with usage of that for proper grammar.
The name is just a general name of the classinfo.
Description and examples are optional but I do suggest adding them.
There is a since method but it's only used internally and was planned for addons to use it aswell, but that was never implemented. So you don't need to use the since method, but for syntax the since annotation does respect addons. There is also a usage() method which tells the user how to use the classinfo.
The defaultExpression method will register an event value expression for you based on the class you're registering. This is what will create event-%classinfo% for you so event-raid or just raid. If you want to have something custom like adding changers to the event value only, you can extend the event value expression as it's own expression, a good example of this is ExprItem https://github.com/SkriptLang/Skript/blob/master/src/main/java/ch/njol/skript/expressions/ExprItem.java
SpawnReason and TeleportReason expressions from Skript don't need to be registered as their own EventValueExpression because they're simple and only doing that so they can get a custom syntax, so they're not a great example.
You can also add changers to the classinfo itself instead of when it's just the event value, which I will get into later in this tutorial.
Now to get into the parser. The parser is what allows Skript and scripters to parse things like "LimeGlass" parsed as offlineplayer. You can use this to add parsing to your class info. In our case it makes no sense to allow parsing on a raid, they should be using an effect rather than parsing. By default Skript enables parsing and then throws when the classinfo doesn't override the parse method which is a poor system, it should be false by default. So in our case we need to tell skript NO. So we override the canParse method, we also return null on the parse method even though it will never be called if it's false, the Parser just requires it to be overriden.
The toVariableNameString is an over complex method name I know lol, When a user uses our classinfo within a variable like {_raids::%event-raid%} this method will be called to turn the object into a string, because everything in Skript's databases aka variables need to be a string, this is database managment. So in our case it's not really something that should be used in a variable name, so I will just return the location of the raid. The getVariableNamePattern is a regex pattern that needs to match whatever you just input in the toVariableNameString method, it's used to determine if the user messed up and to test for variable conflicts, it's required because it helps Skript debug issues.
The toString is mainly used for debugging and error reporting, so you can return whatever might be helpful to debug issues.
Now on to more complex methods within the ClassInfo class that aren't used in the basic ClassInfo above.
There is after() and before() which tell Skript to register our ClassInfo after another ClassInfo, for example call direction after location since direction uses locations which is what Skript does for these two classinfos.
There is changers() which I mentioned above in the event value section. You can define set, add, remove and such on the classinfo, so you can use something like add player to event-raid even though that's not something that would be added to a raid. It's similar to expressions which I went over in the expressions section. It's easy to understand, the only thing difference is that the Changer object is directly created rather than be included in the Abstract expression class. Again you can view the Skript BukkitClasses.class for examples on this like entities. There is also DefaultChangers in Skript which register general changers like blocks, location and entities. That can be used for reference too.
You can use the math() method to add an Arithmetic object to the classinfo. This basically allows scripters to do math on your classinfo if you want to allow that. Like add 1 to event-raid etc event-raid / 2. If there was a reasonable sense to using it, in our case raids don't need this feature. It's more for numbers like double, floats and integers. (Which are broken anyways lol)
Next is the serializer this allows Skript to write the Raid classinfo to the database, like when a player saves a global variable instead of using a local variable. In our case Raids do persist after server restarts so we will have to add a serializer. Things like entities cannot be saved. Entities will be despawned on server restart and spawned as a clone when the server starts, it won't be the exact same entity, it will have a different entity id which is why Skript doesn't serialize them. Bossbars are another example, but we can use the serializer to recreate the bossbar, Basically just make a new bossbar when it's called with the same properties, it can be done the same way as I will express below.
To start you need to add the serialize() method and create a new Serializer. Njol created his own serializer called Yggdrasil named after the mythical tree in norse cosmology, Njol made Yggdrasil because Gson didn't exist back then and Java's serializer was too heavy for such a simple task of Skript. It's included with Skript so you don't need to worry about dependencies if you have Skript already.
public class ClassInfos {
static {
Classes.registerClass(new ClassInfo<>(Raid.class, "raid")
.user("raids?")
.name("Raid")
.description("Represents a raid from a pillager raid on a village.")
.examples("on raid start:", "\tbroadcast \"A raid has started at level %omen level of event-raid%\"")
.defaultExpression(new EventValueExpression<>(Raid.class))
.parser(new Parser<Raid>() {
@Override
@Nullable
public Raid parse(String input, ParseContext context) {
return null;
}
@Override
public boolean canParse(ParseContext context) {
return false;
}
@Override
public String toVariableNameString(Raid raid) {
Location location = raid.getLocation();
return "raid:" + location.getX() + "," + location.getY() + "," + location.getZ();
}
@Override
public String getVariableNamePattern() {
return "raid:[0-9],[0-9],[0-9]";
}
@Override
public String toString(Raid raid, int flags) {
return toVariableNameString(raid);
}
}).serializer(new Serializer<Raid>() {
@Override
public Fields serialize(Raid raid) throws NotSerializableException {
Fields fields = new Fields();
fields.putPrimitive("omen-level", raid.getBadOmenLevel());
fields.putPrimitive("spawned", raid.getSpawnedGroups());
fields.putPrimitive("health", raid.getTotalHealth());
fields.putPrimitive("groups", raid.getTotalGroups());
fields.putPrimitive("ticks", raid.getActiveTicks());
fields.putPrimitive("waves", raid.getTotalWaves());
fields.putPrimitive("started", raid.isStarted());
fields.putObject("location", raid.getLocation());
fields.putObject("raiders", raid.getRaiders());
fields.putObject("status", raid.getStatus());
fields.putObject("heros", raid.getHeroes());
return fields;
}
@Override
public Raid deserialize(Fields fields) throws StreamCorruptedException {
//return new Raid(etc);
return null;
}
@Override
public void deserialize(Raid raid, Fields fields) throws StreamCorruptedException, NotSerializableException {
assert false;
}
@Override
public boolean mustSyncDeserialization() {
return true;
}
@Override
protected boolean canBeInstantiated() {
return false;
}
}));
}
}
static {
Classes.registerClass(new ClassInfo<>(Raid.class, "raid")
.user("raids?")
.name("Raid")
.description("Represents a raid from a pillager raid on a village.")
.examples("on raid start:", "\tbroadcast \"A raid has started at level %omen level of event-raid%\"")
.defaultExpression(new EventValueExpression<>(Raid.class))
.parser(new Parser<Raid>() {
@Override
@Nullable
public Raid parse(String input, ParseContext context) {
return null;
}
@Override
public boolean canParse(ParseContext context) {
return false;
}
@Override
public String toVariableNameString(Raid raid) {
Location location = raid.getLocation();
return "raid:" + location.getX() + "," + location.getY() + "," + location.getZ();
}
@Override
public String getVariableNamePattern() {
return "raid:[0-9],[0-9],[0-9]";
}
@Override
public String toString(Raid raid, int flags) {
return toVariableNameString(raid);
}
}).serializer(new Serializer<Raid>() {
@Override
public Fields serialize(Raid raid) throws NotSerializableException {
Fields fields = new Fields();
fields.putPrimitive("omen-level", raid.getBadOmenLevel());
fields.putPrimitive("spawned", raid.getSpawnedGroups());
fields.putPrimitive("health", raid.getTotalHealth());
fields.putPrimitive("groups", raid.getTotalGroups());
fields.putPrimitive("ticks", raid.getActiveTicks());
fields.putPrimitive("waves", raid.getTotalWaves());
fields.putPrimitive("started", raid.isStarted());
fields.putObject("location", raid.getLocation());
fields.putObject("raiders", raid.getRaiders());
fields.putObject("status", raid.getStatus());
fields.putObject("heros", raid.getHeroes());
return fields;
}
@Override
public Raid deserialize(Fields fields) throws StreamCorruptedException {
//return new Raid(etc);
return null;
}
@Override
public void deserialize(Raid raid, Fields fields) throws StreamCorruptedException, NotSerializableException {
assert false;
}
@Override
public boolean mustSyncDeserialization() {
return true;
}
@Override
protected boolean canBeInstantiated() {
return false;
}
}));
}
}
We need to put the object into the fields, so with the serialize method we add everything.
Then when deserializing we need to turn those into a Raid object. We can't create a Raid so we shouldn't really be serializing a Raid and since it's kinda weird storing a raid. But for example we can grab from the fields in the deserialize(fields) method String name = fields.getAndRemovePrimitive("name", String.class);
We then return the Raid object. The deserialize with the object already as a parameter is used when it gets instantiated which is a method that can be overrided to set that. If it's true Yggdrasil will create the object and send it though the method in some cases. It's not really needed when you can just build one from the method that requires the return of the Raid, but if you can't this can be used.
mustSyncDeserialization is not used currently but in my opinion it should, since Skript has started to evolve into not just Bukkit things anymore. This was suppose to be a method to define if it can be determined async or not from the Minecraft thread.
And that's a serializer. If you're using a Enum as the ClassInfo, you can use the Enum Serializer that bensku and Pika added awhile back. You can view BukkitClasses at the bottom for examples, You can also use serializeAs.