Astral Realms Documentation Help

Developer API

AstralMobs exposes its services through the MobsAPI static façade and through the main plugin instance. Mobs are runtime entities that do not persist on reload — they spawn on demand and are cleaned up when the plugin disables or when a mob dies.

Maven coordinates

<dependency> <groupId>com.astralrealms</groupId> <artifactId>mobs</artifactId> <version>1.1-SNAPSHOT</version> <scope>provided</scope> </dependency>

Repository: https://maven.astralrealms.fr/repository/maven-public/.

MobsAPI

Static lookups suitable for spawning mobs and querying blueprints without taking a hard dependency on the service classes. Initialized in onEnable and available immediately.

import com.astralrealms.mobs.MobsAPI; import org.bukkit.Location; import org.bukkit.entity.Entity; // Blueprint lookups MobsAPI.findBlueprintById("zombie-warrior"); // Optional<MobBlueprint> MobsAPI.getBlueprint(entityUniqueId); // Optional<MobBlueprint> // Spawning Entity mob = MobsAPI.spawnMob("zombie-warrior", location); // Throws IllegalArgumentException on unknown id Entity mob = MobsAPI.spawnMob(blueprint, location); // Entity

Method

Returns

Use

findBlueprintById(String id)

Optional<MobBlueprint>

Resolve a blueprint by id.

spawnMob(String blueprintId, Location)

Entity

Spawn a mob by blueprint id. Throws IllegalArgumentException if the id is not found.

spawnMob(MobBlueprint, Location)

Entity

Spawn a mob from an existing blueprint object.

getBlueprint(UUID entityUniqueId)

Optional<MobBlueprint>

Retrieve the blueprint of a spawned mob by its entity UUID.

spawnMob(String blueprintId, Location, UUID ownerId)

Entity

Spawn a mob bound to an owning player.

spawnOwnedMob(MobBlueprint, Location, UUID ownerId)

AstralEntity

The same, returning the tracked wrapper (blueprint, health display, NMSMob) rather than the Bukkit handle.

spawnOwnedMob(…, Map<String, ComplexPlaceholder> attachments)

AstralEntity

Spawn with placeholder attachments.

spawnOwnedMob(…, attachments, MobOverrides overrides)

AstralEntity

Spawn with per-spawn overrides.

findMob(UUID entityUniqueId)

Optional<AstralEntity>

The tracked mob behind an entity, if AstralMobs spawned it.

removeMob(UUID entityUniqueId)

void

Remove a spawned mob and its displays.

isAstralMob(Entity)

boolean

Whether the entity is an AstralMobs mob. Reads the entity's own handle rather than looking anything up, so it is cheap enough to ask about every entity on the server.

spawnedMobs()

Collection<AstralEntity>

A live view of every mob currently spawned, in no particular order — backed by the tracking map, not copied. Meant to be walked, not kept.

spawnedMobEntities()

Collection<LivingEntity>

The same as Bukkit handles, for APIs that deal in those.

Everything here is main thread only.

Placeholder attachments

spawnOwnedMob(blueprint, location, ownerId, attachments) grafts placeholder trees onto the mob, reachable as %mob_<key>_…% — which lets a plugin expose a domain object AstralMobs knows nothing about. AstralPets attaches the Pet behind a pet mob under "pet", so a blueprint can read %mob_pet_level%.

Prefer this over attaching after the fact: the attachments are in place before the blueprint's requirement-gated gear, health display and spawn actions run, so those already resolve against them.

Per-spawn overrides

The last overload takes a MobOverrides: it alters the blueprint for that spawn only — another rendered entity type or BetterModel model, another size, a baby form, extra equipment or potion effects, raw client-side metadata. Anything the overrides leave unset comes from the blueprint, and its goals, movement, animations and actions are untouched: the mob behaves exactly as the blueprint says, it just looks different. A null spawns the plain blueprint.

This is how AstralPets applies a pet skin: one mob-blueprint defines how a pet behaves, and each skin re-dresses it.

Plugin handle

import com.astralrealms.mobs.AstralMobsPlugin; AstralMobsPlugin plugin = AstralMobsPlugin.getInstance();

Services

BlueprintService — plugin.blueprints()

Manages blueprint loading and resolution.

plugin.blueprints().load(); // Re-scan blueprints/ from disk plugin.blueprints().findById("zombie-warrior"); // Optional<MobBlueprint> plugin.blueprints().all(); // Unmodifiable Map<String, MobBlueprint>

Method

Returns

Use

load()

void

Re-scan blueprints/ folder from disk and reload all blueprints.

findById(String id)

Optional<MobBlueprint>

Resolve a blueprint by id.

all()

Map<String, MobBlueprint>

Every loaded blueprint (read-only).

MobService — plugin.mobs()

Manages the lifecycle of spawned mobs, including the repeating display/gear/metadata loops.

// Summoning Entity mob = plugin.mobs().summon(blueprint, location); // Entity // Lookups plugin.mobs().findByUniqueId(entityUUID); // Optional<AstralEntity> plugin.mobs().findByEntityId(entityId); // Optional<AstralEntity> plugin.mobs().entities(); // Map<UUID, AstralEntity> // Removal plugin.mobs().remove(astralEntity); // void

Method

Returns

Use

summon(MobBlueprint, Location)

Entity

Spawn a mob and return its Bukkit entity handle. Automatically spawns configured passengers and wires move control.

findByUniqueId(UUID)

Optional<AstralEntity>

Retrieve a spawned mob's wrapper by its entity UUID.

findByEntityId(int)

Optional<AstralEntity>

Retrieve a spawned mob's wrapper by its entity id (network id).

entities()

Map<UUID, AstralEntity>

Every currently spawned mob (read-only map of UUID → AstralEntity).

remove(AstralEntity)

void

Manually remove a mob from tracking and despawn it.

Background loops: MobService runs three repeating tasks:

  • Display loop (configurable interval, default disabled): refreshes health displays (boss bars or overhead text) every N ticks. Controlled by config.yml force-update-interval.

  • Gear loop (configurable interval, default every tick): re-applies requirement-gated equipment and potion effects to track dynamic changes. Only touches mobs whose blueprint has requirement-gated gear. Controlled by config.yml requirement-update-interval.

  • Metadata reconciliation loop (every 10 ticks): re-broadcasts client-side metadata overrides to tracking players so dropped or mis-ordered packets self-heal. Only touches mobs with active overrides.

All three loops are cancelled and every spawned mob is despawned when the plugin disables.

AstralEntity wrapper

When a mob is spawned, the MobService wraps it in an AstralEntity that holds identity and display state. Retrieve an AstralEntity by calling plugin.mobs().findByUniqueId(uuid) or findByEntityId(id).

AstralEntity astralEntity = plugin.mobs().findByUniqueId(entityUUID).orElseThrow(); // Identity UUID uuid = astralEntity.uniqueId(); int entityId = astralEntity.entityId(); MobBlueprint blueprint = astralEntity.blueprint(); // Entity handle (the NMSMob instance) NMSMob nms = astralEntity.entity(); org.bukkit.entity.Entity bukkit = astralEntity.entity().getBukkitEntity(); // State boolean alive = astralEntity.alive(); astralEntity.updateHealthDisplay(); // Force a refresh of the health display astralEntity.remove(); // Despawn and untrack the mob

Method

Returns

Use

uniqueId()

UUID

The entity's UUID.

entityId()

int

The entity's network id.

blueprint()

MobBlueprint

The blueprint this mob is spawned from.

entity()

NMSMob

The underlying NMS mob (extends Pig).

alive()

boolean

Whether the mob is still spawned and alive.

updateHealthDisplay()

void

Force an immediate refresh of the health bar / overhead display.

remove()

void

Despawn the mob and untrack it. Cleans up display entities.

NMSMob — server-side mob entity

The NMSMob class extends Pig and represents the server-side mob entity. It implements ComplexPlaceholder for the mob: placeholder namespace (see Placeholders for available keys).

Memory and timers

Mobs have a shared memory store (persistent during the mob's lifetime, cleared on despawn) and a timer store for tracking elapsed time since a marker.

NMSMob mob = astralEntity.entity(); // Memory: arbitrary key-value storage mob.setMemory("aggression-level", 5); Object value = mob.getMemory("aggression-level"); // Returns "0" if key not found // Timers: track elapsed milliseconds since a start point mob.startTimer("attack-cooldown"); long elapsedMs = mob.getTimer("attack-cooldown"); // Milliseconds since startTimer was called

Method

Parameters

Returns

Use

setMemory(String key, Object value)

key, value

void

Store an arbitrary value in the mob's memory.

getMemory(String key)

key

Object

Retrieve a memory value, or "0" if not found.

startTimer(String key)

key

void

Mark the current time for a named timer.

getTimer(String key)

key

long

Return elapsed milliseconds since the timer was started, or 0 if not found.

Active goal tracking

Retrieve the simple class name of the currently running AI goal.

String goalName = mob.activeGoalName(); // e.g., "CastSkillGoal", or "none" if idle

Method

Returns

Use

activeGoalName()

String

Simple class name of the running AI goal, or "none" if no goal is active.

Move control and pathfinding

Replace the mob's movement controller or path navigation strategy.

mob.applyMoveControl(new CustomMoveControl(mob)); mob.applyNavigation(new FlyingPathNavigation(mob, level));

Method

Parameter

Returns

Use

applyMoveControl(MoveControl)

A MoveControl instance

void

Replace the mob's move controller. Blueprints use this to apply configured movement types.

applyNavigation(PathNavigation)

A PathNavigation instance

void

Replace the mob's path navigation.

canMove()

—

boolean

Hook queried by certain move controls (e.g., creaking-style freeze-on-observation). Defaults to true. Override in a subclass to gate movement.

Metadata spoofing

Mobs are Pigs on the server but render as a different type on the client. Use the metadata-override API to change client-side properties (pose, scale, etc.) without affecting the server's real entity state.

// Metadata index 6 is the Pose field EntityData<?> roaringPose = new EntityData<>(6, EntityDataTypes.POSE, net.minecraft.world.entity.Pose.ROARING); mob.putMetadataOverride(roaringPose); // Later, restore the real value mob.removeMetadataOverride(6); // Check what overrides are active Map<Integer, EntityData<?>> overrides = mob.metadataOverrides();

Method

Parameter

Returns

Use

putMetadataOverride(EntityData<?> data)

EntityData with index and value

void

Register a client-side metadata override at the index in data. Overwrites any existing override at that index.

removeMetadataOverride(int index)

Metadata index

void

Drop the override at this index, returning the client to the server's real value.

metadataOverrides()

—

Map<Integer, EntityData<?>>

Every active override, keyed by metadata index.

The PacketListener automatically sanitizes outgoing metadata packets: it strips type-specific fields that would crash the client, then forces in your overrides. Spoofing is bidirectional — the SpoofRegistry maps entity id → NMSMob so both SPAWN_ENTITY (type rewrite) and ENTITY_METADATA (field rewrite) packets are handled transparently.

Passenger handling

When a mob is a vehicle carrying a configured passenger, access the passenger through the NMSMob.

NMSMob passenger = mob.passengerMob(); // The passenger mob, or null mob.passengerMob(otherMob); // Set the passenger (or null)

Method

Parameter

Returns

Use

passengerMob()

—

NMSMob or null

Retrieve the passenger mob, if configured and alive.

passengerMob(NMSMob)

A mob or null

void

Update the stored passenger reference. Used internally by MobService during spawning and by listeners during passenger death.

The PassengerFollowMoveControl uses this field to steer the vehicle toward the passenger's movement target when the blueprint enables passenger control.

Spoof registry

The SpoofRegistry is an internal static map that tracks entity id ↔ NMSMob associations so the PacketListener can rewrite outgoing packets to show clients the spoofed type instead of the Pig. You generally do not call this directly — it is managed automatically by NMSMob constructor and destruction.

// Internal — called automatically SpoofRegistry.register(entityId, nmsMob); SpoofRegistry.unregister(entityId); NMSMob mob = SpoofRegistry.get(entityId);

Events and listeners

The plugin fires no custom events. Instead, it listens to Bukkit events and applies custom logic:

MobListener

Handles Bukkit entity events for spawned mobs:

  • EntityDamageEvent: Cancels self-inflicted damage (mobs damaging themselves with their own projectiles or AoE), updates health displays on hit, and redirects damage to passengers when configured via redirect-damage blueprint field.

  • EntityDeathEvent: Runs configured death actions, suppresses drops/XP if configured, cleans up passenger references, and unregisters the mob from tracking.

  • EntityPotionEffectEvent: Cancels Wither effect applications on players (prevents mobs from being able to apply Wither).

PacketListener

Intercepts outgoing packets to implement type spoofing:

  • SPAWN_ENTITY: Rewrites the entity type from Pig to the spoofed type.

  • ENTITY_METADATA: Strips type-specific metadata fields that would crash the client, then forces in the mob's configured metadata overrides.

External plugins can rely on these listeners running automatically.

Extension points

Actions

Register custom mob actions via plugin.registerAction(...). The built-in actions are:

Action

Use

spawn-mob-location

Global action; spawn a mob at a location (e.g., in a custom menu).

store-memory

Per-mob action; store a key-value pair in the mob's memory.

start-timer

Per-mob action; start a named timer on the mob.

send-entity-status

Per-mob action; send a client-visible status effect (e.g., thorns, hurt).

edit-entity-metadata

Per-mob action; override a metadata field and immediately broadcast to tracking players.

Completions and context resolvers

The plugin registers:

  • mobBlueprints completion handler: auto-complete mob blueprint ids in commands.

  • MobBlueprint context resolver: convert a string argument to a resolved MobBlueprint in ACF commands.

Type serializers

The plugin registers a custom type serializer for EquipmentSlot (Bukkit enum) so blueprints can reference slots like HEAD, CHEST, LEGS, FEET, HAND, OFF_HAND in YAML.

AstralSkill integration

AstralMobs integrates with AstralSkill via the CastSkillGoal AI goal. When a mob is configured to cast skills, the goal calls SkillService.castThroughPos(...) with the mob as the executor. This is a soft dependency — if AstralSkill is not present, skill-based goals fail gracefully at load time, and everything below is simply not registered (the interfaces live in AstralSkill).

See Casting Skills for how to configure skills on mobs.

Owned mobs as skill casters

A skill fired by an owned mob is cast with the owner as the caster, so the damage, the kill credit and the retaliation land on them rather than on the pet — the same rule the melee goal follows.

Pets and hit detection

Two mirrored rules keep pets from ruining a fight, registered through AstralSkill's target service:

Class

Registered as

Effect

PetSkillTargetFilter

SkillTargetFilter

An owned AstralMobs entity is never a valid skill target, whoever casts — owner, another player, or a mob.

PetSkillDamageListener

SkillDamageEvent listener

A pet's own skill damage only ever lands on ownerless AstralMobs entities — never on players, never on anyone's pet.

AstralMobSkillTargetSource

SkillTargetSource

Hands AstralSkill the mobs AstralMobs has spawned, so its hitbox cache can track them instead of searching the world.

The filter exists because a pet stands exactly where its owner is fighting: without it, it soaks every area impact its owner throws, and a projectile spends a pierce on it instead of reaching what was aimed at. AstralSkill's targets: cannot express this — it compares entity types, and every AstralMobs entity shares one server-side type. The check is read straight off the entity's NMS handle, a type check and a null test, so the vanilla entities making up almost all of the per-tick sweep cost nothing.

Only skills are affected. Ordinary melee, arrows and every other vanilla damage source still hit a pet exactly as before. Pets are deliberately included in the target source — the filter is what hides them, and it runs over everything the source returns, which keeps a pet reachable by its owner's own self-targeted skills.

With caches.entities.registered-targets-only on, spawnedMobEntities() plus the players is the entire population AstralSkill tracks.

Type resolution

Mob blueprints specify a type: field (e.g., type: ZOMBIE). The NMSMob constructor resolves this Bukkit EntityType to the corresponding NMS EntityType for hitbox calculations and rendering. If a type cannot be resolved, it falls back to Pig (the server-side base class).

The spoofed type is stored in two forms:

  • NMS type (EntityType<?>) — used for hitbox dimensions and pathfinding.

  • PacketEvents type (EntityType) — used in the SPAWN_ENTITY packet for client-side rendering.

Lifecycle and persistence

Mobs are non-persistent. They exist only while the server is running and the plugin is enabled. On server shutdown or plugin reload:

  1. All spawned mobs are removed by MobService.shutdown().

  2. The display loop, gear loop, and metadata-reconciliation loop are cancelled.

  3. All mobs are removed from tracking.

  4. All health display entities (boss bars and overhead text displays) are cleaned up.

Restarting the server does not respawn mobs. To respawn them, execute the spawn command or call MobsAPI.spawnMob() again from a plugin.

Last modified: 25 September 2026