> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rpgmobs.frotty27.com/llms.txt
> Use this file to discover all available pages before exploring further.

# API Overview

> Integration guide for mod developers who want to hook into RPGMobs events and data.

The RPGMobs API allows other Hytale mods to react to RPG mob events, inspect mob state, and modify behavior at runtime. It ships as a separate `RPGMobs-api` artifact so your mod only depends on the lightweight API interfaces, not the full plugin.

## Setup

### Dependency

Add the API JAR as a compile-only dependency in your `build.gradle`:

```groovy theme={null}
dependencies {
    compileOnly files('libs/RPGMobs-API-1.3.0.jar')
}
```

Or if published to a Maven repository:

```groovy theme={null}
dependencies {
    compileOnly 'Frotty27:RPGMobs-api:1.3.0'
}
```

The `-javadoc.jar` and `-sources.jar` are available as additional downloads. Place all three in your `libs/` folder for full IDE support (autocomplete, inline docs, click-to-source).

### Mod Manifest

Declare RPGMobs as a dependency in your mod's `manifest.json` so Hytale loads it before your mod:

```json theme={null}
{
  "Dependencies": {
    "Frotty27:RPGMobs": "*"
  }
}
```

## Entry Point

All API access goes through the static `RPGMobsAPI` class:

```java theme={null}
import com.frotty27.rpgmobs.api.RPGMobsAPI;

// Register a listener
RPGMobsAPI.registerListener(myListener);

// Query mob state
RPGMobsAPI.query().getTier(entityRef);

// Unregister when your mod shuts down
RPGMobsAPI.unregisterListener(myListener);
```

If RPGMobs has not initialized yet, calling these methods throws `RPGMobsNotInitializedException`.

## Event System

Implement `IRPGMobsEventListener` and override the events you care about. All handler methods have default no-op implementations, so you only need to override what you use.

```java theme={null}
import com.frotty27.rpgmobs.api.IRPGMobsEventListener;
import com.frotty27.rpgmobs.api.events.*;

public class MyEliteListener implements IRPGMobsEventListener {

    @Override
    public void onRPGMobSpawned(RPGMobsSpawnedEvent event) {
        int tier = event.getTier();
        // Cancel the spawn if needed
        // event.setCancelled(true);
    }

    @Override
    public void onRPGMobDrops(RPGMobsDropsEvent event) {
        // Add custom items to the drop list
        event.getDrops().add(myCustomItem);
    }

    @Override
    public void onRPGMobDamageDealt(RPGMobsDamageDealtEvent event) {
        // Reduce elite damage by 20%
        event.setMultiplier(event.getMultiplier() * 0.8f);
    }
}
```

Register it during your mod's initialization:

```java theme={null}
RPGMobsAPI.registerListener(new MyEliteListener());
```

### Available Events

| Event                            | Fired when                  | Cancellable | Mutable fields                                                      |
| :------------------------------- | :-------------------------- | :---------: | :------------------------------------------------------------------ |
| `RPGMobsSpawnedEvent`            | Elite enters the world      |     Yes     | -                                                                   |
| `RPGMobsDeathEvent`              | Elite or minion dies        |      No     | `isMinion()`                                                        |
| `RPGMobsDropsEvent`              | Loot is about to spawn      |     Yes     | `getDrops()` (live list)                                            |
| `RPGMobsDamageDealtEvent`        | Elite deals outgoing damage |     Yes     | `setMultiplier(float)`                                              |
| `RPGMobsDamageReceivedEvent`     | Elite takes incoming damage |      No     | -                                                                   |
| `RPGMobsAbilityStartedEvent`     | Ability execution begins    |     Yes     | -                                                                   |
| `RPGMobsAbilityCompletedEvent`   | Ability finishes normally   |      No     | -                                                                   |
| `RPGMobsAbilityInterruptedEvent` | Ability was interrupted     |      No     | `getReason()`                                                       |
| `RPGMobsAggroEvent`              | Elite targets an entity     |      No     | `getTargetRef()`                                                    |
| `RPGMobsDeaggroEvent`            | Elite loses its target      |      No     | -                                                                   |
| `RPGMobsScalingAppliedEvent`     | Scaling parameters computed |      No     | `getHealthMultiplier()`, `getDamageMultiplier()`, `getModelScale()` |
| `RPGMobsReconcileEvent`          | Config reconciliation pass  |      No     | `getWorldName()`, `getEntityCount()`                                |

### Base Event Fields

Every event extends `RPGMobsEvent` which provides:

* `getWorld()`  -  the `World` in which the event occurred *(since 1.1.0)*
* `getEntityRef()`  -  the RPG mob's `Ref<EntityStore>`
* `getEntityUuid()`  -  the entity's `UUID`, or `null` if unavailable *(since 1.1.0)*
* `getTier()`  -  zero-based tier index (0 = Tier 1, 4 = Tier 5)
* `getRoleName()`  -  the NPC role identifier (e.g. `"Skeleton_Frost_Knight"`)

### Cancelling Events

Events that implement `ICancellable` can be cancelled to prevent the action:

```java theme={null}
@Override
public void onRPGMobSpawned(RPGMobsSpawnedEvent event) {
    if (event.getTier() >= 4) {
        event.setCancelled(true); // Block Tier 5 spawns
    }
}
```

Cancellable events: `RPGMobsSpawnedEvent`, `RPGMobsDropsEvent`, `RPGMobsDamageDealtEvent`, `RPGMobsAbilityStartedEvent`.

## Query API

The Query API provides read-only access to any RPG mob's current state. Access it via `RPGMobsAPI.query()`.

```java theme={null}
import com.frotty27.rpgmobs.api.query.IRPGMobsQueryAPI;

IRPGMobsQueryAPI query = RPGMobsAPI.query();
```

### Identity

| Method          | Returns             | Description                             |
| :-------------- | :------------------ | :-------------------------------------- |
| `isRPGMob(ref)` | `boolean`           | Whether the entity is an RPGMobs elite  |
| `isMinion(ref)` | `boolean`           | Whether the entity is a summoned minion |
| `getTier(ref)`  | `Optional<Integer>` | Zero-based tier index                   |

### Scaling

| Method                     | Returns           | Description                              |
| :------------------------- | :---------------- | :--------------------------------------- |
| `getHealthMultiplier(ref)` | `Optional<Float>` | Applied health multiplier                |
| `getDamageMultiplier(ref)` | `Optional<Float>` | Applied damage multiplier                |
| `getModelScale(ref)`       | `Optional<Float>` | Visual size scale                        |
| `isHealthFinalized(ref)`   | `boolean`         | Whether health scaling has been verified |

### Progression

| Method                        | Returns           | Description                         |
| :---------------------------- | :---------------- | :---------------------------------- |
| `getSpawnDistance(ref)`       | `Optional<Float>` | Distance from world origin at spawn |
| `getDistanceHealthBonus(ref)` | `Optional<Float>` | Bonus health from distance          |
| `getDistanceDamageBonus(ref)` | `Optional<Float>` | Bonus damage from distance          |

### Combat

| Method                    | Returns                      | Description                          |
| :------------------------ | :--------------------------- | :----------------------------------- |
| `isInCombat(ref)`         | `boolean`                    | Whether the mob has an active target |
| `getLastAggroTarget(ref)` | `Optional<Ref<EntityStore>>` | Current or last aggro target         |
| `getLastAggroTick(ref)`   | `Optional<Long>`             | Server tick of last aggro event      |

### Summons

| Method                        | Returns             | Description                       |
| :---------------------------- | :------------------ | :-------------------------------- |
| `getSummonedMinionCount(ref)` | `Optional<Integer>` | Number of active summoned minions |

### Query Examples

#### Check if an entity is an elite and read its tier

```java theme={null}
IRPGMobsQueryAPI query = RPGMobsAPI.query();

if (query.isRPGMob(entityRef)) {
    int tier = query.getTier(entityRef).orElse(0);
    System.out.println("RPG mob detected  -  Tier " + (tier + 1));
}
```

#### Read scaling values for a HUD or tooltip

```java theme={null}
IRPGMobsQueryAPI query = RPGMobsAPI.query();

Optional<Float> health = query.getHealthMultiplier(entityRef);
Optional<Float> damage = query.getDamageMultiplier(entityRef);
Optional<Float> scale  = query.getModelScale(entityRef);

health.ifPresent(h -> System.out.println("Health multiplier: " + h + "x"));
damage.ifPresent(d -> System.out.println("Damage multiplier: " + d + "x"));
scale.ifPresent(s ->  System.out.println("Model scale: " + s + "x"));
```

#### Check combat state and current target

```java theme={null}
IRPGMobsQueryAPI query = RPGMobsAPI.query();

if (query.isInCombat(entityRef)) {
    query.getLastAggroTarget(entityRef).ifPresent(targetRef -> {
        System.out.println("Currently fighting a target");
    });
    query.getLastAggroTick(entityRef).ifPresent(tick -> {
        System.out.println("Aggro started at server tick " + tick);
    });
}
```

#### Read distance-based progression bonuses

```java theme={null}
IRPGMobsQueryAPI query = RPGMobsAPI.query();

query.getSpawnDistance(entityRef).ifPresent(distance -> {
    System.out.println("Spawned " + distance + "m from world origin");
});

Optional<Float> healthBonus = query.getDistanceHealthBonus(entityRef);
Optional<Float> damageBonus = query.getDistanceDamageBonus(entityRef);

if (healthBonus.isPresent() && damageBonus.isPresent()) {
    System.out.println("Distance bonuses  -  health: +" + healthBonus.get()
        + "x, damage: +" + damageBonus.get() + "x");
}
```

#### Monitor summoned minions

```java theme={null}
IRPGMobsQueryAPI query = RPGMobsAPI.query();

if (query.isMinion(entityRef)) {
    System.out.println("This entity is a summoned minion");
}

query.getSummonedMinionCount(entityRef).ifPresent(count -> {
    System.out.println("Active minions: " + count);
});
```

#### Inspect combat state *(since 1.2.0)*

```java theme={null}
IRPGMobsQueryAPI query = RPGMobsAPI.query();

// Which mob rule matched this elite
String ruleKey = query.getMatchedMobRuleKey(entityRef, store);
// e.g. "Skeleton_Fighter", "Trork_Warrior"

// Currently executing ability (null if idle)
String abilityId = query.getActiveAbilityId(entityRef, store);
// e.g. "multi_slash_medium", "charge_leap", null

// Combat style driving the CAE behavior
String style = query.getCombatStyle(entityRef, store);
// e.g. "Disciplined", "Berserker", "Tactical", "Chaotic"
```

#### Filter minion deaths

```java theme={null}
@Override
public void onRPGMobDeath(RPGMobsDeathEvent event) {
    if (event.isMinion()) {
        // Skip minion deaths  -  only reward XP for real elites
        return;
    }
    int tier = event.getTier();
    UUID uuid = event.getEntityUuid();
    World world = event.getWorld();
    System.out.println("Elite killed in " + world + "  -  tier " + (tier + 1));
}
```

## Ability IDs

Abilities are identified by string IDs passed in ability events (`RPGMobsAbilityStartedEvent`, `RPGMobsAbilityCompletedEvent`, `RPGMobsAbilityInterruptedEvent`). The built-in ability IDs are:

| ID                     | Description                                                   |
| :--------------------- | :------------------------------------------------------------ |
| `"charge_leap"`        | Leap toward target with area slam damage                      |
| `"heal_leap"`          | Jump to safety and drink a healing potion                     |
| `"undead_summon"`      | Spawn role-based reinforcements                               |
| `"dodge_roll"`         | Reactive/preemptive lateral dodge with invulnerability frames |
| `"multi_slash_short"`  | 1-2 hit quick strikes (3 random variations per weapon type)   |
| `"multi_slash_medium"` | 2-4 hit combos (2 random variations per weapon type)          |
| `"multi_slash_long"`   | 4-6 hit full combos (1 variation per weapon type)             |
| `"enrage"`             | Berserk fist-fighting mode ending in exhaustion death         |
| `"volley"`             | Ranged projectile burst for bow/crossbow/gun wielders         |

## Example: Full Integration

```java theme={null}
import com.frotty27.rpgmobs.api.RPGMobsAPI;
import com.frotty27.rpgmobs.api.IRPGMobsEventListener;
import com.frotty27.rpgmobs.api.events.*;

public class MyMod implements IRPGMobsEventListener {

    public void onEnable() {
        RPGMobsAPI.registerListener(this);
    }

    public void onDisable() {
        RPGMobsAPI.unregisterListener(this);
    }

    @Override
    public void onRPGMobSpawned(RPGMobsSpawnedEvent event) {
        // Log spawns for analytics
        System.out.println("Elite spawned: tier=" + event.getTier()
            + " role=" + event.getRoleName());
    }

    @Override
    public void onRPGMobDrops(RPGMobsDropsEvent event) {
        // Double drops for Tier 5 elites
        if (event.getTier() == 4) {
            var drops = event.getDrops();
            drops.addAll(new ArrayList<>(drops));
        }
    }

    @Override
    public void onRPGMobDamageDealt(RPGMobsDamageDealtEvent event) {
        // Check if mob is far from spawn using query API
        var distance = RPGMobsAPI.query()
            .getSpawnDistance(event.getEntityRef());

        if (distance.isPresent() && distance.get() > 5000f) {
            // Extra 10% damage for mobs far from spawn
            event.setMultiplier(event.getMultiplier() * 1.1f);
        }
    }
}
```

## Spawn API *(since 1.3.0)*

The Spawn API lets other mods programmatically create RPGMobs elites. Access it via `RPGMobsAPI.spawn()`.

### Quick Reference

```java theme={null}
import com.frotty27.rpgmobs.api.RPGMobsAPI;
import com.frotty27.rpgmobs.api.spawn.SpawnResult;
```

| Method                                                                                     | Description                                            |
| :----------------------------------------------------------------------------------------- | :----------------------------------------------------- |
| `RPGMobsAPI.spawn().spawnElite(world, roleName, tier, position, rotation, weaponCategory)` | Creates an NPC and promotes it to an elite in one call |
| `RPGMobsAPI.spawn().applyEliteTier(world, npcRef, tier, weaponCategory)`                   | Promotes an already-spawned NPC to an elite            |

**Parameters:**

| Parameter        | Type                 | Description                                                                         |
| :--------------- | :------------------- | :---------------------------------------------------------------------------------- |
| `world`          | `World`              | The world to spawn in                                                               |
| `roleName`       | `String`             | NPC role name (e.g. `"Skeleton_Fighter"`, `"Trork_Warrior"`, `"Goblin_Duke"`)       |
| `tier`           | `int`                | Tier number **1-5** (clamped if out of range). 1 = Common, 5 = Legendary            |
| `position`       | `Vector3d`           | Spawn position in world coordinates                                                 |
| `rotation`       | `@Nullable Vector3f` | Spawn rotation, or `null` for default                                               |
| `weaponCategory` | `@Nullable String`   | Weapon category override (see table below), or `null` to use the mob rule's default |
| `npcRef`         | `Ref<EntityStore>`   | *(applyEliteTier only)* Reference to an existing NPC entity                         |

### Weapon Categories

Pass one of these exact strings as `weaponCategory` to force the elite to spawn with that weapon type. Pass `null` to let RPGMobs pick based on the mob rule's configured categories.

| Category       | Type   | Example weapons                             |
| :------------- | :----- | :------------------------------------------ |
| `"Swords"`     | Melee  | Iron Sword, Steel Sword                     |
| `"Longswords"` | Melee  | Steel Longsword, Adamantite Longsword       |
| `"Daggers"`    | Melee  | Iron Daggers, Bone Daggers                  |
| `"Axes"`       | Melee  | Iron Axe, Steel Axe                         |
| `"Battleaxes"` | Melee  | Iron Battleaxe, Adamantite Battleaxe        |
| `"Maces"`      | Melee  | Iron Mace, Steel Mace                       |
| `"Clubs"`      | Melee  | Wooden Club, Bone Club                      |
| `"ClubsFlail"` | Melee  | Flail variants                              |
| `"Spears"`     | Melee  | Iron Spear, Steel Spear                     |
| `"Pickaxes"`   | Melee  | Iron Pickaxe, Steel Pickaxe                 |
| `"Shortbows"`  | Ranged | Shortbow variants (uses vanilla ranged AI)  |
| `"Crossbows"`  | Ranged | Crossbow variants (uses vanilla ranged AI)  |
| `"Guns"`       | Ranged | Gun variants (uses vanilla ranged AI)       |
| `"Staves"`     | Ranged | Staff variants (uses vanilla ranged AI)     |
| `"Spellbooks"` | Ranged | Spellbook variants (uses vanilla ranged AI) |

Ranged weapon categories use vanilla Hytale AI instead of the CAE combat system.

### Spawn a New Elite

```java theme={null}
SpawnResult result = RPGMobsAPI.spawn().spawnElite(
    world,                  // the world to spawn in
    "Skeleton_Fighter",     // NPC role name
    3,                      // tier (1-5)
    position,               // Vector3d spawn position
    null,                   // rotation (null = default)
    null                    // weapon category (null = mob rule default)
);

if (result instanceof SpawnResult.Success s) {
    Ref<EntityStore> eliteRef = s.entityRef();  // the spawned entity
    int tier = s.tier();                        // 0-4 (zero-indexed)
    String role = s.roleName();                 // resolved role name
}
```

### Elite-ify an Existing NPC

If you already spawned an NPC via `NPCPlugin` and want to promote it:

```java theme={null}
SpawnResult result = RPGMobsAPI.spawn().applyEliteTier(
    world, existingNpcRef, 5, "Longswords");
```

### Force a Weapon Category

```java theme={null}
// Spawn a T4 Trork with an axe
RPGMobsAPI.spawn().spawnElite(
    world, "Trork_Warrior", 4, pos, null, "Axes");

// Spawn a T5 Skeleton with a longsword
RPGMobsAPI.spawn().spawnElite(
    world, "Skeleton_Fighter", 5, pos, null, "Longswords");
```

### Handle Failures

`SpawnResult` is a sealed interface. Check success or match on the failure reason:

```java theme={null}
SpawnResult result = RPGMobsAPI.spawn().spawnElite(world, "Skeleton_Fighter", 3, pos, null, null);

if (result.isSuccess()) {
    var success = (SpawnResult.Success) result;
    // use success.entityRef()
    return;
}

var failure = (SpawnResult.Failure) result;
switch (failure.reason()) {
    case CONFIG_NOT_LOADED       -> log("RPGMobs config not ready");
    case NPC_SPAWN_FAILED        -> log("Hytale couldn't spawn the NPC");
    case NO_MOB_RULE             -> log("No mob rule matches this role");
    case MOB_RULE_DISABLED       -> log("Mob rule is disabled in this world");
    case RPGMOBS_DISABLED_IN_WORLD -> log("RPGMobs is off in this world");
    case EVENT_CANCELLED         -> log("A listener cancelled the spawn");
    case NOT_INITIALIZED         -> log("RPGMobs hasn't started yet");
    case TIER_APPLY_FAILED       -> log("Internal error: " + failure.message());
}
```

### Threading

All spawn methods **must be called on the world's thread**. From other threads, wrap in `world.execute()`:

```java theme={null}
world.execute(() -> {
    RPGMobsAPI.spawn().spawnElite(world, "Goblin_Duke", 5, pos, null, null);
});
```

## Thread Safety

Event callbacks are dispatched from the server's main tick thread. Do not perform blocking I/O or long-running computations inside event handlers. If you need async processing, queue work to a separate thread and return immediately.

The Query API reads component data from the entity store and should only be called from contexts where the store is accessible (event handlers, tick systems, or `world.execute()` callbacks).
