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.

Skript Skript Addon Best Practices

Discussion in 'Tutorials' started by btk5h, Feb 26, 2017.

  1. btk5h

    Addon Developer

    Joined:
    Jan 25, 2017
    Messages:
    154
    Likes Received:
    158
    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
    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.
    Code (Java):
    1. public class MyMainClass extends JavaPlugin {
    2.   private static MyMainClass instance;
    3.  
    4.   public MyMainClass() {
    5.     if (instance == null) {
    6.       instance = this;
    7.     } else {
    8.       throw new IllegalStateException();
    9.     }
    10.   }
    11.  
    12.   public static MyMainClass getInstance() {
    13.     if (instance == null) {
    14.       throw new IllegalStateException();
    15.     }
    16.     return instance;
    17.   }
    18. }
    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.
    Code (Java):
    1. public class MyMainClass extends JavaPlugin {
    2.   private static MyMainClass instance;
    3.   private static SkriptAddon addonInstance;
    4.  
    5.   public MyMainClass() {
    6.     if (instance == null) {
    7.       instance = this;
    8.     } else {
    9.       throw new IllegalStateException();
    10.     }
    11.   }
    12.  
    13.   public static SkriptAddon getAddonInstance() {
    14.     if (addonInstance == null) {
    15.       addonInstance = Skript.registerAddon(getInstance());
    16.     }
    17.     return addonInstance;
    18.   }
    19.  
    20.   public static MyMainClass getInstance() {
    21.     if (instance == null) {
    22.       throw new IllegalStateException();
    23.     }
    24.     return instance;
    25.   }
    26. }
    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.
    Code (Java):
    1. @Override
    2. public void onEnable() {
    3.   try {
    4.     getAddonInstance().loadClasses("path.to.base.package", "subpackage");
    5.   } catch (IOException e) {
    6.     e.printStackTrace();
    7.   }
    8. }
    "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
    Code (Java):
    1. 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.
    Code (Java):
    1. public class MyEffect extends Effect {
    2.   static {
    3.     Skript.registerEffect(MyEffect.class, "syntax")
    4.   }
    5. }
    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 (Text):
    1. [the] <property> of %<fromType>%
    2. %<fromType>%'[s] <property>
    If the property doesn't contain any other expressions, you can extend SimplePropertyExpression to avoid most of the boilerplate.
    Code (Java):
    1. public class MyProperty extends SimplePropertyExpression<Player, Entity> {
    2.   static {
    3.     // you can call this expression like "pet of {_player}" or "{_player}'s pet"
    4.     PropertyExpression.register(MyProperty.class, Entity.class, "pet", "players");
    5.   }
    6.  
    7.   @Override
    8.   protected String getPropertyName() {
    9.     return "pet";
    10.   }
    11.  
    12.   @Override
    13.   public Entity convert(Player player) {
    14.     // return the player's pet here
    15.   }
    16.  
    17.   @Override
    18.   public Class<? extends Entity> getReturnType() {
    19.     return Entity.class;
    20.   }
    21. }
    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
     
    #1 btk5h, Feb 26, 2017
    Last edited: Apr 1, 2017
    • Informative Informative x 3
    • Useful Useful x 1
  2. ShaneBee

    Moderator Resource Staff Supporter + Addon Developer

    Joined:
    Sep 7, 2017
    Messages:
    2,181
    Likes Received:
    219
    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 (Skript):
    1.  
    2. #SkQuery
    3. make %players% see %block% as %blocktype%
    4.  
    5. #Example addon
    6. [example[sk]] (force|make) %players% see %block% as %blocktype% [for %timespan%]
    7.  
    8. #blocktype is example expression(or exists?!)
    9.  
    I can't provide syntax better than this one.
     
    #2 ShaneBee, Mar 30, 2017
    Last edited by a moderator: Mar 30, 2017
  3. ShaneBee

    Moderator Resource Staff Supporter + Addon Developer

    Joined:
    Sep 7, 2017
    Messages:
    2,181
    Likes Received:
    219
    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.
     
  4. ShaneBee

    Moderator Resource Staff Supporter + Addon Developer

    Joined:
    Sep 7, 2017
    Messages:
    2,181
    Likes Received:
    219
    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.
     
  5. ShaneBee

    Moderator Resource Staff Supporter + Addon Developer

    Joined:
    Sep 7, 2017
    Messages:
    2,181
    Likes Received:
    219
    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
     
  6. ShaneBee

    Moderator Resource Staff Supporter + Addon Developer

    Joined:
    Sep 7, 2017
    Messages:
    2,181
    Likes Received:
    219
    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.
     
  7. ShaneBee

    Moderator Resource Staff Supporter + Addon Developer

    Joined:
    Sep 7, 2017
    Messages:
    2,181
    Likes Received:
    219
    @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?
     
  8. ShaneBee

    Moderator Resource Staff Supporter + Addon Developer

    Joined:
    Sep 7, 2017
    Messages:
    2,181
    Likes Received:
    219
    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.
     

Share This Page

Loading...