> For the complete documentation index, see [llms.txt](https://lethium.gitbook.io/origin-smp/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lethium.gitbook.io/origin-smp/dev-api.md).

# Developer API

For plugin developers. Read and change origins with the OriginSurvival API and its 8 events, and read stack sizes with the StackMaster API.

{% hint style="info" %}
This section is for **plugin developers** who want to read or change OriginSurvival or StackMaster from their own plugin. Server owners do not need it.
{% endhint %}

## What each plugin exposes

| Plugin             | API                                                 | What you can do                                                                                                                                                                 |
| ------------------ | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **OriginSurvival** | `OriginSurvivalAPI` service                         | Read a player's origin, fused origins, rarity, level, XP, souls, essence, streak, bounty, codex, title, families. Set or remove origins, roll, give XP, essence and free rolls. |
|                    | 8 events in `com.alethium.originsurvival.api.event` | React to origin changes, rolls, fusion, unfusion, steals, level-ups, codex discoveries and quests. Five of them are **cancellable**, and the roll result can be **swapped**.    |
| **StackMaster**    | `StackMasterAPI` service                            | Read how many mobs a stacked entity stands for, how many spawners a spawner block stands for, and server-wide totals. **Read-only.**                                            |
|                    | No events                                           | StackMaster does not fire events of its own.                                                                                                                                    |

| Page                                                                  |                                                                                                                              |
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| [OriginSurvival API](/origin-smp/dev-api/originsurvival-api.md)       | Every method, `OriginInfo`, the 57 `OriginType` values, `Rarity`.                                                            |
| [OriginSurvival events](/origin-smp/dev-api/originsurvival-events.md) | The 8 events: when they fire, what they carry, what you can change.                                                          |
| [StackMaster API](/origin-smp/dev-api/stackmaster-api.md)             | Every method, `LogicalStack`, `StackKey`, `StackKind`, thread rules.                                                         |
| [Examples](/origin-smp/dev-api/examples.md)                           | Recipes: level-up rewards, no fusion in a world, a better first roll, offline lookup, stack sizes in a GUI, mobs in a chunk. |

## Add the plugin to your project

There is no separate API jar and no Maven repository. You compile against the **plugin jar itself**, the same file you put in `plugins/`.

The release jars are obfuscated, but the API keeps its real names:

| Plugin         | Packages and classes with real names                                                                                                              |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| OriginSurvival | `com.alethium.originsurvival.api` (and `api.event`), `com.alethium.originsurvival.origin.OriginType`, `com.alethium.originsurvival.origin.Rarity` |
| StackMaster    | `com.alethium.stackmaster.api`, `com.alethium.stackmaster.model.LogicalStack`, `StackKey`, `StackKind`                                            |

Every other class in the jars has a meaningless name. Do not use them: they change from build to build.

{% stepper %}
{% step %}

### Copy the jar into your project

Put the plugin jar in a `libs/` folder of your project, for example `libs/OriginSurvival-<version>.jar` and `libs/StackMaster-<version>.jar`. Use the file names of the jars you have.
{% endstep %}

{% step %}

### Add it as a compile-only dependency

Never shade or bundle the jar into your own plugin. The server loads it from `plugins/`.

{% tabs %}
{% tab title="Gradle (Kotlin)" %}

```kotlin
repositories {
    mavenCentral()
    maven("https://repo.papermc.io/repository/maven-public/")
}

dependencies {
    compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT")
    compileOnly(files("libs/OriginSurvival-1.0.0.jar"))
    compileOnly(files("libs/StackMaster-1.0.0.jar"))
}

java {
    toolchain.languageVersion = JavaLanguageVersion.of(21)
}
```

{% endtab %}

{% tab title="Gradle (Groovy)" %}

```groovy
repositories {
    mavenCentral()
    maven { url = 'https://repo.papermc.io/repository/maven-public/' }
}

dependencies {
    compileOnly 'io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT'
    compileOnly files('libs/OriginSurvival-1.0.0.jar')
    compileOnly files('libs/StackMaster-1.0.0.jar')
}

java {
    toolchain.languageVersion = JavaLanguageVersion.of(21)
}
```

{% endtab %}

{% tab title="Maven" %}

```xml
<dependency>
  <groupId>com.alethium</groupId>
  <artifactId>originsurvival</artifactId>
  <version>1.0.0</version>
  <scope>system</scope>
  <systemPath>${project.basedir}/libs/OriginSurvival-1.0.0.jar</systemPath>
</dependency>
<dependency>
  <groupId>com.alethium</groupId>
  <artifactId>stackmaster</artifactId>
  <version>1.0.0</version>
  <scope>system</scope>
  <systemPath>${project.basedir}/libs/StackMaster-1.0.0.jar</systemPath>
</dependency>
```

{% endtab %}
{% endtabs %}

Only add the plugin you use. Both plugins are compiled for **Java 21**. OriginSurvival is built against Paper API **1.21.1** and StackMaster against Paper API **1.21.11**. Any Paper API at or above the one of the plugin you use works.
{% endstep %}

{% step %}

### Declare the dependency in `plugin.yml`

Use `depend` when your plugin cannot run without it:

```yaml
depend: [OriginSurvival]
```

Use `softdepend` when your plugin also works without them:

```yaml
softdepend: [OriginSurvival, StackMaster]
```

The plugin names are **`OriginSurvival`** and **`StackMaster`**. Either line makes the server enable them before your plugin.
{% endstep %}
{% endstepper %}

## Get the API

Each API has a static getter and is also registered in Bukkit's `ServicesManager`.

```java
import com.alethium.originsurvival.api.OriginSurvivalAPI;
import com.alethium.stackmaster.api.StackMasterAPI;

OriginSurvivalAPI origins = OriginSurvivalAPI.get();   // throws IllegalStateException if OriginSurvival is not enabled
StackMasterAPI stacks = StackMasterAPI.get();          // throws IllegalStateException if StackMaster is not enabled

OriginSurvivalAPI same = getServer().getServicesManager().load(OriginSurvivalAPI.class);
```

Call them in your own `onEnable` or later, never in a static initialiser.

### Start-up and licence

Both plugins register their service **during their own `onEnable`**, before the licence check. The licence check runs in the background right after. While it runs, the plugin's commands answer "still starting up", but **API calls already work**.

If the licence is not valid, the plugin disables itself. Its service is then unregistered and `get()` throws. So:

* Use `softdepend` if your plugin should keep working without them.
* Check `Bukkit.getPluginManager().isPluginEnabled("OriginSurvival")` (or `"StackMaster"`) before you use the API.
* Prefer calling `get()` when you need it over keeping the instance in a field. For StackMaster this is required, see the rules below.

## A first example

```java
public final class MyPlugin extends JavaPlugin implements Listener {

    @Override
    public void onEnable() {
        if (!getServer().getPluginManager().isPluginEnabled("OriginSurvival")) {
            getLogger().warning("OriginSurvival is not installed, origin features are off.");
            return;
        }
        getServer().getPluginManager().registerEvents(this, this);
    }

    @EventHandler
    public void onLevelUp(OriginLevelUpEvent event) {
        Player player = event.getPlayer();
        if (event.getNewLevel() % 10 == 0) {
            OriginSurvivalAPI.get().addEssence(player, 100);
            player.sendMessage("Level " + event.getNewLevel() + " " + event.getOrigin().getFallbackName() + ": +100 essence");
        }
    }
}
```

## Rules to know

| Rule                                                    | Why                                                                                                                                                                                                                                                                                                   |
| ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **OriginSurvival: main thread only**                    | It is a Paper plugin, not a Folia one. Every method that takes a `Player` must run on the main thread. From async code, hop back with `Bukkit.getScheduler().runTask(...)`.                                                                                                                           |
| OriginSurvival events fire on the **main thread**       | Keep handlers short. Do not block.                                                                                                                                                                                                                                                                    |
| `getOriginOffline` completes on the **database thread** | Switch to the main thread before you touch players or worlds in `thenAccept`. See [Examples](/origin-smp/dev-api/examples.md).                                                                                                                                                                        |
| Player data loads **after join**                        | Until it has loaded, reads return empty or `0` and changes return `false` or do nothing. There is no "data loaded" event.                                                                                                                                                                             |
| No `null` from the service                              | Methods return `Optional`, empty lists or `0`. Only `OriginChangeEvent.getOldPrimary()` and `getNewPrimary()` can be `null`.                                                                                                                                                                          |
| Disabled systems                                        | `addXp` does nothing when `progression.enabled` is false. `roll` returns `false` when `roll.enabled` is false or `roll.mode` is `PICK`. No discover events with `codex.enabled` false, no quest events with `quests.enabled` false, no steal events while `pvp.on-kill.mode` is `NONE` (the default). |
| **StackMaster: any thread**                             | Its stack registry is a concurrent map, so every API method is safe from any thread, including Folia region threads. The size can change right after you read it. The entities themselves still belong to their region thread on Folia.                                                               |
| **StackMaster: do not keep the API in a field**         | `/stackmaster reload` builds a new registry and registers a **new** API instance. An old instance keeps reading the registry that was thrown away. Call `StackMasterAPI.get()` each time.                                                                                                             |
| StackMaster is read-only                                | `LogicalStack` has a `setSize` method, but calling it only changes the number in memory, not the mob, its name tag or the saved data. Do not call it.                                                                                                                                                 |
| StackMaster has no events                               | Poll the API when you need a value.                                                                                                                                                                                                                                                                   |

{% hint style="warning" %}
Neither API has a version constant. If you rely on a method, check the plugin version with `getServer().getPluginManager().getPlugin("OriginSurvival").getPluginMeta().getVersion()`.
{% endhint %}
