Skript Skript Addon Best Practices

  • 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!

btk5h

Addon Developer
Jan 25, 2017
154
159
0
Pyongyang, North Korea
This tutorial was written to help explain Skript's poorly documented API and to promote quality standards for Skript addons.

This tutorial is a work-in-progress and will be updated frequently. Feedback is appreciated.

Limit the scope of your addon
Addons should solve a single problem. Unrelated features should be kept in separate addons. Most Minecraft features (features that don't require NMS) should be added to Skript itself, not into a new addon.

Splitting features into multiple addons has quite a few benefits:
  • It's easier for users to remember which addon adds which features.
  • It's easier for developers to contribute to your addon when it's designed to solve a specific issue.
  • Users download less unnecessary content if they don't use your entire addon.
  • Addons are more modular and less likely to conflict.
As a rule of thumb, if the best way to describe your addon is with your name or with some random word, you should consider splitting features into different addons.

Register your addon
Skript's documentation said:
This is currently not required for addons to work, but the returned SkriptAddon provides useful methods for registering syntax elements and adding new strings to Skript's localization system (e.g. the required "types.[type]" strings for registered classes).
Registering your addon is a prerequisite for many of the practices described later in this tutorial.

Keep a reference to your addon's main class in the constructor.
Java:
public class MyMainClass extends JavaPlugin {
  private static MyMainClass instance;

  public MyMainClass() {
    if (instance == null) {
      instance = this;
    } else {
      throw new IllegalStateException();
    }
  }

  public static MyMainClass getInstance() {
    if (instance == null) {
      throw new IllegalStateException();
    }
    return instance;
  }
}
This is a common pattern used by many Bukkit plugins, allowing you to retrieve a plugin instance from anywhere by calling MyMainClass.getInstance().

Create a field for a SkriptAddon and write a getter that registers your addon when it is called the first time.
Java:
public class MyMainClass extends JavaPlugin {
  private static MyMainClass instance;
  private static SkriptAddon addonInstance;

  public MyMainClass() {
    if (instance == null) {
      instance = this;
    } else {
      throw new IllegalStateException();
    }
  }

  public static SkriptAddon getAddonInstance() {
    if (addonInstance == null) {
      addonInstance = Skript.registerAddon(getInstance());
    }
    return addonInstance;
  }

  public static MyMainClass getInstance() {
    if (instance == null) {
      throw new IllegalStateException();
    }
    return instance;
  }
}
You can now call MyMainClass.getAddonInstance() to get your addon's SkriptAddon instance. Note that because MyMainClass.getAddonInstance() has not been called, the addon hasn't been registered yet. The next part of the tutorial will call this method, registering your addon.

Practical Example

Use Skript's class loader
Many addons register new syntax from within the addon's main class or from a utility class. While this approach works, using Skript's class loading utility allows you to keep your registration logic inside the same class as the expression/effect.

In order to start using Skript's class loader, you must call loadClasses() from your main class's onEnable. loadClasses() can throw an IOException, so you must catch wrap the statement in a try-catch block.
Java:
@Override
public void onEnable() {
  try {
    getAddonInstance().loadClasses("path.to.base.package", "subpackage");
  } catch (IOException e) {
    e.printStackTrace();
  }
}
"path.to.base.package" and "subpackage" should refer to the package (folder) containing your registrable classes. For example, if the classes for your syntax are located in com.example.myaddon.skript, "path.to.base.package" should be replaced with "com.example.myaddon" and "subpackage" should be replaced with "skript".

If your classes are split into multiple packages, you can call loadClasses() with multiple subpackages. If your syntax is located in com.example.myaddon.effects and com.example.myaddon.expressions, you can call
Java:
getAddonInstance().loadClasses("com.example.myaddon", "effects", "expressions")
This does not need to be called for subclasses (e.g. com.example.myaddon.skript.subpackage is already registered if com.example.myaddon.skript is registered)

When Skript loads, all classes from registered packages will be initialized, executing any static initializers. Simply move your registration code to each class's static initalizer block.
Java:
public class MyEffect extends Effect {
  static {
    Skript.registerEffect(MyEffect.class, "syntax")
  }
}

Practical Example

Write user friendly syntax
Your addon's syntax should be consistent with Skript's syntax. Remember, Skript's syntax was designed to be readable and English-like.

Don't prefix your syntax with the name of your addon
Conflicts should not be an issue if you limit the scope of your addon, as explained above.

Use PropertyExpression#register when possible
PropertyExpression#register registers the following expressions:
Code:
[the] <property> of %<fromType>%
%<fromType>%'[s] <property>
If the property doesn't contain any other expressions, you can extend SimplePropertyExpression to avoid most of the boilerplate.
Java:
public class MyProperty extends SimplePropertyExpression<Player, Entity> {
  static {
    // you can call this expression like "pet of {_player}" or "{_player}'s pet"
    PropertyExpression.register(MyProperty.class, Entity.class, "pet", "players");
  }

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

  @Override
  public Entity convert(Player player) {
    // return the player's pet here
  }

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

Use PropertyCondition#register when possible
WIP, this is the same concept as using PropertyExpression

Do not perform expensive operations in expressions
Expensive operations, such as performing file I/O or contacting other servers, must not be performed in expressions. Instead, perform the action asynchronously in an effect and make the result available in a last <name of expression> expression.

The current trigger can be paused (allowing the server and other code to run) by overriding the instance method TriggerItem.walk(Event) and manually calling the static method TriggerItem.walk(TriggerItem, Event) when your action completes.

Practical Example
 
Last edited:
I can't say that you promote quality of addons.

For example, syntax prefix is a good thing when you have same features as another addon but with extra stuff:
code_language.skript:
#SkQuery
make %players% see %block% as %blocktype%

#Example addon
[example[sk]] (force|make) %players% see %block% as %blocktype% [for %timespan%]

#blocktype is example expression(or exists?!)
I can't provide syntax better than this one.
 
Last edited by a moderator:
Although, if you do that, do it smartly. All of my syntaxes are inherently prefixed with "[quark] " to avoid me forgetting to prefix it. It also helps following DRY.
 
I can't say that you promote quality of addons.

For example, syntax prefix is a good thing when you have same features as another addon but with extra stuff:
code_language.skript:
#SkQuery
make %players% see %block% as %blocktype%

#Example addon
[example[sk]] (force|make) %players% see %block% as %blocktype% (for %timespan%)

#blocktype is example expression(or exists?!)
I can't provide syntax better than this one.
There may be a few cases where an addon prefix would help, but for the most part, prefixes are unnecessary. Most features will have different syntax and addons should rarely have overlapping features. Even in cases where addons have overlapping features, chances are that either implementation is acceptable. In your example, the features don't conflict, even without the prefix. The example addon effect will run if you decide you need the extra features provided by example addon.

I understand this is may be a problem with bulky addons (like skQuery); however, as I've stated, this shouldn't be a problem with modularized addons.
 
There may be a few cases where an addon prefix would help, but for the most part, prefixes are unnecessary. Most features will have different syntax and addons should rarely have overlapping features. Even in cases where addons have overlapping features, chances are that either implementation is acceptable. In your example, the features don't conflict, even without the prefix. The example addon effect will run if you decide you need the extra features provided by example addon.

I understand this is may be a problem with bulky addons (like skQuery); however, as I've stated, this shouldn't be a problem with modularized addons.
SkQuery is most used addon so it must be at high priority.

About my example - i fixed syntax so they both(SkQuery and ExampleSk) will be fired with "make %players% see %block% as %blocktype%" and throw error
 
SkQuery is most used addon so it must be at high priority.

About my example - i fixed syntax so they both(SkQuery and ExampleSk) will be fired with "make %players% see %block% as %blocktype%" and throw error
skQuery is deprecated. skQuery's features should be gradually migrated to Skript or separate addons as appropriate. I urge maintainers of skQuery to not add new features.

With your example, Skript will resolve to a single addon's implementation. Both are not used. Addons can ensure that Skript prefers their feature by changing when the plugin loads.
 
skQuery is deprecated. skQuery's features should be gradually migrated to Skript or separate addons as appropriate. I urge maintainers of skQuery to not add new features.

With your example, Skript will resolve to a single addon's implementation. Both are not used. Addons can ensure that Skript prefers their feature by changing when the plugin loads.
@RepublicanSensei as i remember updated SkQuery for newest versions and fixed/removed broken things so talking about migration could be used for any addon(as 1000 times was said) and make Skript addonless?
 
@RepublicanSensei as i remember updated SkQuery for newest versions and fixed/removed broken things so talking about migration could be used for any addon(as 1000 times was said) and make Skript addonless?
Addons are still important. Skript's core should contain stable Minecraft features. Addons should contain more unstable/complex features like inventory menus, map rendering, and I/O.