Skip to content

Latest commit

 

History

History
239 lines (176 loc) · 9.56 KB

File metadata and controls

239 lines (176 loc) · 9.56 KB

Addon API Documentation

The Addon API allows external Bukkit/Paper plugins to extend MinecraftLuaScripting by registering custom Lua API tables that become available inside every loaded script.

Overview

An addon is a separate plugin that:

  1. Declares depend: [ MinecraftLuaScripting ] in its plugin.yml.
  2. Obtains the MinecraftLuaScripting plugin instance at runtime via MinecraftLuaScripting.getInstance() (or the Bukkit ServicesManager).
  3. Creates an Addon subclass that exposes one or more AddonApi tables.
  4. Registers the addon through mls.getAddonManager().registerAddon(this, addon), passing its own plugin instance as the owner so the addon is automatically unregistered when the owning plugin is disabled.

Each AddonApi table is installed as a global Lua variable with the name returned by getApiName(). New scripts loaded after the addon is registered automatically receive the APIs.

Java API

Obtaining the plugin instance

MinecraftLuaScripting mls = MinecraftLuaScripting.getInstance();

or via the ServicesManager:

RegisteredServiceProvider<MinecraftLuaScripting> provider =
        Bukkit.getServicesManager().getRegistration(MinecraftLuaScripting.class);
MinecraftLuaScripting mls = provider != null ? provider.getProvider() : null;

Addon

Base class all addons must extend.

public abstract class Addon {
    public abstract Set<AddonApi> getApiTables();
    public abstract String getName();
    public abstract String getVersion();

    // optional lifecycle hooks
    public void onEnable(MinecraftLuaScripting plugin);
    public void onDisable();
    public void onScriptsReloaded();

    public void log(String message);
    public void logWarning(String message);
    public void logSevere(String message);
}

Methods

  • getApiTables(): Returns the set of API tables this addon exposes.
  • getName(): Returns the addon name. Must be non-empty and unique among registered addons.
  • getVersion(): Returns the addon version string.
  • onEnable(plugin): Called after the addon has been registered and its APIs applied to the Lua state.
  • onDisable(): Called after the addon has been unregistered and its APIs removed.
  • onScriptsReloaded(): Called after /mls reloadscripts recreates the Lua state and re-applies the addon's APIs.
  • log, logWarning, logSevere: Convenience loggers prefixed with the addon name.

AddonApi

Base class for each Lua API table an addon exposes. There are three ways to define the table contents:

1. @LuaFunction annotations (recommended)

public class MyApi extends AddonApi {
    @Override
    public String getApiName() { return "MyApi"; }

    @LuaFunction
    public LuaValue greet(LuaValue name) {
        return LuaValue.valueOf("Hello, " + name.tojstring());
    }

    @LuaFunction("add")
    public LuaValue addNumbers(LuaValue a, LuaValue b) {
        return LuaValue.valueOf(a.checkint() + b.checkint());
    }
}

Annotated methods must return LuaValue (or void) and take either zero or more LuaValue parameters or a single LuaValue[] parameter for varargs. Missing arguments are passed as LuaValue.NIL. The Lua field name defaults to the method name and can be overridden via the annotation value.

2. Fluent builder (no subclassing)

AddonApi api = AddonApi.builder("MyApi")
    .function("greet", args -> LuaValue.valueOf("Hello, " + args[0].tojstring()))
    .constant("version", LuaValue.valueOf("1.0"))
    .build();

3. getTableContents() override (classic)

public abstract class AddonApi extends LuaTable {
    public Map<String, LuaValue> getTableContents(); // optional, defaults to null
    public abstract String getApiName();
    public static Builder builder(String apiName);
}
  • getApiName(): Name of the global Lua variable that will hold this table.
  • getTableContents(): Optional map of Lua field names to LuaValues.

AddonFunction

Base class for Lua functions exposed by addons. It transparently handles both dot syntax (MyApi.fn(...)) and colon syntax (MyApi:fn(...)) by stripping the implicit self argument, so addon authors never need to worry about the calling convention:

public abstract class AddonFunction extends VarArgFunction {
    protected abstract LuaValue execute(LuaValue[] args);
}

Exceptions thrown from execute are wrapped in a LuaError and surface as script errors.

LuaCallable

Functional interface used with the builder:

@FunctionalInterface
public interface LuaCallable {
    LuaValue invoke(LuaValue[] args);
}

AddonManager

public class AddonManager {
    public boolean registerAddon(Plugin owner, Addon addon); // preferred
    public boolean registerAddon(Addon addon);
    public boolean unregisterAddon(String name);
    public Addon getAddon(String name);
    public List<Addon> getRegisteredAddons();
}

Obtained via MinecraftLuaScripting.getAddonManager().

Prefer registerAddon(owner, addon): when the owning plugin is disabled, its addons are automatically unregistered and their globals removed from the Lua state. Registration also warns if an addon declares an API name that conflicts with a built-in global (PlayerApi, WorldApi, etc.).

API Table Locking

AddonApi extends ProtectedLuaTable: once the table is built and installed, it is locked and scripts can no longer write to it. Its global name is locked too, exactly like the built-in APIs (see API Guardrails for the full list of rejected operations and error messages).

What this means for addon authors:

  • Build the table completely before it is installed. The table is populated from @LuaFunction methods, the builder, and getTableContents(), and is locked immediately afterwards. Mutating the LuaTable after that throws a LuaError.
  • Locking is deep. Any plain nested LuaTable — e.g. builder.table("sub", subTable) — is replaced by a locked copy when the API is locked, so MyApi.sub.fn = evil is rejected as well. Because it is a copy, later writes to the instance you passed in are not reflected in Lua; finish building the nested table first.
  • Nested metatables are frozen as well, and shared or cyclic nested tables are frozen once and keep sharing the same locked copy.
  • Scripts cannot shadow your API name with a local, a function parameter, or a loop variable; such a script is refused at load time. Pick an API name distinctive enough that scripts will not want it as a variable name.
  • Unregistering the addon unlocks and removes its global, so a script loaded afterwards may reuse the name freely.

Calling into Lua from an addon

If your addon invokes Lua itself — a callback a script handed you, a function stored in your API table — route the call through MinecraftLuaScripting.getLuaDispatcher() rather than calling LuaValue.call() directly:

plugin.getLuaDispatcher().dispatch(scriptName, "MyAddon callback", () -> callback.call());

The dispatcher holds the Lua lock, runs the call on the main server thread, attributes anything the callback registers to the owning script, and opens the script-timeout-ms execution budget around it. A direct call() gets none of that: it runs with no budget, so a script that never returns freezes the server exactly as it would have before the watchdog existed.

Minimal Example

MyAddon.java

public class MyAddon extends Addon {
    @Override
    public Set<AddonApi> getApiTables() {
        return Set.of(
            AddonApi.builder("MyApi")
                .function("greet", args -> LuaValue.valueOf("Hello, " + args[0].tojstring()))
                .constant("version", LuaValue.valueOf("1.0"))
                .build()
        );
    }

    @Override
    public String getName() { return "MyAddon"; }

    @Override
    public String getVersion() { return "1.0"; }

    @Override
    public void onEnable(MinecraftLuaScripting plugin) {
        log("MyAddon is ready");
    }
}

PluginMain.java

import me.touchie771.minecraftLuaScripting.MinecraftLuaScripting;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;

public class PluginMain extends JavaPlugin {
    @Override
    public void onEnable() {
        MinecraftLuaScripting mls = MinecraftLuaScripting.getInstance();
        if (mls == null) {
            getLogger().severe("MinecraftLuaScripting not found, disabling.");
            Bukkit.getPluginManager().disablePlugin(this);
            return;
        }
        mls.getAddonManager().registerAddon(this, new MyAddon());
    }
}

Lua Usage

After the addon is registered, the API name becomes a global variable:

MyApi.greet("world") -- returns "Hello, world"
MyApi:greet("world") -- also works: AddonFunction strips the implicit self argument

Functions defined via @LuaFunction, the builder, or AddonFunction support both dot and colon syntax. Only raw OneArgFunction/TwoArgFunction values placed directly into getTableContents() require dot syntax.

Notes

  • Register addons during onEnable so they are available before scripts load.
  • Addon API names must not conflict with existing globals such as PlayerApi, WorldApi, or another addon's API name. A warning is logged at registration if a name conflicts with a built-in global.
  • Unregistering an addon removes its API tables from the currently loaded Lua state.
  • Addons registered with an owning plugin are automatically unregistered when the owner is disabled.
  • /mls viewaddons lists each registered addon along with its API table names.
  • API tables are locked once installed; see API Table Locking.