Astral Realms Documentation Help

Dungeon & Room Blueprints

The dungeon server reads two folders under its data directory (plugins/AstralDungeons/), both scanned recursively (.yml/.yaml, any subfolder depth) by BlueprintService.load() on enable and on /dungeon reload:

Folder

File type

Java type

Keyed by

rooms/

RoomBlueprint YAML + the WorldEdit schematics they reference

com.astralrealms.roomeditor.room.RoomBlueprint (AstralRoomEditor)

the room's own name field

blueprints/

DungeonBlueprint YAML

com.astralrealms.dungeons.model.dungeon.DungeonBlueprint

the blueprint's own id field

A RoomBlueprint is produced by AstralRoomEditor's in-game building tool — it describes a single, self-contained room: its schematic and the components placed inside it (doors, spawn/boss/mob markers, bounding box). A DungeonBlueprint is dungeon-level: it names a pool of rooms, the generation limits that turn that pool into a procedural layout, and the mobs/loot tied to that layout. Rooms load first, then dungeon blueprints — a dungeon blueprint resolves its room references against the room set that was just loaded. See Overview for how a blueprint turns into a live instance.

Room blueprints (rooms/)

Field

Type

Read by AstralDungeons

Description

name

String

Yes

The room's id. This — not the filename — is what every reference to a room uses: rooms.start, rooms.end, rooms.rooms, rooms.caps, and room-mobs keys in a DungeonBlueprint. A duplicate name across files is rejected (see Validation).

gamemode

String

No

AstralRoomEditor authoring metadata (the gamemode the room was built in). Not read by AstralDungeons.

layout

String

No

AstralRoomEditor authoring metadata. Not read by AstralDungeons.

schematic-path

Path

Yes

WorldEdit schematic path (any format ClipboardFormats recognises, e.g. .schem), resolved against the dungeon server's data folder — not against rooms/. Loading skips the room if this file doesn't exist.

components

Map<String, RoomComponent>

Yes

Free-form component id → placed component. See Room components.

name: "boss_room" gamemode: "SURVIVAL" layout: "dungeons" schematic-path: "schematics/boss_room.schem" components: bounding_box: component-type: "cuboid" type: "bounding_box" min: { x: 0, y: 0, z: 0 } max: { x: 15, y: 8, z: 15 } dungeon_door_1: component-type: "oriented-cuboid" type: "dungeon_door" min: { x: 7, y: 1, z: 0 } max: { x: 8, y: 3, z: 0 } direction: "NORTH" boss: component-type: "position" type: "boss" position: { x: 8.5, y: 1.0, z: 8.5 } yaw: 0.0 pitch: 0.0 mob_1: component-type: "position" type: "mob" position: { x: 3.0, y: 1.0, z: 3.0 } yaw: 0.0 pitch: 0.0

Room components AstralDungeons reads

Each entry under components carries a RoomEditor primitive discriminator (component-type: position, typed-position, cuboid, orientation, or oriented-cuboid) plus a semantic type string that AstralDungeons matches on. Only a handful of types are meaningful to AstralDungeons:

type

Matched component-type (s)

Where

Required

Purpose

bounding_box

cuboid or oriented-cuboid (anything implementing CuboidComponent)

every room

Yes

The room's block bounds (RoomUtils.boundingBox), used to derive door facings and the room's footprint. A room with none throws while the dungeon blueprint referencing it loads — not caught as a graceful skip (see Validation).

dungeon_door

oriented-cuboid only

any room the generator should connect through

0+, one per doorway

A doorway the grid generator matches doors through. The map key must end in _<number> (e.g. dungeon_door_1, dungeon_door_2) — doors are sorted by that trailing number, and a non-numeric suffix throws. The door's facing (Cardinal) is derived, not read from the authored direction field: the door's box must be flush against exactly one of the bounding box's four walls (aligned on X or Z, never both, never neither) or extraction throws.

spawn (matched by map key, not type)

position or typed-position (anything instanceof PositionRoomComponent)

start room

Yes, to let players in

Where DungeonService#registerPlayer teleports each participant on join — looked up by the literal component id spawn. Missing it throws when the first player joins the instance, not at load time.

boss (matched by type)

position or typed-position

end room

Functionally required

Where the blueprint's boss mob spawns once a participant is within mobs.activation-range (Configuration). If no room in the generated instance has one, instance creation fails outright.

mob (matched by type)

position or typed-position

any room

No

Ambient mob spawn markers. MobListener scans for these as players move; which mob blueprint actually spawns at one is picked from the dungeon blueprint's room-mobs map for that room, never from anything on the marker itself.

Every position-family lookup above only checks instanceof PositionRoomComponenttyped-position (TypedPositionRoomComponent) is a subclass with an extra type-id field that AstralDungeons never reads, so either component-type works for spawn, boss, or mob markers.

Dungeon blueprints (blueprints/)

Field

Type

Description

id

String

Unique blueprint identifier. Also the id /dungeon create <blueprintId> and the server-advertisement heartbeat use. Duplicates are rejected — see Validation.

generation

Generation

Limits the grid generator uses to turn rooms into a procedural layout. See generation.

rooms

Rooms

The room pool: start, end, normal rooms, dead-end caps. See rooms.

room-mobs

Map<String, Set<String>>

Room name → set of AstralMobs blueprint ids eligible to spawn there. See room-mobs.

boss

String

AstralMobs blueprint id spawned at the end room's boss marker. See boss & mob-experiences.

mob-experiences

Map<String, Double>

AstralMobs blueprint id → experience awarded on kill. See boss & mob-experiences.

components-loot-tables

Map<String, DungeonLootTable>

Loot tables keyed by an arbitrary id — currently only pots is consumed (the reward-pot mechanic, not a RoomEditor component type). See Loot & Rewards.

mobs-loot-tables

Map<String, DungeonLootTable>

Loot tables keyed by AstralMobs blueprint id, same ids as mob-experiences. See Loot & Rewards.

id: "crypt_of_ashes" generation: min-rooms: 6 max-rooms: 10 cell-size: 16 max-consecutive-identical-room: 2 max-attempts: 20 rooms: start: "entrance_hall" end: "boss_room" rooms: - "corridor" - "mob_room" - "treasure_room" caps: - "dead_end_small" room-mobs: mob_room: - "skeleton_warrior" - "zombie_brute" corridor: - "skeleton_warrior" boss: "ashen_lich" mob-experiences: skeleton_warrior: 5.0 zombie_brute: 8.0 ashen_lich: 250.0 components-loot-tables: pots: minimum: 1 maximum: 3 entries: { } mobs-loot-tables: ashen_lich: minimum: 2 maximum: 4 entries: { }

generation

Field

Type

Description

min-rooms

int

Lower bound of the random room budget — the number of NORMAL rooms the generator tries to grow. Clamped to at least 1.

max-rooms

int

Upper bound of the room budget. Clamped to at least min-rooms's (post-clamp) value, so a misconfigured max-rooms < min-rooms can't produce an invalid range. DungeonGenerator rolls a budget uniformly at random in [min-rooms, max-rooms] once per creation attempt.

cell-size

int (blocks)

Size of one grid cell. Every referenced room's bounding box and doors are quantised into cell-size-block cells once, when the dungeon blueprint loads. See Footprints.

max-consecutive-identical-room

int

Upper bound on how many times the same room blueprint may chain parent-to-child in a row while the generator grows a corridor. <= 0 disables the limit.

max-attempts

int

How many full attempts (topology pass + world-space collision check) DungeonGenerator makes, each with a fresh seed, before giving up and failing instance creation with LayoutInfeasibleException.

rooms

Field

Type

Description

start

String

Room id used as the single START room — the generator's anchor point, and where the spawn marker lives.

end

String

Room id used as the single END (boss) room — where the boss marker lives.

rooms

Set<String>

Room ids available as NORMAL rooms the generator can grow the layout with. Must be non-empty in the YAML.

caps

Set<String>

Room ids available as CAP rooms — dead ends used to close any doorway the generator can't otherwise connect. Must be non-empty in the YAML.

Every id here must resolve against a room's name loaded from rooms/. See Validation for exactly what happens — and what still loads — when one doesn't.

room-mobs

Map of room name → set of AstralMobs blueprint ids eligible to spawn in that room. The map key is a room's name — the same id used in rooms.rooms/rooms.caps — not a per-instance concept, so the same room-mobs entry applies to every occurrence of that room blueprint across a generated layout (a NORMAL room can be placed more than once if the generator reuses it while growing).

Spawn locations come from the room's mob markers, activated as participants move through the instance (mobs.detection-range/mobs.activation-range, see Configuration). For each activated location, MobListener picks one id from that room's room-mobs set uniformly at random (equal weight per id) and spawns it there via MobsAPI. A room with no room-mobs entry (or an empty set) simply never spawns an ambient mob at its markers. See Dungeon Instances: Mob activation.

boss & mob-experiences

boss is a single AstralMobs blueprint id, spawned once via MobsAPI.spawnMob at the end room's boss marker the first time any participant comes within mobs.activation-range blocks of it (MobListener#trySpawnBoss, gated so it fires at most once per instance). If boss is missing or blank, spawning is skipped and a warning is logged — the marker's location was already required at instance-creation time regardless (see Room components), so a blank boss only means nothing ever appears there. See Dungeon Instances: Boss.

mob-experiences maps an AstralMobs blueprint id to a double experience value. It applies to any dungeon mob a participant kills — MobListener#onMobDeath resolves the killed entity's blueprint id and looks it up here regardless of whether the mob was an ambient room-mobs spawn or the boss. A mob with no entry drops no XP (logged as a warning). Experience accumulates on the killer's DungeonPlayerData and is paid out on the lobby once they leave the instance. See Loot & Rewards: Mob loot & experience.

components-loot-tables & mobs-loot-tables

Both are maps of id → DungeonLootTable. mobs-loot-tables is keyed by AstralMobs blueprint id (checked against the same ids as mob-experiences) and rolled on a qualifying kill. components-loot-tables is a general map, but only the pots key is currently consumed, rolled once per click on a DecoratedPot block in a room schematic. The full entry shape (entries/minimum/maximum), roll rules, and reward carry-over to the lobby are covered in Loot & Rewards.

Generation & the cell grid

Footprints (computed at load)

When a dungeon blueprint loads, BlueprintService converts every room it references into a DungeonRoomBlueprint — its type (START/END/NORMAL/CAP), its extracted doors, and a footprint quantised to that dungeon's generation.cell-size:

  1. The room's bounding box (inclusive block span) is divided by cell-size, rounded up, giving the footprint's width and length in cells (wCells/lCells).

  2. Each door's local anchor — the center of its min/max box, in the room's own schematic-relative coordinates — is mapped to the local cell it falls in and kept alongside the wall edge (Cardinal) the door was derived to face.

This footprint — not the raw block geometry — is what the grid generator places and rotates.

A room whose block span isn't an exact multiple of cell-size, whose door anchor falls outside the footprint, or whose door isn't roughly centered on the cell edge it's assigned to, is not grid-conforming. This is logged as a [gen] room '<name>' not grid-conforming: ... warning at load time for every such room — it never blocks loading or changes generator behavior; it's a diagnostic for whoever is authoring rooms against that dungeon's cell size.

Generating a layout (instance creation)

At instance creation, DungeonGenerator retries up to generation.max-attempts times, each attempt using a fresh seed:

  1. Topology. A pure, Bukkit-free grid engine (GridLayout) works entirely in integer cells: it seeds the START room's footprint at the origin, grows NORMAL rooms while the random room budget allows, then resolves — the END room claims the deepest still-open doorway it fits, and every remaining open doorway is closed with a CAP room. A placement is only valid under forced exact-edge matching: every footprint cell-edge that abuts a placed room must have a door meeting a door on the neighbour facing back — there are no sealed dead doors.

  2. Realization. The accepted topology is converted to world space by chaining door anchors, not grid coordinates: walking the connection tree out from START, each room is positioned so its connector door's world anchor lands exactly on its already-placed parent's door anchor, so any two connected rooms meet flush at the doorway regardless of footprint size.

  3. Collision check. Because door-anchored placement can in principle overlap, the realized layout is checked with a real bounding-box test (a 2-block horizontal inset lets a flush shared wall pass) — a colliding layout is discarded and the whole attempt retried with the next seed.

Exhausting max-attempts without a non-overlapping, fully-connected layout throws LayoutInfeasibleException, which fails the creation request (logged, and the requesting server replies success=false). See Dungeon Instances: Cross-server creation handshake.

Validation

BlueprintService.load() clears and fully re-scans both folders every time it runs (enable, or /dungeon reload).

Rooms — a room is genuinely dropped (never enters the room registry, so nothing can reference it):

  • Its name duplicates one already loaded from another file.

  • Its schematic-path doesn't resolve to an existing file on disk.

Dungeon blueprints — the duplicate-id check is a true drop:

  • A blueprint whose id duplicates one already loaded is skipped entirely.

The remaining rooms-block checks are logged as warnings that say "Skipping", but they only skip populating that blueprint's processed room pool — the blueprint itself is still registered, still resolvable by /dungeon create, and still advertised on the server heartbeat:

  • A blueprint whose rooms.start or rooms.end doesn't resolve to a loaded room, or whose rooms.rooms or rooms.caps is empty in the YAML, still loads with a completely empty room pool. Nothing surfaces at load time — generation itself requires a START room in the pool and throws IllegalStateException("pool has no START room") immediately on the first attempt. That exception is deliberately not caught by the attempt-retry loop (which only retries LayoutInfeasibleException), so /dungeon create against that blueprint fails outright, every time, until the blueprint is fixed and the server reloaded.

  • A blueprint whose rooms.rooms/rooms.caps set is non-empty in the YAML but contains individual ids that don't resolve still gets its START and END room — only the NORMAL/CAP pool shrinks (each unresolved id is filtered out one at a time, with its own warning; the rest of the set still loads). This can still fail generation if too few rooms/caps remain to place or close every doorway, but as a retried LayoutInfeasibleException, not an immediate hard failure.

Not caught at load time at all — these throw unhandled exceptions from inside the per-dungeon loop while a dungeon blueprint that references the offending room is being processed. Because nothing in that loop catches them, they abort the rest of load() for every blueprint not yet processed in that call (blueprints already committed in earlier loop iterations stay loaded). On /dungeon reload this surfaces as "Failed to reload dungeon blueprints: ..." to the invoking player; on server start it propagates out of onEnable() entirely.

  • A room referenced by the dungeon has no bounding_box component.

  • A dungeon_door component isn't flush against exactly one bounding-box wall, or its map key has no trailing _<number> suffix to sort by.

Finally, two checks aren't part of load() at all — they only fail once a party actually creates or joins an instance of the blueprint (see Room components):

  • No room in the generated instance has a boss-typed marker — instance creation throws outright.

  • The start room has no spawn-id marker — throws when the first player joins the instance.

Hot reload

/dungeon reload (permission dungeons.command + dungeons.reload, dungeon server only) calls BlueprintService.load() again — both folders are fully re-scanned from disk, so blueprint files added, edited, or removed since the last load take effect immediately. It does not affect dungeon instances already running. See Configuration: Reload.

Last modified: 25 July 2026