resourcepack.ai
DOCS

For developers

Plugin API

The plugin connects your pack to the Studio: a player runs /link, you press Sync, and models animate and emotes play. What it can't know is what any of that means on your server — when a statue should appear, who has earned an emote, what a screen is for.

That's what this API is. It hands the whole engine to your own plugin, so the things a pack can do stop being commands somebody types and become part of how your server actually works:

  • A quest completes, and the statue you generated places itself at the shrine.
  • A player wins a duel, and their character takes a bow in front of the crowd.
  • Somebody clicks a model you made, and your shop opens with your GUI art drawn over it.
  • A boss loses health, and the meter you drew updates on everyone's screen.

Without it, a pack is art the server hands out. With it, the art is something your server can react to and drive.

It's the same engine either way

These calls are exactly the ones the plugin makes for its own commands — /emote, /gui, /hud and the rest are thin wrappers over what's on these pages. Nothing here is a limited version of anything, and nothing the plugin does is off-limits to you.

Installing#

The plugin ships the whole API inside it, so a server that runs ResourcePackAI.jar already has everything. Your plugin only needs to compile against it.

  1. 1

    Add the repository and the dependency

    repositories {
        maven { url = 'https://repo.resourcepack.ai' }
    }
     
    dependencies {
        compileOnly 'ai.resourcepack:library:2.0.0'
    }

    Sources and javadoc are published beside the jar, so your IDE shows the documentation for every call rather than decompiled bytecode.

    compileOnly matters. The classes come from the plugin at runtime, and a second copy inside your jar would be a second copy of the engine fighting the first over the same entities.

    Not using a build tool? Download the plugin jar, drop it in a libs/ folder and use compileOnly files("libs/resourcepackai.jar") instead — it contains the whole API.

  2. 2

    Tell Bukkit the order

    In your plugin.yml:

    softdepend: [ResourcePackAI]

    softdepend, not depend — your plugin should still load on a server that doesn't run ours. The next step is how you handle that.

Your first call#

import ai.resourcepack.api.ResourcePackAI;
 
public class MyPlugin extends JavaPlugin {
 
    private ResourcePackAI rpai;
 
    @Override
    public void onEnable() {
        rpai = Bukkit.getServicesManager().load(ResourcePackAI.class);
        if (rpai == null) {
            getLogger().info("ResourcePack AI isn't installed — statues are off.");
            return;
        }
 
        for (String model : rpai.models().ids()) {
            getLogger().info("This server knows how to animate " + model);
        }
    }
}

Two things to copy from that, because both bite people:

  • Load it in onEnable, not in your constructor. Services are registered as plugins enable, and yours may run before ours.
  • Null means "not installed". No exception, no crash. Check it once and keep the reference, or check it each time — both are fine, but never assume.

Is a feature actually usable?

Being installed isn't the same as being ready. rpai.supports(Feature.EMOTES) answers whether this server has emote data and a rig for somebody to wear; Feature.SKINS is false on Spigot, which can't change a skin after a player joins. Ask before offering something in your own menu.

The shape of everything#

The API is deliberately repetitive. Learn one service and you know the rest.

You wantYou callYou get
Everything of a kindids()List<String>
Details of oneinfo(id)Optional<…Info>
Do a thing that can failplay(…), draw(…)boolean
Do a thing that returns somethingplace(…), open(…)Optional<…>
Do a thing that can fail several waysemotes().play(…)a result with a reason

And the rules that hold everywhere:

  • Nothing returns null. Empty lists, Optional.empty(), false.
  • Nothing throws because you passed null. A model id read out of a config that turned out to be missing gives you Optional.empty(), not a stack trace in your console.
  • Whoever the action is about comes first. play(player, …), draw(viewer, …), place(location, …).
  • Optional arguments come last, as an options object. PlaceOptions.defaults().scale(2f) — build it by chaining, in any order.

Threading#

Anything that touches a player, an entity or the world must be on the main thread. It throws IllegalStateException if it isn't, rather than letting Bukkit corrupt something quietly hours later.

Asking what the server holds is safe from anywhere — ids(), info(), animationsOf(), isAnimated() and everything on content() read thread-safe state. So you can build a menu on an async task and only hop back when you act on it:

Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
    List<String> models = rpai.models().ids();          // fine off-thread
 
    Bukkit.getScheduler().runTask(this, () -> {
        rpai.models().place(spot, models.get(0), PlaceOptions.defaults());
    });
});

Where to go next#